From 13214d288118979cfd47e4a47c429dae31dcaffe Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:45:25 +0200 Subject: [PATCH 01/12] Initial editor system --- ui/components-react/package.json | 1 + .../newEditors/CommittingEditor.tsx | 63 +++++++ .../newEditors/DefaultEditors.tsx | 39 ++++ .../components-react/newEditors/Editor.tsx | 56 ++++++ .../newEditors/EditorInterop.ts | 177 ++++++++++++++++++ .../newEditors/FormatOverrides.ts | 61 ++++++ .../src/components-react/newEditors/Types.ts | 169 +++++++++++++++++ .../newEditors/WithFormatOverrides.tsx | 23 +++ .../editors/boolean-editor/BooleanEditor.tsx | 25 +++ .../boolean-editor/UseBooleanEditorProps.ts | 26 +++ .../editors/date-editor/DateTimeEditor.tsx | 33 ++++ .../date-editor/UseDateTimeEditorProps.ts | 26 +++ .../editors/enum-editor/EnumEditor.tsx | 27 +++ .../editors/enum-editor/UseEnumEditorProps.ts | 41 ++++ .../editors/numeric-editor/NumericEditor.tsx | 25 +++ .../numeric-editor/ParsedNumericInput.tsx | 113 +++++++++++ .../editors/numeric-editor/QuantityInput.tsx | 57 ++++++ .../numeric-editor/UseNumericEditorProps.ts | 28 +++ .../editors/text-editor/TextEditor.tsx | 19 ++ .../editors/text-editor/UseTextEditorProps.ts | 26 +++ .../editorsRegistry/EditorsRegistry.tsx | 17 ++ .../EditorsRegistryProvider.tsx | 29 +++ .../src/components-react/newEditors/index.ts | 19 ++ 23 files changed, 1100 insertions(+) create mode 100644 ui/components-react/src/components-react/newEditors/CommittingEditor.tsx create mode 100644 ui/components-react/src/components-react/newEditors/DefaultEditors.tsx create mode 100644 ui/components-react/src/components-react/newEditors/Editor.tsx create mode 100644 ui/components-react/src/components-react/newEditors/EditorInterop.ts create mode 100644 ui/components-react/src/components-react/newEditors/FormatOverrides.ts create mode 100644 ui/components-react/src/components-react/newEditors/Types.ts create mode 100644 ui/components-react/src/components-react/newEditors/WithFormatOverrides.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/boolean-editor/BooleanEditor.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/boolean-editor/UseBooleanEditorProps.ts create mode 100644 ui/components-react/src/components-react/newEditors/editors/date-editor/DateTimeEditor.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/date-editor/UseDateTimeEditorProps.ts create mode 100644 ui/components-react/src/components-react/newEditors/editors/enum-editor/EnumEditor.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/enum-editor/UseEnumEditorProps.ts create mode 100644 ui/components-react/src/components-react/newEditors/editors/numeric-editor/NumericEditor.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/numeric-editor/ParsedNumericInput.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/numeric-editor/QuantityInput.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/numeric-editor/UseNumericEditorProps.ts create mode 100644 ui/components-react/src/components-react/newEditors/editors/text-editor/TextEditor.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editors/text-editor/UseTextEditorProps.ts create mode 100644 ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx create mode 100644 ui/components-react/src/components-react/newEditors/index.ts diff --git a/ui/components-react/package.json b/ui/components-react/package.json index b2569c7a967..e48fa8386fa 100644 --- a/ui/components-react/package.json +++ b/ui/components-react/package.json @@ -57,6 +57,7 @@ "@itwin/appui-abstract": "^4.0.0", "@itwin/core-bentley": "^4.0.0", "@itwin/core-react": "workspace:^5.1.0-dev.0", + "@itwin/core-quantity": "^4.0.0", "@itwin/itwinui-react": "^3.15.0", "react": "^18.0.0", "react-dom": "^18.0.0" diff --git a/ui/components-react/src/components-react/newEditors/CommittingEditor.tsx b/ui/components-react/src/components-react/newEditors/CommittingEditor.tsx new file mode 100644 index 00000000000..d5e92ba21eb --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/CommittingEditor.tsx @@ -0,0 +1,63 @@ +import * as React from "react"; +import { useRef, useState } from "react"; +import type { Value, ValueMetadata } from "./Types.js"; +import { Editor } from "./Editor.js"; + +interface CommittingEditorProps { + metadata: ValueMetadata; + value?: Value; + onCommit: (value: Value) => void; + onCancel?: () => void; + size?: "small" | "large"; +} + +/** + * + */ +export function CommittingEditor({ + metadata, + value, + onCommit, + onCancel, + size, +}: CommittingEditorProps) { + const [currentValue, setCurrentValue] = useState(); + const currentValueRef = useRef(currentValue); + + const handleChange = (newValue: Value) => { + currentValueRef.current = newValue; + setCurrentValue(newValue); + }; + + const handleCommit = () => { + if (currentValueRef.current !== undefined) { + onCommit(currentValueRef.current); + return; + } + + onCancel?.(); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + e.stopPropagation(); + + if (e.key === "Enter" || e.key === "Tab") { + handleCommit(); + } else if (e.key === "Escape") { + onCancel?.(); + } + }; + + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
+ +
+ ); +} diff --git a/ui/components-react/src/components-react/newEditors/DefaultEditors.tsx b/ui/components-react/src/components-react/newEditors/DefaultEditors.tsx new file mode 100644 index 00000000000..87dfcfa762d --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/DefaultEditors.tsx @@ -0,0 +1,39 @@ +import { BooleanEditor } from "./editors/boolean-editor/BooleanEditor.js"; +import { DateTimeEditor } from "./editors/date-editor/DateTimeEditor.js"; +import { EnumEditor } from "./editors/enum-editor/EnumEditor.js"; +import { NumericEditor } from "./editors/numeric-editor/NumericEditor.js"; +import { TextEditor } from "./editors/text-editor/TextEditor.js"; +import type { EditorSpec } from "./Types.js"; + +export const TextEditorSpec: EditorSpec = { + applies: (metadata) => metadata.type === "string", + Editor: TextEditor, +}; + +export const DateEditorSpec: EditorSpec = { + applies: (metadata) => metadata.type === "date", + Editor: DateTimeEditor, +}; + +export const BoolEditorSpec: EditorSpec = { + applies: (metadata) => metadata.type === "bool", + Editor: BooleanEditor, +}; + +export const NumericEditorSpec: EditorSpec = { + applies: (metadata) => metadata.type === "number", + Editor: NumericEditor, +}; + +export const EnumEditorSpec: EditorSpec = { + applies: (metadata) => metadata.type === "enum", + Editor: EnumEditor, +}; + +export const defaultEditors: EditorSpec[] = [ + TextEditorSpec, + BoolEditorSpec, + NumericEditorSpec, + DateEditorSpec, + EnumEditorSpec, +]; diff --git a/ui/components-react/src/components-react/newEditors/Editor.tsx b/ui/components-react/src/components-react/newEditors/Editor.tsx new file mode 100644 index 00000000000..93e54368992 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/Editor.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import { defaultEditors } from "./DefaultEditors.js"; +import { useEditorsRegistry } from "./editorsRegistry/EditorsRegistry.js"; +import type { EditorProps, EditorSpec, Value, ValueMetadata } from "./Types.js"; + +function noopOnFinish() {} + +/** + * + */ +export function Editor({ + metadata, + value, + onChange, + onFinish, + size, +}: EditorProps) { + const TypeEditor = useEditor(metadata, value); + + if (!TypeEditor) { + return null; + } + + return ( + + ); +} + +function useEditor( + metadata: ValueMetadata, + value: Value | undefined +): EditorSpec["Editor"] | undefined { + const { editors } = useEditorsRegistry(); + + const registeredEditor = editors.find((editor) => + editor.applies(metadata, value) + )?.Editor; + if (registeredEditor) { + return registeredEditor; + } + + const defaultEditor = defaultEditors.find((editor) => + editor.applies(metadata, value) + )?.Editor; + if (defaultEditor) { + return defaultEditor; + } + + throw new Error(`No editor found for metadata: ${JSON.stringify(metadata)}`); +} diff --git a/ui/components-react/src/components-react/newEditors/EditorInterop.ts b/ui/components-react/src/components-react/newEditors/EditorInterop.ts new file mode 100644 index 00000000000..ab50d4418b5 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/EditorInterop.ts @@ -0,0 +1,177 @@ +import type { + PrimitiveValue, + PropertyEditorParams, + PropertyRecord, +} from "@itwin/appui-abstract"; +import { PropertyValueFormat } from "@itwin/appui-abstract"; +import type { + BooleanValue, + DateValue, + Value as EditorValue, + EnumValueMetadata, + NumericValue, + TextValue, + ValueMetadata, +} from "./Types.js"; +import { + isBooleanValue, + isDateTimeValue, + isEnumValue, + isNumericValue, + isTextValue, +} from "./Types.js"; + +export namespace EditorInterop { + /** + * + */ + export interface NumericEditorMetadata extends ValueMetadata { + type: "number"; + params: PropertyEditorParams[]; + } + + /** + * + */ + export function isNumericEditorMetadata( + metadata: ValueMetadata + ): metadata is NumericEditorMetadata { + return metadata.type === "number" && "params" in metadata; + } + + /** + * + */ + export function getMetadataAndValue(propertyRecord: PropertyRecord): { + metadata: ValueMetadata | undefined; + value: EditorValue | undefined; + } { + const baseMetadata: Omit = { + preferredEditor: propertyRecord.property.editor?.name, + ...(propertyRecord.property.editor + ? { params: propertyRecord.property.editor.params } + : {}), + ...(propertyRecord.property.enum + ? { choices: propertyRecord.property.enum.choices } + : {}), + ...(propertyRecord.property.quantityType + ? { quantityType: propertyRecord.property.quantityType } + : {}), + ...propertyRecord.extendedData, + }; + + const primitiveValue = propertyRecord.value as PrimitiveValue; + switch (propertyRecord.property.typename) { + case "text": + case "string": + return { + metadata: { + ...baseMetadata, + type: "string", + }, + value: { + value: (primitiveValue.value as string) ?? "", + } satisfies TextValue, + }; + case "dateTime": + case "shortdate": + return { + metadata: { + ...baseMetadata, + type: "date", + }, + value: { + value: (primitiveValue.value as Date) ?? new Date(), + } satisfies DateValue, + }; + case "boolean": + case "bool": + return { + metadata: { + ...baseMetadata, + type: "bool", + }, + value: { + value: (primitiveValue.value as boolean) ?? false, + } satisfies BooleanValue, + }; + case "float": + case "double": + case "int": + case "integer": + case "number": + return { + metadata: { + ...baseMetadata, + type: "number", + } as ValueMetadata, + value: { + rawValue: primitiveValue.value as number, + displayValue: primitiveValue.displayValue ?? "", + } satisfies NumericValue, + }; + case "enum": + return { + metadata: { + ...baseMetadata, + type: "enum", + } as EnumValueMetadata, + value: { + choice: primitiveValue.value as number | string, + label: primitiveValue.displayValue as string, + }, + }; + } + + return { + metadata: undefined, + value: undefined, + }; + } + + /** + * + */ + export function convertToPrimitiveValue( + newValue: EditorValue + ): PrimitiveValue { + if (isTextValue(newValue)) { + return { + valueFormat: PropertyValueFormat.Primitive, + value: newValue.value, + displayValue: newValue.value, + }; + } + if (isNumericValue(newValue)) { + return { + valueFormat: PropertyValueFormat.Primitive, + value: newValue.rawValue, + displayValue: newValue.displayValue, + }; + } + if (isBooleanValue(newValue)) { + return { + valueFormat: PropertyValueFormat.Primitive, + value: newValue.value, + displayValue: newValue.value.toString(), + }; + } + if (isDateTimeValue(newValue)) { + return { + valueFormat: PropertyValueFormat.Primitive, + value: newValue.value, + displayValue: newValue.value.toString(), + }; + } + + if (isEnumValue(newValue)) { + return { + valueFormat: PropertyValueFormat.Primitive, + value: newValue.choice, + displayValue: newValue.label, + }; + } + + throw new Error("Invalid value type"); + } +} diff --git a/ui/components-react/src/components-react/newEditors/FormatOverrides.ts b/ui/components-react/src/components-react/newEditors/FormatOverrides.ts new file mode 100644 index 00000000000..fe07265a332 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/FormatOverrides.ts @@ -0,0 +1,61 @@ +import type { FormatProps, UnitSystemKey } from "@itwin/core-quantity"; + +/** + * A data structure that associates unit systems with property value formatting props. The associations are used for + * assigning formatting props for specific phenomenon and unit system combinations (see [[FormatsMap]]). + * + * @public + */ +export interface UnitSystemFormat { + unitSystems: UnitSystemKey[]; + format: FormatProps; +} +/** + * A data structure that associates specific phenomenon with one or more formatting props for specific unit system. + * + * Example: + * ```json + * { + * length: [{ + * unitSystems: ["metric"], + * format: formatForCentimeters, + * }, { + * unitSystems: ["imperial", "usCustomary"], + * format: formatForInches, + * }, { + * unitSystems: ["usSurvey"], + * format: formatForUsSurveyInches, + * }] + * } + * ``` + * + * @public + */ +export interface FormatOverrides { + [phenomenon: string]: UnitSystemFormat | UnitSystemFormat[]; +} + +interface MatchingFormatOverrideProps { + overrides: FormatOverrides; + phenomenon: string; + unitSystem: UnitSystemKey; +} + +/** @public */ +export function getMatchingFormatOverride({ + overrides, + phenomenon, + unitSystem, +}: MatchingFormatOverrideProps): FormatProps | undefined { + const overridesForPhenomenon = overrides[phenomenon]; + if (!overridesForPhenomenon) { + return undefined; + } + + const overridesArray = Array.isArray(overridesForPhenomenon) + ? overridesForPhenomenon + : [overridesForPhenomenon]; + return overridesArray.find((override) => + override.unitSystems.includes(unitSystem) + )?.format; +} diff --git a/ui/components-react/src/components-react/newEditors/Types.ts b/ui/components-react/src/components-react/newEditors/Types.ts new file mode 100644 index 00000000000..a26e3fdbf7c --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/Types.ts @@ -0,0 +1,169 @@ +/** + * + */ +export interface NumericValue { + rawValue: number | undefined; + displayValue: string; + roundingError?: number; +} + +/** + * + */ +export interface InstanceKeyValue { + key: { id: string; className: string }; + label: string; +} + +/** + * + */ +export interface TextValue { + value: string; +} + +/** + * + */ +export interface BooleanValue { + value: boolean; +} + +/** + * + */ +export interface DateValue { + value: Date; +} + +/** + * + */ +export interface EnumValue { + choice: number | string; + label: string; +} + +/** + * + */ +export type Value = + | NumericValue + | InstanceKeyValue + | TextValue + | BooleanValue + | DateValue + | EnumValue; + +/** + * + */ +export function isTextValue( + value: Value | undefined +): value is TextValue | undefined { + return ( + value === undefined || ("value" in value && typeof value.value === "string") + ); +} + +/** + * + */ +export function isNumericValue( + value: Value | undefined +): value is NumericValue | undefined { + return ( + value === undefined || ("rawValue" in value && "displayValue" in value) + ); +} + +/** + * + */ +export function isBooleanValue( + value: Value | undefined +): value is BooleanValue | undefined { + return ( + value === undefined || + ("value" in value && typeof value.value === "boolean") + ); +} + +/** + * + */ +export function isDateTimeValue( + value: Value | undefined +): value is DateValue | undefined { + return ( + value === undefined || ("value" in value && value.value instanceof Date) + ); +} + +/** + * + */ +export function isEnumValue( + value: Value | undefined +): value is EnumValue | undefined { + return value === undefined || ("choice" in value && "label" in value); +} + +/** + * + */ +export type ValueType = "string" | "number" | "bool" | "date" | "enum"; + +/** + * + */ +export interface ValueMetadata { + type: ValueType; + preferredEditor?: string; +} +/** + * + */ +export interface EnumChoice { + value: number | string; + label: string; +} + +/** + * + */ +export interface EnumValueMetadata extends ValueMetadata { + type: "enum"; + choices: EnumChoice[]; +} + +/** + * + */ +export interface EditorSpec { + applies: (metaData: ValueMetadata, value: Value | undefined) => boolean; + Editor: React.ComponentType; +} + +interface BaseEditorProps { + metadata: ValueMetadata; + onChange: (value: TValue) => void; + onFinish: () => void; + disabled?: boolean; + size?: "small" | "large"; +} + +/** + * + */ +export interface EditorProps extends BaseEditorProps { + value?: TValue; +} + +/** + * + */ +export interface SpecificEditorProps + extends BaseEditorProps { + value: TValue; +} diff --git a/ui/components-react/src/components-react/newEditors/WithFormatOverrides.tsx b/ui/components-react/src/components-react/newEditors/WithFormatOverrides.tsx new file mode 100644 index 00000000000..90d7ac94825 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/WithFormatOverrides.tsx @@ -0,0 +1,23 @@ +import * as React from "react"; +import type { ComponentType } from "react"; +import type { FormatOverrides } from "./FormatOverrides.js"; +import type { EditorProps } from "./Types.js"; + +/** + * + */ +export interface EditorPropsWithFormatOverrides extends EditorProps { + formatOverrides?: FormatOverrides; +} + +/** + * + */ +export function withFormatOverrides( + BaseEditor: ComponentType, + formatOverrides: FormatOverrides +) { + return function EditorWithFormatOverrides(props: EditorProps) { + return ; + }; +} diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean-editor/BooleanEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/boolean-editor/BooleanEditor.tsx new file mode 100644 index 00000000000..8b858332b2f --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/boolean-editor/BooleanEditor.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; +import { ToggleSwitch } from "@itwin/itwinui-react"; +import type { EditorProps } from "../../Types.js"; +import { useBooleanEditorProps } from "./UseBooleanEditorProps.js"; + +/** + * + */ +export function BooleanEditor(props: EditorProps) { + const { value, onChange, onFinish } = useBooleanEditorProps(props); + + const handleChange = (e: React.ChangeEvent) => { + const newValue = { value: e.target.checked }; + onChange(newValue); + onFinish(); + }; + + return ( + + ); +} diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean-editor/UseBooleanEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/boolean-editor/UseBooleanEditorProps.ts new file mode 100644 index 00000000000..c241d807344 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/boolean-editor/UseBooleanEditorProps.ts @@ -0,0 +1,26 @@ +import type { + BooleanValue, + EditorProps, + SpecificEditorProps, + Value, +} from "../../Types.js"; +import { isBooleanValue } from "../../Types.js"; + +/** + * + */ +export function useBooleanEditorProps({ + value, + onChange, + ...rest +}: EditorProps): SpecificEditorProps { + return { + ...rest, + value: getBooleanValue(value), + onChange, + }; +} + +function getBooleanValue(value: Value | undefined): BooleanValue { + return value && isBooleanValue(value) ? value : { value: false }; +} diff --git a/ui/components-react/src/components-react/newEditors/editors/date-editor/DateTimeEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/date-editor/DateTimeEditor.tsx new file mode 100644 index 00000000000..dca8d87383c --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/date-editor/DateTimeEditor.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; +import { Button, DatePicker, Popover } from "@itwin/itwinui-react"; +import type { EditorProps } from "../../Types.js"; +import { useDateTimeEditorProps } from "./UseDateTimeEditorProps.js"; + +/** + * + */ +export function DateTimeEditor(props: EditorProps) { + const { value, onChange, onFinish } = useDateTimeEditorProps(props); + const dateStr = value.value.toLocaleDateString(); + + return ( + { + onChange({ value: e }); + }} + showDatesOutsideMonth={false} + /> + } + onVisibleChange={(visible) => { + if (!visible) { + onFinish(); + } + }} + > + + + ); +} diff --git a/ui/components-react/src/components-react/newEditors/editors/date-editor/UseDateTimeEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/date-editor/UseDateTimeEditorProps.ts new file mode 100644 index 00000000000..86abd0f8c56 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/date-editor/UseDateTimeEditorProps.ts @@ -0,0 +1,26 @@ +import type { + DateValue, + EditorProps, + SpecificEditorProps, + Value, +} from "../../Types.js"; +import { isDateTimeValue } from "../../Types.js"; + +/** + * + */ +export function useDateTimeEditorProps({ + value, + onChange, + ...rest +}: EditorProps): SpecificEditorProps { + return { + ...rest, + value: getDateTimeValue(value), + onChange, + }; +} + +function getDateTimeValue(value: Value | undefined): DateValue { + return value && isDateTimeValue(value) ? value : { value: new Date() }; +} diff --git a/ui/components-react/src/components-react/newEditors/editors/enum-editor/EnumEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/enum-editor/EnumEditor.tsx new file mode 100644 index 00000000000..6ae150506a7 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/enum-editor/EnumEditor.tsx @@ -0,0 +1,27 @@ +import * as React from "react"; +import { Select } from "@itwin/itwinui-react"; +import type { EditorProps } from "../../Types.js"; +import { useEnumEditorProps } from "./UseEnumEditorProps.js"; + +/** + * + */ +export function EnumEditor(props: EditorProps) { + const { value, onChange, onFinish, choices } = useEnumEditorProps(props); + + const handleChange = (newChoice: number | string) => { + const choice = choices.find((c) => c.value === newChoice); + const newValue = { choice: newChoice, label: choice?.label ?? "" }; + onChange(newValue); + onFinish(); + }; + + return ( + + onChange({ + rawValue: parseFloat(e.target.value), + displayValue: e.target.value, + }) + } + onBlur={onFinish} + size={props.size} + /> + ); +} diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/ParsedNumericInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric-editor/ParsedNumericInput.tsx new file mode 100644 index 00000000000..dcbf1945947 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/numeric-editor/ParsedNumericInput.tsx @@ -0,0 +1,113 @@ +import * as React from "react"; +import { Input } from "@itwin/itwinui-react"; +import type { NumericValue } from "../../Types.js"; + +interface ParsedNumericInputProps { + value: NumericValue; + onChange: (value: NumericValue) => void; + parseValue: (value: string) => number | undefined; + formatValue: (num: number) => string; + disabled?: boolean; + size?: "small" | "large"; + onBlur: () => void; +} + +/** + * + */ +export function ParsedNumericInput({ + onChange, + value, + parseValue, + formatValue, + disabled, + onBlur, + size, +}: ParsedNumericInputProps) { + const { currentValue, inputProps } = useParsedNumberInput({ + initialValue: value.rawValue, + parseValue, + formatValue, + }); + const onChangeRef = React.useRef(onChange); + React.useEffect(() => { + onChangeRef.current = onChange; + }, [onChange]); + + React.useEffect(() => { + onChangeRef.current(currentValue); + }, [currentValue]); + + return ( + + ); +} + +interface HookProps { + initialValue: number | undefined; + parseValue: (value: string) => number | undefined; + formatValue: (num: number) => string; +} + +function useParsedNumberInput({ + initialValue, + formatValue, + parseValue, +}: HookProps) { + interface State { + value: NumericValue; + placeholder: string; + } + + const [state, setState] = React.useState(() => { + return { + value: { + rawValue: initialValue, + displayValue: + initialValue !== undefined ? formatValue(initialValue) : "", + }, + placeholder: formatValue(123.45), + }; + }); + + React.useEffect(() => { + setState((prevState) => { + return { + ...prevState, + value: { + ...prevState.value, + displayValue: + prevState.value.rawValue !== undefined + ? formatValue(prevState.value.rawValue) + : "", + }, + placeholder: formatValue(123.45), + }; + }); + }, [formatValue]); + + const handleChange = (e: React.ChangeEvent) => { + const newValue = e.target.value; + const rawValue = parseValue(newValue); + + setState((prevState) => { + return { + ...prevState, + value: { + ...prevState.value, + rawValue, + displayValue: newValue, + }, + }; + }); + }; + + return { + currentValue: state.value, + inputProps: { + value: state.value.displayValue, + placeholder: state.placeholder, + onChange: handleChange, + }, + }; +} diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/QuantityInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric-editor/QuantityInput.tsx new file mode 100644 index 00000000000..a56b93c39ce --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/numeric-editor/QuantityInput.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; +import { ParsedNumericInput } from "./ParsedNumericInput.js"; +import type { FormatterSpec, ParserSpec } from "@itwin/core-quantity"; + +type ParsedNumericInputProps = React.ComponentPropsWithoutRef< + typeof ParsedNumericInput +>; + +interface QuantityInputProps + extends Omit< + ParsedNumericInputProps, + "parseValue" | "formatValue" | "disabled" + > { + formatter?: FormatterSpec; + parser?: ParserSpec; +} + +/** + * + */ +export function QuantityInput({ + formatter, + parser, + ...props +}: QuantityInputProps) { + const initialValue = React.useRef(props.value); + const formatValue = React.useCallback( + (currValue: number) => { + if (!formatter) { + return initialValue.current.displayValue; + } + return formatter.applyFormatting(currValue); + }, + [formatter] + ); + + const parseString = React.useCallback( + (userInput: string) => { + if (!parser) { + return undefined; + } + const parseResult = parser.parseToQuantityValue(userInput); + return parseResult.ok ? parseResult.value : undefined; + }, + [parser] + ); + + return ( + + ); +} diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/UseNumericEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/numeric-editor/UseNumericEditorProps.ts new file mode 100644 index 00000000000..dbf35a5d4fa --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/numeric-editor/UseNumericEditorProps.ts @@ -0,0 +1,28 @@ +import type { + EditorProps, + NumericValue, + SpecificEditorProps, + Value, +} from "../../Types.js"; +import { isNumericValue } from "../../Types.js"; + +/** + * + */ +export function useNumericEditorProps({ + value, + onChange, + ...rest +}: EditorProps): SpecificEditorProps { + return { + ...rest, + value: getNumericValue(value), + onChange, + }; +} + +function getNumericValue(value: Value | undefined): NumericValue { + return value && isNumericValue(value) + ? value + : { rawValue: undefined, displayValue: "" }; +} diff --git a/ui/components-react/src/components-react/newEditors/editors/text-editor/TextEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/text-editor/TextEditor.tsx new file mode 100644 index 00000000000..7cee0c190c1 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/text-editor/TextEditor.tsx @@ -0,0 +1,19 @@ +import * as React from "react"; +import { Input } from "@itwin/itwinui-react"; +import type { EditorProps } from "../../Types.js"; +import { useTextEditorProps } from "./UseTextEditorProps.js"; + +/** + * + */ +export function TextEditor(props: EditorProps) { + const { value, onChange, onFinish } = useTextEditorProps(props); + return ( + onChange({ value: e.target.value })} + size={props.size} + onBlur={onFinish} + /> + ); +} diff --git a/ui/components-react/src/components-react/newEditors/editors/text-editor/UseTextEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/text-editor/UseTextEditorProps.ts new file mode 100644 index 00000000000..23ced0cba8f --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/text-editor/UseTextEditorProps.ts @@ -0,0 +1,26 @@ +import type { + EditorProps, + SpecificEditorProps, + TextValue, + Value, +} from "../../Types.js"; +import { isTextValue } from "../../Types.js"; + +/** + * + */ +export function useTextEditorProps({ + value, + onChange, + ...rest +}: EditorProps): SpecificEditorProps { + return { + ...rest, + value: getTextValue(value), + onChange, + }; +} + +function getTextValue(value: Value | undefined): TextValue { + return value && isTextValue(value) ? value : { value: "" }; +} diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx new file mode 100644 index 00000000000..d456bc23f05 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx @@ -0,0 +1,17 @@ +import { createContext, useContext } from "react"; +import type { EditorSpec } from "../Types.js"; + +interface EditorsRegistry { + editors: EditorSpec[]; +} + +export const editorsRegistryContext = createContext({ + editors: [], +}); + +/** + * + */ +export function useEditorsRegistry() { + return useContext(editorsRegistryContext); +} diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx new file mode 100644 index 00000000000..57657b89705 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx @@ -0,0 +1,29 @@ +import * as React from "react"; +import { useContext, useMemo } from "react"; +import type { EditorSpec } from "../Types.js"; +import { editorsRegistryContext } from "./EditorsRegistry.js"; + +/** + * + */ +export function EditorsRegistryProvider({ + children, + editors, +}: { + children: React.ReactNode; + editors: EditorSpec[]; +}) { + const parentContext = useContext(editorsRegistryContext); + + const value = useMemo(() => { + return { + editors: [...parentContext.editors, ...editors], + }; + }, [parentContext, editors]); + + return ( + + {children} + + ); +} diff --git a/ui/components-react/src/components-react/newEditors/index.ts b/ui/components-react/src/components-react/newEditors/index.ts new file mode 100644 index 00000000000..361ec71f247 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/index.ts @@ -0,0 +1,19 @@ +export * from "./Editor.js"; +export * from "./CommittingEditor.js"; +export * from "./Types.js"; +export * from "./DefaultEditors.js"; +export * from "./editors/boolean-editor/BooleanEditor.js"; +export * from "./editors/boolean-editor/UseBooleanEditorProps.js"; +export * from "./editors/date-editor/DateTimeEditor.js"; +export * from "./editors/date-editor/UseDateTimeEditorProps.js"; +export * from "./editors/numeric-editor/NumericEditor.js"; +export * from "./editors/numeric-editor/UseNumericEditorProps.js"; +export * from "./editors/numeric-editor/ParsedNumericInput.js"; +export * from "./editors/numeric-editor/QuantityInput.js"; +export * from "./editors/text-editor/TextEditor.js"; +export * from "./editors/text-editor/UseTextEditorProps.js"; +export * from "./editors/enum-editor/EnumEditor.js"; +export * from "./editors/enum-editor/UseEnumEditorProps.js"; +export * from "./editorsRegistry/EditorsRegistryProvider.js"; +export * from "./FormatOverrides.js"; +export * from "./WithFormatOverrides.js"; From 2fc52ebc3de764c9eeecc41d8535a5d84d8c9490 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:45:39 +0200 Subject: [PATCH 02/12] Use new editors in property grid --- ui/components-react/src/components-react.ts | 2 ++ .../component/VirtualizedPropertyGrid.tsx | 4 +++ ...irtualizedPropertyGridWithDataProvider.tsx | 2 ++ .../flat-properties/FlatPropertyRenderer.tsx | 28 ++++++++++++++++++- 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/ui/components-react/src/components-react.ts b/ui/components-react/src/components-react.ts index 73c1df8719f..f57578c9177 100644 --- a/ui/components-react/src/components-react.ts +++ b/ui/components-react/src/components-react.ts @@ -92,6 +92,8 @@ export { } from "./components-react/datepicker/DatePickerPopupButton.js"; export { IntlFormatter } from "./components-react/datepicker/IntlFormatter.js"; +export { EditorInterop } from "./components-react/newEditors/EditorInterop.js"; +export * from "./components-react/newEditors/index.js"; export { BooleanEditor, BooleanPropertyEditor, diff --git a/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGrid.tsx b/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGrid.tsx index 76b95ce6605..f75706af941 100644 --- a/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGrid.tsx +++ b/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGrid.tsx @@ -69,6 +69,7 @@ export interface VirtualizedPropertyGridProps extends CommonPropertyGridProps { width: number; /** Height of the property grid component. */ height: number; + usedEditor?: "old" | "new"; } /** State of [[VirtualizedPropertyGrid]] React component @@ -125,6 +126,7 @@ export interface VirtualizedPropertyGridContext { category: PropertyCategory ) => void; onEditCancel?: () => void; + usedEditor: "old" | "new"; eventHandler: IPropertyGridEventHandler; dataProvider: IPropertyDataProvider; @@ -385,6 +387,7 @@ export class VirtualizedPropertyGrid extends React.Component< editingPropertyKey: selectionContext.editingPropertyKey, onEditCommit: selectionContext.onEditCommit, onEditCancel: selectionContext.onEditCancel, + usedEditor: this.props.usedEditor ?? "old", eventHandler: this.props.eventHandler, dataProvider: this.props.dataProvider, @@ -609,6 +612,7 @@ const FlatGridItemNode = React.memo( alwaysShowEditor={gridContext.alwaysShowEditor} onEditCommit={gridContext.onEditCommit} onEditCancel={gridContext.onEditCancel} + usedEditor={gridContext.usedEditor} isExpanded={node.isExpanded} onExpansionToggled={onExpansionToggled} onHeightChanged={onHeightChanged} diff --git a/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGridWithDataProvider.tsx b/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGridWithDataProvider.tsx index c4e866b8038..36dee87a387 100644 --- a/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGridWithDataProvider.tsx +++ b/ui/components-react/src/components-react/propertygrid/component/VirtualizedPropertyGridWithDataProvider.tsx @@ -38,6 +38,7 @@ export interface VirtualizedPropertyGridWithDataProviderProps width: number; /** Height of the property grid component. */ height: number; + usedEditor?: "old" | "new"; } /** @@ -63,6 +64,7 @@ export function VirtualizedPropertyGridWithDataProvider( {...props} model={model} eventHandler={eventHandler} + usedEditor={props.usedEditor ?? "old"} /> )} diff --git a/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx b/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx index 35ab2acfd73..bd1ba71a511 100644 --- a/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx +++ b/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx @@ -20,6 +20,8 @@ import type { PropertyCategory } from "../../PropertyDataProvider.js"; import { FlatNonPrimitivePropertyRenderer } from "./FlatNonPrimitivePropertyRenderer.js"; import { CustomizablePropertyRenderer } from "../../../properties/renderers/CustomizablePropertyRenderer.js"; import { Orientation } from "../../../common/Orientation.js"; +import { EditorInterop } from "../../../newEditors/EditorInterop.js"; +import { CommittingEditor } from "../../../newEditors/CommittingEditor.js"; /** Properties of [[FlatPropertyRenderer]] React component * @internal @@ -41,6 +43,7 @@ export interface FlatPropertyRendererProps extends SharedRendererProps { ) => void; /** Called when property edit is cancelled. */ onEditCancel?: () => void; + usedEditor?: "old" | "new"; /** Whether property value is displayed in expanded state. */ isExpanded: boolean; /** Called when toggling between expanded and collapsed property value display state. */ @@ -65,7 +68,9 @@ export const FlatPropertyRenderer: React.FC = ( const { propertyValueRendererManager, highlight, ...passthroughProps } = props; - const valueElementRenderer = () => ; + const valueElementRenderer = () => ( + + ); const primitiveRendererProps: PrimitiveRendererProps = { ...passthroughProps, @@ -140,6 +145,7 @@ interface DisplayValueProps { onClick?: (property: PropertyRecord, key?: string) => void; uniqueKey?: string; category?: PropertyCategory; + usedEditor: "old" | "new"; onEditCancel?: () => void; onEditCommit?: ( args: PropertyUpdatedArgs, @@ -171,6 +177,26 @@ const DisplayValue: React.FC = (props) => { if (props.category) props.onEditCommit?.(args, props.category); }; + const { metadata, value } = EditorInterop.getMetadataAndValue( + props.propertyRecord + ); + if (props.usedEditor === "new" && metadata && value) { + return ( + + _onEditCommit({ + propertyRecord: props.propertyRecord, + newValue: EditorInterop.convertToPrimitiveValue(newValue), + }) + } + onCancel={props.onEditCancel} + size="small" + /> + ); + } + return ( Date: Mon, 28 Oct 2024 16:45:50 +0200 Subject: [PATCH 03/12] Use new editors in tools --- .../uiprovider/ComponentGenerator.tsx | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx b/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx index 3a8d52dc5a1..4931f8dd82e 100644 --- a/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx +++ b/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx @@ -15,13 +15,18 @@ import type { DialogItemValue, DialogPropertySyncItem, DialogRow, + PropertyRecord, } from "@itwin/appui-abstract"; import { PropertyValueFormat, UiLayoutDataProvider, } from "@itwin/appui-abstract"; import type { PropertyUpdatedArgs } from "@itwin/components-react"; -import { EditorContainer } from "@itwin/components-react"; +import { + CommittingEditor, + EditorContainer, + EditorInterop, +} from "@itwin/components-react"; import type { ToolSettingsEntry } from "../widget-panels/ToolSettings.js"; import { assert, Logger } from "@itwin/core-bentley"; import { Label } from "@itwin/itwinui-react"; @@ -189,7 +194,7 @@ function PropertyEditor({ return (
- void; + onCancel: () => void; +}) { + const { metadata, value } = EditorInterop.getMetadataAndValue(propertyRecord); + if (metadata && value) { + return ( + { + onCommit({ + propertyRecord, + newValue: EditorInterop.convertToPrimitiveValue(newValue), + }); + }} + onCancel={onCancel} + size="small" + /> + ); + } + + return ( + + ); +} + /** Utility methods to generate react ui from DialogRow specs * @public */ From 9aa93d4f0fffade36c32e1cdda4dfbd5d530bea2 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:46:26 +0200 Subject: [PATCH 04/12] Use new editors in imodel-components-react --- .../src/imodel-components-react.ts | 6 ++ .../editors/NewColorEditor.tsx | 97 +++++++++++++++++ .../editors/NewWeightEditor.tsx | 52 +++++++++ .../inputs/QuantityEditor.tsx | 43 ++++++++ .../inputs/UseQuantityInfo.ts | 101 ++++++++++++++++++ 5 files changed, 299 insertions(+) create mode 100644 ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx create mode 100644 ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx create mode 100644 ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx create mode 100644 ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts diff --git a/ui/imodel-components-react/src/imodel-components-react.ts b/ui/imodel-components-react/src/imodel-components-react.ts index 527e16d0c9b..f7f90b70136 100644 --- a/ui/imodel-components-react/src/imodel-components-react.ts +++ b/ui/imodel-components-react/src/imodel-components-react.ts @@ -43,6 +43,8 @@ export { WeightEditor, WeightPropertyEditor, } from "./imodel-components-react/editors/WeightEditor.js"; +export { ColorEditorSpec } from "./imodel-components-react/editors/NewColorEditor.js"; +export { WeightEditorSpec } from "./imodel-components-react/editors/NewWeightEditor.js"; export { QuantityInput, @@ -53,6 +55,10 @@ export { QuantityNumberInputProps, StepFunctionProp, } from "./imodel-components-react/inputs/QuantityNumberInput.js"; +export { + QuantityEditor, + QuantityEditorSpec, +} from "./imodel-components-react/inputs/QuantityEditor.js"; export { LineWeightSwatch, diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx new file mode 100644 index 00000000000..977eec5a44f --- /dev/null +++ b/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx @@ -0,0 +1,97 @@ +import * as React from "react"; +import { + type ColorEditorParams, + StandardEditorNames, +} from "@itwin/appui-abstract"; +import { + ColorPalette, + ColorPicker, + ColorSwatch, + ColorValue, + IconButton, + Popover, +} from "@itwin/itwinui-react"; +import type { + EditorProps, + EditorSpec, + NumericValue, + SpecificEditorProps, + ValueMetadata, +} from "@itwin/components-react"; +import { isNumericValue } from "@itwin/components-react"; + +export const ColorEditorSpec: EditorSpec = { + applies: (metadata) => + metadata.type === "number" && + metadata.preferredEditor === StandardEditorNames.ColorPicker && + (metadata as ColorValueMetadata).params !== undefined, + Editor: NewColorEditor, +}; + +/** + * + */ +export interface ColorValueMetadata extends ValueMetadata { + params: ColorEditorParams[]; +} + +/** + * + */ +export function NewColorEditor(props: EditorProps) { + const { value, onChange, onFinish, colors, size } = + useColorEditorProps(props); + + const colorsList = colors.map((color) => ColorValue.fromTbgr(color)); + const activeColor = + value.rawValue !== undefined + ? ColorValue.fromTbgr(value.rawValue) + : colorsList[0]; + + const onColorChanged = (color: ColorValue) => { + onChange({ rawValue: color.toTbgr(), displayValue: "" }); + }; + + return ( + + + + } + onVisibleChange={(visible) => { + if (!visible) { + onFinish(); + } + }} + > + + + + + ); +} + +function useColorEditorProps({ + metadata, + value, + onChange, + ...props +}: EditorProps): SpecificEditorProps & { colors: number[] } { + const params = (metadata as ColorValueMetadata).params[0]; + const colors = params.colorValues; + + return { + ...props, + metadata, + colors, + value: + value === undefined || !isNumericValue(value) + ? { rawValue: colors[0], displayValue: "" } + : value, + onChange, + }; +} diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx new file mode 100644 index 00000000000..871c51219da --- /dev/null +++ b/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx @@ -0,0 +1,52 @@ +import * as React from "react"; +import { StandardEditorNames } from "@itwin/appui-abstract"; +import { WeightPickerButton } from "../lineweight/WeightPickerButton.js"; +import { + type EditorProps, + type EditorSpec, + isNumericValue, + type NumericValue, + type SpecificEditorProps, +} from "@itwin/components-react"; + +export const WeightEditorSpec: EditorSpec = { + applies: (metadata) => + metadata.type === "number" && + metadata.preferredEditor === StandardEditorNames.WeightPicker, + Editor: NewWeightEditor, +}; + +/** + * + */ +export function NewWeightEditor(props: EditorProps) { + const { value, onChange, onFinish } = useWeightEditorProps(props); + const activeWeight = value.rawValue ?? 0; + + return ( + { + const newValue = { rawValue: newWeight, displayValue: "" }; + onChange(newValue); + onFinish(); + }} + onBlur={() => onFinish()} + /> + ); +} + +function useWeightEditorProps({ + value, + onChange, + ...props +}: EditorProps): SpecificEditorProps { + return { + ...props, + value: + value === undefined || !isNumericValue(value) + ? { rawValue: 0, displayValue: "" } + : value, + onChange, + }; +} diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx new file mode 100644 index 00000000000..16eaacd8980 --- /dev/null +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import { type QuantityTypeArg } from "@itwin/core-frontend"; +import { useQuantityInfo } from "./UseQuantityInfo.js"; +import type { + EditorPropsWithFormatOverrides, + EditorSpec, +} from "@itwin/components-react"; +import { QuantityInput, useNumericEditorProps } from "@itwin/components-react"; + +export const QuantityEditorSpec: EditorSpec = { + applies: (metadata) => + metadata.type === "number" && + "quantityType" in metadata && + metadata.quantityType !== undefined, + Editor: QuantityEditor, +}; + +/** + * + */ +export function QuantityEditor(props: EditorPropsWithFormatOverrides) { + const { onChange, value, onFinish, size } = useNumericEditorProps(props); + const quantityType = + "quantityType" in props.metadata + ? (props.metadata.quantityType as QuantityTypeArg) + : undefined; + + const { formatter, parser } = useQuantityInfo({ + type: quantityType, + formatOverrides: props.formatOverrides, + }); + + return ( + + ); +} diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts b/ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts new file mode 100644 index 00000000000..72700b3ad6c --- /dev/null +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts @@ -0,0 +1,101 @@ +import type { FormatOverrides } from "@itwin/components-react"; +import { getMatchingFormatOverride } from "@itwin/components-react"; +import type { QuantityTypeArg } from "@itwin/core-frontend"; +import { IModelApp } from "@itwin/core-frontend"; +import { Format, FormatterSpec, ParserSpec } from "@itwin/core-quantity"; +import * as React from "react"; + +interface UseQuantityInfoProps { + type: QuantityTypeArg | undefined; + formatOverrides?: FormatOverrides; +} + +/** + * + */ +export function useQuantityInfo({ + type, + formatOverrides, +}: UseQuantityInfoProps) { + const [{ formatter, parser }, setState] = React.useState<{ + formatter: FormatterSpec | undefined; + parser: ParserSpec | undefined; + }>(() => ({ + formatter: undefined, + parser: undefined, + })); + + React.useEffect(() => { + const defaultFormatterSpec = + type !== undefined + ? IModelApp.quantityFormatter.findFormatterSpecByQuantityType(type) + : undefined; + if (defaultFormatterSpec === undefined) { + setState({ + formatter: undefined, + parser: undefined, + }); + return; + } + + const persistenceUnit = defaultFormatterSpec.persistenceUnit; + const defaultFormat = defaultFormatterSpec.format; + + let disposed = false; + const loadFormatterParser = async () => { + const unitsProvider = IModelApp.quantityFormatter.unitsProvider; + const phenomenon = persistenceUnit.phenomenon; + const overrideFormatProps = getMatchingFormatOverride({ + overrides: formatOverrides ?? {}, + phenomenon, + unitSystem: IModelApp.quantityFormatter.activeUnitSystem, + }); + + const format = + overrideFormatProps !== undefined + ? await Format.createFromJSON("", unitsProvider, overrideFormatProps) + : defaultFormat; + + const newFormatter = await FormatterSpec.create( + "", + format, + unitsProvider, + persistenceUnit + ); + const newParser = await ParserSpec.create( + format, + unitsProvider, + persistenceUnit + ); + + if (disposed) { + return; + } + + setState({ formatter: newFormatter, parser: newParser }); + }; + + void loadFormatterParser(); + const removeListeners = [ + IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener( + () => void loadFormatterParser() + ), + IModelApp.quantityFormatter.onQuantityFormatsChanged.addListener( + ({ quantityType }) => { + if (quantityType === type) { + void loadFormatterParser(); + } + } + ), + ]; + + return () => { + disposed = true; + removeListeners.forEach((remove) => { + remove(); + }); + }; + }, [type, formatOverrides]); + + return { formatter, parser }; +} From dfa1a8b80ebc40803fc8094cc5cafa5504da5bb2 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:46:48 +0200 Subject: [PATCH 05/12] Use new editors in test-app --- apps/test-app/package.json | 4 + apps/test-app/src/backend/main.ts | 3 + apps/test-app/src/common/rpcs.ts | 2 + apps/test-app/src/frontend/App.tsx | 82 ++++++++++- apps/test-app/src/frontend/AppInitializer.tsx | 5 + apps/test-app/src/frontend/SchemaContext.ts | 19 +++ .../appui/frontstages/EditorFrontstage.tsx | 22 --- .../appui/providers/PropertyGridProvider.tsx | 130 ++++++++++++++++++ .../src/frontend/registerFrontstages.tsx | 5 + apps/test-app/src/routes/__root.tsx | 2 +- apps/test-app/src/routes/iTwin_.$iTwinId.tsx | 34 ++--- apps/test-app/src/routes/local_.$fileName.tsx | 50 +++---- apps/test-providers/package.json | 4 + apps/test-providers/src/tools/SampleTool.ts | 26 +++- .../test-providers/src/ui/ViewportContent.tsx | 5 +- 15 files changed, 319 insertions(+), 74 deletions(-) create mode 100644 apps/test-app/src/frontend/SchemaContext.ts create mode 100644 apps/test-app/src/frontend/appui/providers/PropertyGridProvider.tsx diff --git a/apps/test-app/package.json b/apps/test-app/package.json index bcb189908e0..0db7154bb58 100644 --- a/apps/test-app/package.json +++ b/apps/test-app/package.json @@ -85,6 +85,7 @@ "@itwin/ecschema-metadata": "^4.0.0", "@itwin/ecschema-rpcinterface-common": "^4.0.0", "@itwin/ecschema-rpcinterface-impl": "^4.0.0", + "@itwin/ecschema-metadata": "^4.0.0", "@itwin/editor-backend": "^4.0.0", "@itwin/editor-common": "^4.0.0", "@itwin/editor-frontend": "^4.0.0", @@ -102,7 +103,10 @@ "@itwin/itwinui-icons-react": "^2.8.0", "@itwin/itwinui-layouts-react": "~0.4.1", "@itwin/itwinui-layouts-css": "~0.4.0", + "@itwin/presentation-backend": "^4.0.0", "@itwin/presentation-common": "^4.0.0", + "@itwin/presentation-components": "^5.0.0", + "@itwin/presentation-frontend": "^4.0.0", "@itwin/reality-data-client": "1.2.2", "@itwin/webgl-compatibility": "^4.0.0", "@tanstack/react-router": "^1.87.6", diff --git a/apps/test-app/src/backend/main.ts b/apps/test-app/src/backend/main.ts index 69d841e3802..06f79b60b58 100644 --- a/apps/test-app/src/backend/main.ts +++ b/apps/test-app/src/backend/main.ts @@ -13,6 +13,7 @@ import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common"; import { ECSchemaRpcImpl } from "@itwin/ecschema-rpcinterface-impl"; import { BackendIModelsAccess } from "@itwin/imodels-access-backend"; import { IModelsClient } from "@itwin/imodels-client-authoring"; +import { Presentation } from "@itwin/presentation-backend"; void (async () => { try { @@ -44,6 +45,8 @@ void (async () => { } else { await initializeWeb(opts); } + + Presentation.initialize(); } catch (error: any) { Logger.logError(loggerCategory, error); process.exitCode = 1; diff --git a/apps/test-app/src/common/rpcs.ts b/apps/test-app/src/common/rpcs.ts index edd3451c167..adb724ffc66 100644 --- a/apps/test-app/src/common/rpcs.ts +++ b/apps/test-app/src/common/rpcs.ts @@ -9,6 +9,7 @@ import { SnapshotIModelRpcInterface, } from "@itwin/core-common"; import { ECSchemaRpcInterface } from "@itwin/ecschema-rpcinterface-common"; +import { PresentationRpcInterface } from "@itwin/presentation-common"; /** * Returns a list of RPCs supported by this application @@ -19,5 +20,6 @@ export function getSupportedRpcs(): RpcInterfaceDefinition[] { IModelTileRpcInterface, SnapshotIModelRpcInterface, ECSchemaRpcInterface, + PresentationRpcInterface, ]; } diff --git a/apps/test-app/src/frontend/App.tsx b/apps/test-app/src/frontend/App.tsx index 8296ca6f4a0..d7a207bb35a 100644 --- a/apps/test-app/src/frontend/App.tsx +++ b/apps/test-app/src/frontend/App.tsx @@ -17,6 +17,23 @@ import { import { ThemeProvider as IUI2_ThemeProvider } from "@itwin/itwinui-react-v2"; import { useEngagementTime } from "./appui/useEngagementTime"; import { AppLocalizationProvider } from "./Localization"; +import { KoqEditorSpec } from "@itwin/presentation-components"; +import { + ColorEditorSpec, + QuantityEditorSpec, + WeightEditorSpec, +} from "@itwin/imodel-components-react"; +import { ButtonGroup, IconButton } from "@itwin/itwinui-react"; +import { SvgPlaceholder } from "@itwin/itwinui-icons-react"; +import { + EditorProps, + EditorSpec, + EditorsRegistryProvider, + FormatOverrides, + NumericEditorSpec, + useEnumEditorProps, + withFormatOverrides, +} from "@itwin/components-react"; interface AppProps { featureOverrides?: React.ComponentProps< @@ -32,10 +49,14 @@ export function App({ featureOverrides }: AppProps) { - } - childWindow={ChildWindow} - /> + + + } + childWindow={ChildWindow} + /> + + @@ -48,3 +69,56 @@ export function App({ featureOverrides }: AppProps) { function ChildWindow(props: React.PropsWithChildren) { return {props.children}; } + +const formatOverrides: FormatOverrides = { + ["Units.LENGTH"]: { + unitSystems: ["metric"], + format: { + type: "decimal", + decimalSeparator: "k", + }, + }, +}; + +const editors: EditorSpec[] = [ + NumericEditorSpec, + WeightEditorSpec, + ColorEditorSpec, +]; + +const rootEditors: EditorSpec[] = [ + { + applies: (metadata) => + metadata.type === "enum" && + metadata.preferredEditor === "enum-buttongroup", + Editor: CustomEnumEditor, + }, + { + ...KoqEditorSpec, + Editor: withFormatOverrides(KoqEditorSpec.Editor, formatOverrides), + }, + QuantityEditorSpec, +]; + +function CustomEnumEditor(props: EditorProps) { + const { value, onChange, choices, size, onFinish } = + useEnumEditorProps(props); + return ( + + {choices.map((c) => ( + { + onChange({ choice: c.value, label: c.label }); + onFinish(); + }} + isActive={value.choice === c.value} + size={size} + label={c.label} + > + + + ))} + + ); +} diff --git a/apps/test-app/src/frontend/AppInitializer.tsx b/apps/test-app/src/frontend/AppInitializer.tsx index 17adf268b14..2091d421a01 100644 --- a/apps/test-app/src/frontend/AppInitializer.tsx +++ b/apps/test-app/src/frontend/AppInitializer.tsx @@ -39,6 +39,7 @@ import { TestAppLocalization } from "./useTranslation"; import { ElectronApp } from "@itwin/core-electron/lib/cjs/ElectronFrontend"; import { FrontendIModelsAccess } from "@itwin/imodels-access-frontend"; import { IModelsClient } from "@itwin/imodels-client-management"; +import { Presentation } from "@itwin/presentation-frontend"; function createInitializer() { let initializing: Promise | undefined; @@ -106,6 +107,8 @@ function createInitializer() { await IModelApp.startup(options); } + await Presentation.initialize(); + ToolAdmin.exceptionHandler = async (err) => Promise.resolve(UnexpectedErrors.handle(err)); await IModelApp.localization.registerNamespace( @@ -165,6 +168,8 @@ function createInitializer() { } }); + await IModelApp.quantityFormatter.setActiveUnitSystem("metric"); + // TODO: should not be required. Start event loop to open key-in palette. IModelApp.startEventLoop(); } diff --git a/apps/test-app/src/frontend/SchemaContext.ts b/apps/test-app/src/frontend/SchemaContext.ts new file mode 100644 index 00000000000..3788946f78b --- /dev/null +++ b/apps/test-app/src/frontend/SchemaContext.ts @@ -0,0 +1,19 @@ +import { IModelConnection } from "@itwin/core-frontend"; +import { SchemaContext } from "@itwin/ecschema-metadata"; +import { ECSchemaRpcLocater } from "@itwin/ecschema-rpcinterface-common"; + +const schemaContextsCache = new Map(); +export function getSchemaContext(imodel: IModelConnection) { + const context = schemaContextsCache.get(imodel.key); + if (context) { + return context; + } + + const newContext = new SchemaContext(); + newContext.addLocater(new ECSchemaRpcLocater(imodel.getRpcProps())); + schemaContextsCache.set(imodel.key, newContext); + + imodel.onClose.addListener(() => schemaContextsCache.delete(imodel.key)); + + return newContext; +} diff --git a/apps/test-app/src/frontend/appui/frontstages/EditorFrontstage.tsx b/apps/test-app/src/frontend/appui/frontstages/EditorFrontstage.tsx index 97738d79c63..8d71b23420a 100644 --- a/apps/test-app/src/frontend/appui/frontstages/EditorFrontstage.tsx +++ b/apps/test-app/src/frontend/appui/frontstages/EditorFrontstage.tsx @@ -12,12 +12,8 @@ import { StagePanelSection, StageUsage, StandardContentLayouts, - ToolbarItemUtilities, - ToolbarOrientation, - ToolbarUsage, UiItemsProvider, } from "@itwin/appui-react"; -import { CreateArcTool, CreateLineStringTool } from "@itwin/editor-frontend"; import { SvgDraw, SvgEdit } from "@itwin/itwinui-icons-react"; import { ViewportContent } from "@itwin/appui-test-providers"; @@ -53,24 +49,6 @@ export function createEditorFrontstageProvider(): UiItemsProvider { icon: , }), ], - getToolbarItems: () => { - const layouts = { - standard: { - orientation: ToolbarOrientation.Horizontal, - usage: ToolbarUsage.ContentManipulation, - }, - }; - return [ - ToolbarItemUtilities.createForTool(CreateLineStringTool, { - itemPriority: 10, - layouts, - }), - ToolbarItemUtilities.createForTool(CreateArcTool, { - itemPriority: 10, - layouts, - }), - ]; - }, getWidgets: () => { const layouts = { standard: { diff --git a/apps/test-app/src/frontend/appui/providers/PropertyGridProvider.tsx b/apps/test-app/src/frontend/appui/providers/PropertyGridProvider.tsx new file mode 100644 index 00000000000..1b4a9624bd7 --- /dev/null +++ b/apps/test-app/src/frontend/appui/providers/PropertyGridProvider.tsx @@ -0,0 +1,130 @@ +import * as React from "react"; +import { + StagePanelLocation, + StagePanelSection, + UiItemsProvider, + useActiveIModelConnection, + WidgetState, +} from "@itwin/appui-react"; +import { VirtualizedPropertyGridWithDataProvider } from "@itwin/components-react"; +import { useResizeObserver } from "@itwin/core-react/internal"; +import { + IPresentationPropertyDataProvider, + PresentationPropertyDataProvider, + SchemaMetadataContextProvider, + usePropertyDataProviderWithUnifiedSelection, +} from "@itwin/presentation-components"; +import { getSchemaContext } from "../../SchemaContext"; +import { + IModelApp, + NotifyMessageDetails, + OutputMessagePriority, + OutputMessageType, +} from "@itwin/core-frontend"; + +export function createPropertyGridProvider() { + return { + id: "appui-test-app:property-grid-provider", + getWidgets: () => { + return [ + { + id: "property-grid-widget-new", + label: "With new editors", + content: , + defaultState: WidgetState.Open, + layouts: { + standard: { + location: StagePanelLocation.Left, + section: StagePanelSection.Start, + }, + }, + }, + { + id: "property-grid-widget-old", + label: "With old editors", + content: , + defaultState: WidgetState.Open, + layouts: { + standard: { + location: StagePanelLocation.Right, + section: StagePanelSection.Start, + }, + }, + }, + ]; + }, + } satisfies UiItemsProvider; +} + +function PropertyGrid({ usedEditor }: { usedEditor: "old" | "new" }) { + const imodel = useActiveIModelConnection(); + const [dataProvider, setDataProvider] = + React.useState(); + React.useEffect(() => { + if (!imodel) { + setDataProvider(undefined); + return; + } + + const provider = new PresentationPropertyDataProvider({ imodel }); + setDataProvider(provider); + return () => { + provider.dispose(); + }; + }, [imodel]); + + if (!dataProvider) { + return null; + } + + return ( + + ); +} + +function PropertyGridInternal({ + dataProvider, + usedEditor, +}: { + dataProvider: IPresentationPropertyDataProvider; + usedEditor: "old" | "new"; +}) { + const [{ width, height }, setSize] = React.useState({ + width: 0, + height: 0, + }); + const onResize = React.useCallback((w: number, h: number) => { + setSize({ width: w, height: h }); + }, []); + const ref = useResizeObserver(onResize); + usePropertyDataProviderWithUnifiedSelection({ dataProvider }); + + return ( + +
+ { + const message = `Property updated: ${usedEditor} - ${JSON.stringify(args.newValue)}`; + IModelApp.notifications.outputMessage( + new NotifyMessageDetails( + OutputMessagePriority.Info, + message, + undefined, + OutputMessageType.Sticky + ) + ); + return true; + }} + usedEditor={usedEditor} + /> +
+
+ ); +} diff --git a/apps/test-app/src/frontend/registerFrontstages.tsx b/apps/test-app/src/frontend/registerFrontstages.tsx index 597e27245a4..7dfcca60432 100644 --- a/apps/test-app/src/frontend/registerFrontstages.tsx +++ b/apps/test-app/src/frontend/registerFrontstages.tsx @@ -51,6 +51,7 @@ import { createITwinUIV2Frontstage, createITwinUIV2FrontstageProvider, } from "./appui/frontstages/iTwinUIV2Frontstage"; +import { createPropertyGridProvider } from "./appui/providers/PropertyGridProvider"; interface RegisterFrontstagesArgs { iModelConnection?: IModelConnection; @@ -169,6 +170,10 @@ export function registerFrontstages({ stageIds: [createITwinUIV2Frontstage.stageId], }); + UiItemsManager.register(createPropertyGridProvider(), { + stageIds: [createMainFrontstage.stageId], + }); + if (ProcessDetector.isElectronAppFrontend) { UiFramework.frontstages.addFrontstage(createEditorFrontstage()); UiItemsManager.register(createEditorFrontstageProvider(), { diff --git a/apps/test-app/src/routes/__root.tsx b/apps/test-app/src/routes/__root.tsx index afe6bcc6283..4abd1ce5438 100644 --- a/apps/test-app/src/routes/__root.tsx +++ b/apps/test-app/src/routes/__root.tsx @@ -71,7 +71,7 @@ function Root() { const search = Route.useSearch(); const menu = search.menu !== 0; return ( - + {menu && ( diff --git a/apps/test-app/src/routes/iTwin_.$iTwinId.tsx b/apps/test-app/src/routes/iTwin_.$iTwinId.tsx index b710e8eb5e8..5e2a1521fc1 100644 --- a/apps/test-app/src/routes/iTwin_.$iTwinId.tsx +++ b/apps/test-app/src/routes/iTwin_.$iTwinId.tsx @@ -2,36 +2,36 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import React from "react"; -import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { IModelGrid } from "@itwin/imodel-browser-react"; -import { config } from "../frontend/config"; -import { useAuth } from "../frontend/Auth"; -import { PageLayout } from "@itwin/itwinui-layouts-react"; -import { SignInPage } from "../frontend/SignInPage"; +import React from 'react' +import { createFileRoute, useNavigate } from '@tanstack/react-router' +import { IModelGrid } from '@itwin/imodel-browser-react' +import { config } from '../frontend/config' +import { useAuth } from '../frontend/Auth' +import { PageLayout } from '@itwin/itwinui-layouts-react' +import { SignInPage } from '../frontend/SignInPage' export const Route = createFileRoute("/iTwin_/$iTwinId")({ component: ITwin, -}); +}) -const { serverEnvironmentPrefix } = config; +const { serverEnvironmentPrefix } = config const apiOverrides = { serverEnvironmentPrefix, -}; +} function ITwin() { - const { accessToken } = useAuth(); - const { iTwinId } = Route.useParams(); - const navigate = useNavigate(); - if (!accessToken) return ; + const { accessToken } = useAuth() + const { iTwinId } = Route.useParams() + const navigate = useNavigate() + if (!accessToken) return return ( { void navigate({ - to: "/iTwin/$iTwinId/iModel/$iModelId", + to: '/iTwin/$iTwinId/iModel/$iModelId', params: { iTwinId, iModelId: iModel.id }, - }); + }) }} iTwinId={iTwinId} accessToken={accessToken} @@ -39,5 +39,5 @@ function ITwin() { searchText="" /> - ); + ) } diff --git a/apps/test-app/src/routes/local_.$fileName.tsx b/apps/test-app/src/routes/local_.$fileName.tsx index 53970022eb9..f5056867317 100644 --- a/apps/test-app/src/routes/local_.$fileName.tsx +++ b/apps/test-app/src/routes/local_.$fileName.tsx @@ -2,55 +2,55 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import React from "react"; -import { createFileRoute } from "@tanstack/react-router"; -import { PageLayout } from "@itwin/itwinui-layouts-react"; -import { config } from "../frontend/config"; -import { SnapshotConnection } from "@itwin/core-frontend"; -import { createViewState } from "../frontend/createViewState"; -import { appInitializer } from "../frontend/AppInitializer"; -import { App } from "../frontend/App"; -import { UiFramework } from "@itwin/appui-react"; +import React from 'react' +import { createFileRoute } from '@tanstack/react-router' +import { PageLayout } from '@itwin/itwinui-layouts-react' +import { config } from '../frontend/config' +import { SnapshotConnection } from '@itwin/core-frontend' +import { createViewState } from '../frontend/createViewState' +import { appInitializer } from '../frontend/AppInitializer' +import { App } from '../frontend/App' +import { UiFramework } from '@itwin/appui-react' import { AppParams, useFeatureOverrideParams, useSyncFrontstageParam, -} from "../frontend/SearchParams"; -import { registerFrontstages } from "../frontend/registerFrontstages"; +} from '../frontend/SearchParams' +import { registerFrontstages } from '../frontend/registerFrontstages' export const Route = createFileRoute("/local_/$fileName")({ component: Local, loader: async (ctx) => { - await appInitializer.initialize(); + await appInitializer.initialize() - const filePath = `${config.bimDir}/${ctx.params.fileName}`; - const iModelConnection = await SnapshotConnection.openFile(filePath); - const viewState = await createViewState(iModelConnection); + const filePath = `${config.bimDir}/${ctx.params.fileName}` + const iModelConnection = await SnapshotConnection.openFile(filePath) + const viewState = await createViewState(iModelConnection) - registerFrontstages({ iModelConnection, viewState }); - UiFramework.setIModelConnection(iModelConnection); - UiFramework.setDefaultViewState(viewState); + registerFrontstages({ iModelConnection, viewState }) + UiFramework.setIModelConnection(iModelConnection) + UiFramework.setDefaultViewState(viewState) return { iModelConnection, viewState, - }; + } }, validateSearch: (search: AppParams) => { - return search; + return search }, shouldReload: (ctx) => { - return ctx.cause === "enter"; + return ctx.cause === 'enter' }, gcTime: 0, -}); +}) function Local() { - useSyncFrontstageParam(); - const featureOverrides = useFeatureOverrideParams(); + useSyncFrontstageParam() + const featureOverrides = useFeatureOverrideParams() return ( - ); + ) } diff --git a/apps/test-providers/package.json b/apps/test-providers/package.json index b8f2b651b10..ecad71165cb 100644 --- a/apps/test-providers/package.json +++ b/apps/test-providers/package.json @@ -44,6 +44,9 @@ "typescript": "~5.6.2", "eslint-config-prettier": "~8.8.0" }, + "peerDependencies": { + "@itwin/presentation-components": "^5.0.0" + }, "dependencies": { "@bentley/icons-generic": "^1.0.34", "@itwin/core-bentley": "^4.0.0", @@ -59,6 +62,7 @@ "@itwin/imodel-components-react": "workspace:*", "@itwin/itwinui-react": "^3.15.0", "@itwin/itwinui-icons-react": "^2.8.0", + "@itwin/presentation-components": "^5.0.0", "@itwin/webgl-compatibility": "^4.0.0", "classnames": "2.5.1", "react": "^18.3.1", diff --git a/apps/test-providers/src/tools/SampleTool.ts b/apps/test-providers/src/tools/SampleTool.ts index d2fa18af698..15d3e1ce38e 100644 --- a/apps/test-providers/src/tools/SampleTool.ts +++ b/apps/test-providers/src/tools/SampleTool.ts @@ -7,11 +7,13 @@ import { AngleDescription, BeButtonEvent, EventHandled, + getQuantityTypeKey, IModelApp, LengthDescription, NotifyMessageDetails, OutputMessagePriority, PrimitiveTool, + QuantityFormatter, QuantityType, SurveyLengthDescription, ToolAssistance, @@ -80,7 +82,7 @@ export class SampleTool extends PrimitiveTool { // Tool Setting Properties // ------------- Enum based picklist --------------- // Example of async method used to populate enum values - private _getChoices = async () => { + private _getChoices = () => { return [ { label: SampleTool.getOptionString("Red"), value: ToolOptions.Red }, { label: SampleTool.getOptionString("White"), value: ToolOptions.White }, @@ -628,20 +630,36 @@ export class SampleTool extends PrimitiveTool { }; toolSettings.push({ value: this._lengthValue, - property: this._lengthDescription, + property: { + ...this._lengthDescription, + quantityType: getQuantityTypeKey( + this._lengthDescription.formatterQuantityType + ), + }, editorPosition: { rowPriority: 20, columnIndex: 2 }, isDisabled: false, lockProperty: lengthLock, }); toolSettings.push({ value: this._surveyLengthValue, - property: this._surveyLengthDescription, + property: { + ...this._surveyLengthDescription, + quantityType: getQuantityTypeKey( + this._surveyLengthDescription.formatterQuantityType + ), + }, editorPosition: { rowPriority: 21, columnIndex: 2 }, isDisabled: readonly, }); + const angleDescription = new AngleDescription(); toolSettings.push({ value: this._angleValue, - property: new AngleDescription(), + property: { + ...angleDescription, + quantityType: getQuantityTypeKey( + angleDescription.formatterQuantityType + ), + }, editorPosition: { rowPriority: 25, columnIndex: 2 }, }); return toolSettings; diff --git a/apps/test-providers/src/ui/ViewportContent.tsx b/apps/test-providers/src/ui/ViewportContent.tsx index 4cfd3878a13..882b0f251df 100644 --- a/apps/test-providers/src/ui/ViewportContent.tsx +++ b/apps/test-providers/src/ui/ViewportContent.tsx @@ -6,9 +6,12 @@ import * as React from "react"; import { DefaultViewOverlay, UiFramework } from "@itwin/appui-react"; import { IModelApp, ScreenViewport, ViewState } from "@itwin/core-frontend"; import { ViewportComponent } from "@itwin/imodel-components-react"; +import { viewWithUnifiedSelection } from "@itwin/presentation-components"; type ViewportComponentProps = React.ComponentProps; +const UnifiedViewport = viewWithUnifiedSelection(ViewportComponent); + interface ViewportContentProps extends Partial { contentId?: string; viewState?: ViewState; @@ -49,7 +52,7 @@ export function ViewportContent({ return ( <> - { From 910c13fa049cefde2f2ab25dd544d62211efe57b Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Mon, 9 Dec 2024 22:00:55 +0200 Subject: [PATCH 06/12] Move formatting stuff to imodel-components-react --- apps/test-app/package.json | 5 +- apps/test-providers/package.json | 5 +- .../rush/browser-approved-packages.json | 12 ++ common/config/rush/pnpm-lock.yaml | 151 ++++++++++++++++++ .../src/components-react/newEditors/index.ts | 6 +- .../inputs}/FormatOverrides.ts | 4 + .../inputs/QuantityEditor.tsx | 13 +- .../inputs/UseQuantityInfo.ts | 8 +- .../inputs}/WithFormatOverrides.tsx | 6 +- 9 files changed, 194 insertions(+), 16 deletions(-) rename ui/{components-react/src/components-react/newEditors => imodel-components-react/src/imodel-components-react/inputs}/FormatOverrides.ts (82%) rename ui/{components-react/src/components-react/newEditors => imodel-components-react/src/imodel-components-react/inputs}/WithFormatOverrides.tsx (57%) diff --git a/apps/test-app/package.json b/apps/test-app/package.json index 0db7154bb58..42941d237e4 100644 --- a/apps/test-app/package.json +++ b/apps/test-app/package.json @@ -85,7 +85,6 @@ "@itwin/ecschema-metadata": "^4.0.0", "@itwin/ecschema-rpcinterface-common": "^4.0.0", "@itwin/ecschema-rpcinterface-impl": "^4.0.0", - "@itwin/ecschema-metadata": "^4.0.0", "@itwin/editor-backend": "^4.0.0", "@itwin/editor-common": "^4.0.0", "@itwin/editor-frontend": "^4.0.0", @@ -103,10 +102,10 @@ "@itwin/itwinui-icons-react": "^2.8.0", "@itwin/itwinui-layouts-react": "~0.4.1", "@itwin/itwinui-layouts-css": "~0.4.0", - "@itwin/presentation-backend": "^4.0.0", + "@itwin/presentation-backend": "4.4.0", "@itwin/presentation-common": "^4.0.0", "@itwin/presentation-components": "^5.0.0", - "@itwin/presentation-frontend": "^4.0.0", + "@itwin/presentation-frontend": "4.4.0", "@itwin/reality-data-client": "1.2.2", "@itwin/webgl-compatibility": "^4.0.0", "@tanstack/react-router": "^1.87.6", diff --git a/apps/test-providers/package.json b/apps/test-providers/package.json index ecad71165cb..dc35d7d5854 100644 --- a/apps/test-providers/package.json +++ b/apps/test-providers/package.json @@ -44,9 +44,6 @@ "typescript": "~5.6.2", "eslint-config-prettier": "~8.8.0" }, - "peerDependencies": { - "@itwin/presentation-components": "^5.0.0" - }, "dependencies": { "@bentley/icons-generic": "^1.0.34", "@itwin/core-bentley": "^4.0.0", @@ -55,6 +52,7 @@ "@itwin/core-frontend": "^4.0.0", "@itwin/core-orbitgt": "^4.0.0", "@itwin/core-quantity": "^4.0.0", + "@itwin/ecschema-metadata": "^4.0.0", "@itwin/appui-abstract": "^4.0.0", "@itwin/components-react": "workspace:*", "@itwin/core-react": "workspace:*", @@ -63,6 +61,7 @@ "@itwin/itwinui-react": "^3.15.0", "@itwin/itwinui-icons-react": "^2.8.0", "@itwin/presentation-components": "^5.0.0", + "@itwin/presentation-frontend": "4.4.0", "@itwin/webgl-compatibility": "^4.0.0", "classnames": "2.5.1", "react": "^18.3.1", diff --git a/common/config/rush/browser-approved-packages.json b/common/config/rush/browser-approved-packages.json index a37bea0b7e5..ca03bb27307 100644 --- a/common/config/rush/browser-approved-packages.json +++ b/common/config/rush/browser-approved-packages.json @@ -178,10 +178,22 @@ "name": "@itwin/itwinui-react", "allowedCategories": [ "frontend", "internal" ] }, + { + "name": "@itwin/presentation-backend", + "allowedCategories": [ "internal" ] + }, { "name": "@itwin/presentation-common", "allowedCategories": [ "internal" ] }, + { + "name": "@itwin/presentation-components", + "allowedCategories": [ "internal" ] + }, + { + "name": "@itwin/presentation-frontend", + "allowedCategories": [ "internal" ] + }, { "name": "@itwin/reality-data-client", "allowedCategories": [ "internal" ] diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 1680b2a13ca..c7cdb6296d7 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -128,9 +128,18 @@ importers: '@itwin/itwinui-react-v2': specifier: npm:@itwin/itwinui-react@^2.12.26 version: '@itwin/itwinui-react@2.12.26(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' + '@itwin/presentation-backend': + specifier: 4.4.0 + version: 4.4.0(@itwin/core-backend@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))(@itwin/presentation-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))) '@itwin/presentation-common': specifier: ^4.0.0 version: 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))) + '@itwin/presentation-components': + specifier: ^5.0.0 + version: 5.7.0(c4uxrixfvmdu2bwfsikho4q2hy) + '@itwin/presentation-frontend': + specifier: 4.4.0 + version: 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-frontend@4.4.0(@itwin/appui-abstract@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0)(@itwin/core-orbitgt@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(inversify@6.0.2)(reflect-metadata@0.1.14))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))(@itwin/presentation-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))) '@itwin/reality-data-client': specifier: 1.2.2 version: 1.2.2(@itwin/core-bentley@4.4.0) @@ -273,6 +282,9 @@ importers: '@itwin/core-react': specifier: workspace:* version: link:../../ui/core-react + '@itwin/ecschema-metadata': + specifier: ^4.0.0 + version: 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)) '@itwin/imodel-components-react': specifier: workspace:* version: link:../../ui/imodel-components-react @@ -282,6 +294,12 @@ importers: '@itwin/itwinui-react': specifier: ^3.15.0 version: 3.15.5(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@itwin/presentation-components': + specifier: ^5.0.0 + version: 5.7.0(c4uxrixfvmdu2bwfsikho4q2hy) + '@itwin/presentation-frontend': + specifier: 4.4.0 + version: 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-frontend@4.4.0(@itwin/appui-abstract@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0)(@itwin/core-orbitgt@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(inversify@6.0.2)(reflect-metadata@0.1.14))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))(@itwin/presentation-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))) '@itwin/webgl-compatibility': specifier: ^4.0.0 version: 4.4.0 @@ -2541,6 +2559,12 @@ packages: react: '>=16.8.6' react-dom: '>=16.8.6' + '@itwin/itwinui-icons-react@2.9.0': + resolution: {integrity: sha512-48oxHUuqEaJOwVRFED0yssfIriX/IQrHd67ffxvEAu7yW1f5a/qFDyImAlwjlzr+4+obBMweshJ8sI+OgziyvA==} + peerDependencies: + react: '>=16.8.6' + react-dom: '>=16.8.6' + '@itwin/itwinui-illustrations-react@2.1.0': resolution: {integrity: sha512-5JR2A3mZy0d0qwwHpveSG3fsXLheJkO6a0GoWb8NQWw5edNZMRynJg0l3hVw3CHMgaaCGbUoKC77MuG0jWDzuA==} peerDependencies: @@ -2602,6 +2626,16 @@ packages: reflect-metadata: optional: true + '@itwin/presentation-backend@4.4.0': + resolution: {integrity: sha512-6iBnoYgkYL7LcFci8sKMJrRFRD8DKHLXh/xFS1I0TG8qRbFmz9w6rQZYVXtUkeJmv5mGCF7wqLCoO/GlgM1oFw==} + peerDependencies: + '@itwin/core-backend': ^4.4.0 + '@itwin/core-bentley': ^4.4.0 + '@itwin/core-common': ^4.4.0 + '@itwin/core-quantity': ^4.4.0 + '@itwin/ecschema-metadata': ^4.4.0 + '@itwin/presentation-common': ^4.4.0 + '@itwin/presentation-common@4.4.0': resolution: {integrity: sha512-k4cZQyMc3uTJ5BvGa2a7qT7tZiS09jKoKGQctzDpDnuMueLcW2+TG7nogKIKl+ded4tKT0zsCBsKXLmAXTQu6Q==} peerDependencies: @@ -2610,6 +2644,34 @@ packages: '@itwin/core-quantity': ^4.4.0 '@itwin/ecschema-metadata': ^4.4.0 + '@itwin/presentation-components@5.7.0': + resolution: {integrity: sha512-s5ifkCS/U9nwnpyYxhdzIHDSUOzWYMov3YpIhbwA2pi6U9HrNu706b1P7pUSWukcDPCkvuVZafzybjVBtyK8ug==} + peerDependencies: + '@itwin/appui-abstract': ^4.4.0 + '@itwin/components-react': ^4.9.0 + '@itwin/core-bentley': ^4.4.0 + '@itwin/core-common': ^4.4.0 + '@itwin/core-frontend': ^4.4.0 + '@itwin/core-quantity': ^4.4.0 + '@itwin/core-react': ^4.9.0 + '@itwin/ecschema-metadata': ^4.4.0 + '@itwin/imodel-components-react': ^4.9.0 + '@itwin/itwinui-react': ^3.0.0 + '@itwin/presentation-common': ^4.4.0 + '@itwin/presentation-frontend': ^4.4.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + + '@itwin/presentation-frontend@4.4.0': + resolution: {integrity: sha512-6lobFOVXgHJC+TOKafpw/tHJwdJ9NtIykliEGpJO9uoLDGDjJxlCgO++TIE+WskWWuPuqYh06v3wWAA9nw+F4g==} + peerDependencies: + '@itwin/core-bentley': ^4.4.0 + '@itwin/core-common': ^4.4.0 + '@itwin/core-frontend': ^4.4.0 + '@itwin/core-quantity': ^4.4.0 + '@itwin/ecschema-metadata': ^4.4.0 + '@itwin/presentation-common': ^4.4.0 + '@itwin/reality-data-client@1.2.2': resolution: {integrity: sha512-Zbm6ooV4auni6yWNaIxOgd+/9H9TIeDH7kIAivepuFFLnJq2atzAj54SvsNfmNxdH6FDftC5U1NFMjnprwo89Q==} peerDependencies: @@ -4910,6 +4972,9 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-sort@3.4.1: + resolution: {integrity: sha512-76uvGPsF6So53sZAqenP9UVT3p5l7cyTHkLWVCMinh41Y8NDrK1IYXJgaBMfc1gk7nJiSRZp676kddFG2Aa5+A==} + fast-xml-parser@4.4.1: resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} hasBin: true @@ -6107,6 +6172,9 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + micro-memoize@4.1.2: + resolution: {integrity: sha512-+HzcV2H+rbSJzApgkj0NdTakkC+bnyeiUxgT6/m7mjcz1CmM22KYFKp+EVj1sWe4UYcnriJr5uqHQD/gMHLD+g==} + micromark-core-commonmark@2.0.1: resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} @@ -6379,6 +6447,10 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@1.3.1: + resolution: {integrity: sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==} + engines: {node: '>= 0.10.0'} + object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} @@ -6804,6 +6876,11 @@ packages: peerDependencies: react: '>=16.13.1' + react-error-boundary@4.1.2: + resolution: {integrity: sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==} + peerDependencies: + react: '>=16.13.1' + react-intersection-observer@8.34.0: resolution: {integrity: sha512-TYKh52Zc0Uptp5/b4N91XydfSGKubEhgZRtcg1rhTKABXijc4Sdr1uTp5lJ8TN27jwUsdXxjHXtHa0kPj704sw==} peerDependencies: @@ -7043,6 +7120,11 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs-for-await@1.0.0: + resolution: {integrity: sha512-MJhvf1vtQaljd5wlzsasvOjcohVogzkHkUI0gFE9nGhZ15/fT2vR1CjkLEh37oRqWwpv11vHo5D+sLM+Aw9Y8g==} + peerDependencies: + rxjs: ^7.0.0 + rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} @@ -9702,6 +9784,11 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + '@itwin/itwinui-icons-react@2.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@itwin/itwinui-illustrations-react@2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: react: 18.3.1 @@ -9787,6 +9874,19 @@ snapshots: transitivePeerDependencies: - debug + '@itwin/presentation-backend@4.4.0(@itwin/core-backend@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))(@itwin/presentation-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))))': + dependencies: + '@itwin/core-backend': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0) + '@itwin/core-bentley': 4.4.0 + '@itwin/core-common': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0) + '@itwin/core-quantity': 4.4.0(@itwin/core-bentley@4.4.0) + '@itwin/ecschema-metadata': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)) + '@itwin/presentation-common': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))) + object-hash: 1.3.1 + rxjs: 7.8.1 + rxjs-for-await: 1.0.0(rxjs@7.8.1) + semver: 7.6.3 + '@itwin/presentation-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))': dependencies: '@itwin/core-bentley': 4.4.0 @@ -9794,6 +9894,42 @@ snapshots: '@itwin/core-quantity': 4.4.0(@itwin/core-bentley@4.4.0) '@itwin/ecschema-metadata': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)) + '@itwin/presentation-components@5.7.0(c4uxrixfvmdu2bwfsikho4q2hy)': + dependencies: + '@itwin/appui-abstract': 4.4.0(@itwin/core-bentley@4.4.0) + '@itwin/components-react': link:../../ui/components-react + '@itwin/core-bentley': 4.4.0 + '@itwin/core-common': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0) + '@itwin/core-frontend': 4.4.0(@itwin/appui-abstract@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0)(@itwin/core-orbitgt@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(inversify@6.0.2)(reflect-metadata@0.1.14) + '@itwin/core-quantity': 4.4.0(@itwin/core-bentley@4.4.0) + '@itwin/core-react': link:../../ui/core-react + '@itwin/ecschema-metadata': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)) + '@itwin/imodel-components-react': link:../../ui/imodel-components-react + '@itwin/itwinui-icons-react': 2.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@itwin/itwinui-illustrations-react': 2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@itwin/itwinui-react': 3.15.5(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@itwin/presentation-common': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))) + '@itwin/presentation-frontend': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-frontend@4.4.0(@itwin/appui-abstract@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0)(@itwin/core-orbitgt@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(inversify@6.0.2)(reflect-metadata@0.1.14))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))(@itwin/presentation-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))) + classnames: 2.5.1 + fast-deep-equal: 3.1.3 + fast-sort: 3.4.1 + micro-memoize: 4.1.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-error-boundary: 4.1.2(react@18.3.1) + rxjs: 7.8.1 + + '@itwin/presentation-frontend@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-frontend@4.4.0(@itwin/appui-abstract@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0)(@itwin/core-orbitgt@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(inversify@6.0.2)(reflect-metadata@0.1.14))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)))(@itwin/presentation-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))))': + dependencies: + '@itwin/core-bentley': 4.4.0 + '@itwin/core-common': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0) + '@itwin/core-frontend': 4.4.0(@itwin/appui-abstract@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-geometry@4.4.0)(@itwin/core-orbitgt@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(inversify@6.0.2)(reflect-metadata@0.1.14) + '@itwin/core-quantity': 4.4.0(@itwin/core-bentley@4.4.0) + '@itwin/ecschema-metadata': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0)) + '@itwin/presentation-common': 4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-common@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-geometry@4.4.0))(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))(@itwin/ecschema-metadata@4.4.0(@itwin/core-bentley@4.4.0)(@itwin/core-quantity@4.4.0(@itwin/core-bentley@4.4.0))) + rxjs: 7.8.1 + rxjs-for-await: 1.0.0(rxjs@7.8.1) + '@itwin/reality-data-client@1.2.2(@itwin/core-bentley@4.4.0)': dependencies: '@itwin/core-bentley': 4.4.0 @@ -12823,6 +12959,8 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-sort@3.4.1: {} + fast-xml-parser@4.4.1: dependencies: strnum: 1.0.5 @@ -14328,6 +14466,8 @@ snapshots: methods@1.1.2: {} + micro-memoize@4.1.2: {} + micromark-core-commonmark@2.0.1: dependencies: decode-named-character-reference: 1.0.2 @@ -14659,6 +14799,8 @@ snapshots: object-assign@4.1.1: {} + object-hash@1.3.1: {} + object-inspect@1.13.1: {} object-keys@1.1.1: {} @@ -15077,6 +15219,11 @@ snapshots: '@babel/runtime': 7.23.8 react: 18.3.1 + react-error-boundary@4.1.2(react@18.3.1): + dependencies: + '@babel/runtime': 7.23.8 + react: 18.3.1 + react-intersection-observer@8.34.0(react@18.3.1): dependencies: react: 18.3.1 @@ -15382,6 +15529,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rxjs-for-await@1.0.0(rxjs@7.8.1): + dependencies: + rxjs: 7.8.1 + rxjs@7.8.1: dependencies: tslib: 2.6.2 diff --git a/ui/components-react/src/components-react/newEditors/index.ts b/ui/components-react/src/components-react/newEditors/index.ts index 361ec71f247..d78def59e02 100644 --- a/ui/components-react/src/components-react/newEditors/index.ts +++ b/ui/components-react/src/components-react/newEditors/index.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ export * from "./Editor.js"; export * from "./CommittingEditor.js"; export * from "./Types.js"; @@ -15,5 +19,3 @@ export * from "./editors/text-editor/UseTextEditorProps.js"; export * from "./editors/enum-editor/EnumEditor.js"; export * from "./editors/enum-editor/UseEnumEditorProps.js"; export * from "./editorsRegistry/EditorsRegistryProvider.js"; -export * from "./FormatOverrides.js"; -export * from "./WithFormatOverrides.js"; diff --git a/ui/components-react/src/components-react/newEditors/FormatOverrides.ts b/ui/imodel-components-react/src/imodel-components-react/inputs/FormatOverrides.ts similarity index 82% rename from ui/components-react/src/components-react/newEditors/FormatOverrides.ts rename to ui/imodel-components-react/src/imodel-components-react/inputs/FormatOverrides.ts index fe07265a332..7e43046a245 100644 --- a/ui/components-react/src/components-react/newEditors/FormatOverrides.ts +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/FormatOverrides.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { FormatProps, UnitSystemKey } from "@itwin/core-quantity"; /** diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx index 16eaacd8980..1374a910271 100644 --- a/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx @@ -1,11 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { type QuantityTypeArg } from "@itwin/core-frontend"; import { useQuantityInfo } from "./UseQuantityInfo.js"; -import type { - EditorPropsWithFormatOverrides, - EditorSpec, -} from "@itwin/components-react"; -import { QuantityInput, useNumericEditorProps } from "@itwin/components-react"; +import type { EditorSpec } from "@itwin/components-react"; +import { useNumericEditorProps } from "@itwin/components-react"; +import type { EditorPropsWithFormatOverrides } from "./WithFormatOverrides.js"; +import { QuantityInput } from "./QuantityInput.js"; export const QuantityEditorSpec: EditorSpec = { applies: (metadata) => diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts b/ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts index 72700b3ad6c..3ff9b4f4c58 100644 --- a/ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/UseQuantityInfo.ts @@ -1,9 +1,13 @@ -import type { FormatOverrides } from "@itwin/components-react"; -import { getMatchingFormatOverride } from "@itwin/components-react"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { QuantityTypeArg } from "@itwin/core-frontend"; import { IModelApp } from "@itwin/core-frontend"; import { Format, FormatterSpec, ParserSpec } from "@itwin/core-quantity"; import * as React from "react"; +import type { FormatOverrides } from "./FormatOverrides.js"; +import { getMatchingFormatOverride } from "./FormatOverrides.js"; interface UseQuantityInfoProps { type: QuantityTypeArg | undefined; diff --git a/ui/components-react/src/components-react/newEditors/WithFormatOverrides.tsx b/ui/imodel-components-react/src/imodel-components-react/inputs/WithFormatOverrides.tsx similarity index 57% rename from ui/components-react/src/components-react/newEditors/WithFormatOverrides.tsx rename to ui/imodel-components-react/src/imodel-components-react/inputs/WithFormatOverrides.tsx index 90d7ac94825..d9cd87ca1de 100644 --- a/ui/components-react/src/components-react/newEditors/WithFormatOverrides.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/WithFormatOverrides.tsx @@ -1,7 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import type { ComponentType } from "react"; import type { FormatOverrides } from "./FormatOverrides.js"; -import type { EditorProps } from "./Types.js"; +import type { EditorProps } from "@itwin/components-react"; /** * From 7201c83575da89d2ce68084c6396d71be1215f10 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:09:46 +0200 Subject: [PATCH 07/12] Cleanup --- apps/test-app/src/frontend/App.tsx | 17 -- ui/components-react/src/components-react.ts | 2 +- .../newEditors/CommittingEditor.tsx | 16 +- .../components-react/newEditors/Editor.tsx | 34 +--- .../src/components-react/newEditors/Types.ts | 146 +----------------- .../BooleanEditor.tsx | 4 + .../UseBooleanEditorProps.ts | 4 + .../{date-editor => date}/DateTimeEditor.tsx | 4 + .../UseDateTimeEditorProps.ts | 4 + .../{enum-editor => enum}/EnumEditor.tsx | 4 + .../UseEnumEditorProps.ts | 4 + .../NumericEditor.tsx | 4 + .../ParsedNumericInput.tsx | 4 + .../QuantityInput.tsx | 0 .../UseNumericEditorProps.ts | 4 + .../{text-editor => text}/TextEditor.tsx | 4 + .../UseTextEditorProps.ts | 4 + .../{ => editorsRegistry}/DefaultEditors.tsx | 16 +- .../EditorsRegistryProvider.tsx | 6 +- .../newEditors/editorsRegistry/UseEditor.ts | 35 +++++ .../src/components-react/newEditors/index.ts | 25 ++- .../newEditors/{ => interop}/EditorInterop.ts | 11 +- .../newEditors/values/Metadata.ts | 26 ++++ .../newEditors/values/Values.ts | 122 +++++++++++++++ .../flat-properties/FlatPropertyRenderer.tsx | 2 +- .../inputs/FormatOverrides.ts | 65 -------- .../inputs/QuantityEditor.tsx | 10 +- .../inputs/UseQuantityInfo.ts | 59 ++----- .../inputs/WithFormatOverrides.tsx | 27 ---- 29 files changed, 303 insertions(+), 360 deletions(-) rename ui/components-react/src/components-react/newEditors/editors/{boolean-editor => boolean}/BooleanEditor.tsx (65%) rename ui/components-react/src/components-react/newEditors/editors/{boolean-editor => boolean}/UseBooleanEditorProps.ts (59%) rename ui/components-react/src/components-react/newEditors/editors/{date-editor => date}/DateTimeEditor.tsx (70%) rename ui/components-react/src/components-react/newEditors/editors/{date-editor => date}/UseDateTimeEditorProps.ts (59%) rename ui/components-react/src/components-react/newEditors/editors/{enum-editor => enum}/EnumEditor.tsx (68%) rename ui/components-react/src/components-react/newEditors/editors/{enum-editor => enum}/UseEnumEditorProps.ts (71%) rename ui/components-react/src/components-react/newEditors/editors/{numeric-editor => numeric}/NumericEditor.tsx (64%) rename ui/components-react/src/components-react/newEditors/editors/{numeric-editor => numeric}/ParsedNumericInput.tsx (87%) rename ui/components-react/src/components-react/newEditors/editors/{numeric-editor => numeric}/QuantityInput.tsx (100%) rename ui/components-react/src/components-react/newEditors/editors/{numeric-editor => numeric}/UseNumericEditorProps.ts (60%) rename ui/components-react/src/components-react/newEditors/editors/{text-editor => text}/TextEditor.tsx (58%) rename ui/components-react/src/components-react/newEditors/editors/{text-editor => text}/UseTextEditorProps.ts (58%) rename ui/components-react/src/components-react/newEditors/{ => editorsRegistry}/DefaultEditors.tsx (51%) create mode 100644 ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts rename ui/components-react/src/components-react/newEditors/{ => interop}/EditorInterop.ts (89%) create mode 100644 ui/components-react/src/components-react/newEditors/values/Metadata.ts create mode 100644 ui/components-react/src/components-react/newEditors/values/Values.ts delete mode 100644 ui/imodel-components-react/src/imodel-components-react/inputs/FormatOverrides.ts delete mode 100644 ui/imodel-components-react/src/imodel-components-react/inputs/WithFormatOverrides.tsx diff --git a/apps/test-app/src/frontend/App.tsx b/apps/test-app/src/frontend/App.tsx index d7a207bb35a..1eb8dac9b3a 100644 --- a/apps/test-app/src/frontend/App.tsx +++ b/apps/test-app/src/frontend/App.tsx @@ -17,7 +17,6 @@ import { import { ThemeProvider as IUI2_ThemeProvider } from "@itwin/itwinui-react-v2"; import { useEngagementTime } from "./appui/useEngagementTime"; import { AppLocalizationProvider } from "./Localization"; -import { KoqEditorSpec } from "@itwin/presentation-components"; import { ColorEditorSpec, QuantityEditorSpec, @@ -29,10 +28,8 @@ import { EditorProps, EditorSpec, EditorsRegistryProvider, - FormatOverrides, NumericEditorSpec, useEnumEditorProps, - withFormatOverrides, } from "@itwin/components-react"; interface AppProps { @@ -70,16 +67,6 @@ function ChildWindow(props: React.PropsWithChildren) { return {props.children}; } -const formatOverrides: FormatOverrides = { - ["Units.LENGTH"]: { - unitSystems: ["metric"], - format: { - type: "decimal", - decimalSeparator: "k", - }, - }, -}; - const editors: EditorSpec[] = [ NumericEditorSpec, WeightEditorSpec, @@ -93,10 +80,6 @@ const rootEditors: EditorSpec[] = [ metadata.preferredEditor === "enum-buttongroup", Editor: CustomEnumEditor, }, - { - ...KoqEditorSpec, - Editor: withFormatOverrides(KoqEditorSpec.Editor, formatOverrides), - }, QuantityEditorSpec, ]; diff --git a/ui/components-react/src/components-react.ts b/ui/components-react/src/components-react.ts index f57578c9177..f40e149f27d 100644 --- a/ui/components-react/src/components-react.ts +++ b/ui/components-react/src/components-react.ts @@ -92,7 +92,7 @@ export { } from "./components-react/datepicker/DatePickerPopupButton.js"; export { IntlFormatter } from "./components-react/datepicker/IntlFormatter.js"; -export { EditorInterop } from "./components-react/newEditors/EditorInterop.js"; +export { EditorInterop } from "./components-react/newEditors/interop/EditorInterop.js"; export * from "./components-react/newEditors/index.js"; export { BooleanEditor, diff --git a/ui/components-react/src/components-react/newEditors/CommittingEditor.tsx b/ui/components-react/src/components-react/newEditors/CommittingEditor.tsx index d5e92ba21eb..c93cf50f1e3 100644 --- a/ui/components-react/src/components-react/newEditors/CommittingEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/CommittingEditor.tsx @@ -1,15 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { useRef, useState } from "react"; -import type { Value, ValueMetadata } from "./Types.js"; import { Editor } from "./Editor.js"; +import type { Value } from "./values/Values.js"; +import type { EditorProps } from "./Types.js"; -interface CommittingEditorProps { - metadata: ValueMetadata; - value?: Value; +type CommittingEditorProps = Omit & { onCommit: (value: Value) => void; onCancel?: () => void; - size?: "small" | "large"; -} +}; /** * @@ -20,6 +22,7 @@ export function CommittingEditor({ onCommit, onCancel, size, + ...restProps }: CommittingEditorProps) { const [currentValue, setCurrentValue] = useState(); const currentValueRef = useRef(currentValue); @@ -52,6 +55,7 @@ export function CommittingEditor({ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
- editor.applies(metadata, value) - )?.Editor; - if (registeredEditor) { - return registeredEditor; - } - - const defaultEditor = defaultEditors.find((editor) => - editor.applies(metadata, value) - )?.Editor; - if (defaultEditor) { - return defaultEditor; - } - - throw new Error(`No editor found for metadata: ${JSON.stringify(metadata)}`); -} +function noopOnFinish() {} diff --git a/ui/components-react/src/components-react/newEditors/Types.ts b/ui/components-react/src/components-react/newEditors/Types.ts index a26e3fdbf7c..568991f321e 100644 --- a/ui/components-react/src/components-react/newEditors/Types.ts +++ b/ui/components-react/src/components-react/newEditors/Types.ts @@ -1,141 +1,9 @@ -/** - * - */ -export interface NumericValue { - rawValue: number | undefined; - displayValue: string; - roundingError?: number; -} - -/** - * - */ -export interface InstanceKeyValue { - key: { id: string; className: string }; - label: string; -} - -/** - * - */ -export interface TextValue { - value: string; -} - -/** - * - */ -export interface BooleanValue { - value: boolean; -} - -/** - * - */ -export interface DateValue { - value: Date; -} - -/** - * - */ -export interface EnumValue { - choice: number | string; - label: string; -} - -/** - * - */ -export type Value = - | NumericValue - | InstanceKeyValue - | TextValue - | BooleanValue - | DateValue - | EnumValue; - -/** - * - */ -export function isTextValue( - value: Value | undefined -): value is TextValue | undefined { - return ( - value === undefined || ("value" in value && typeof value.value === "string") - ); -} - -/** - * - */ -export function isNumericValue( - value: Value | undefined -): value is NumericValue | undefined { - return ( - value === undefined || ("rawValue" in value && "displayValue" in value) - ); -} - -/** - * - */ -export function isBooleanValue( - value: Value | undefined -): value is BooleanValue | undefined { - return ( - value === undefined || - ("value" in value && typeof value.value === "boolean") - ); -} - -/** - * - */ -export function isDateTimeValue( - value: Value | undefined -): value is DateValue | undefined { - return ( - value === undefined || ("value" in value && value.value instanceof Date) - ); -} - -/** - * - */ -export function isEnumValue( - value: Value | undefined -): value is EnumValue | undefined { - return value === undefined || ("choice" in value && "label" in value); -} - -/** - * - */ -export type ValueType = "string" | "number" | "bool" | "date" | "enum"; - -/** - * - */ -export interface ValueMetadata { - type: ValueType; - preferredEditor?: string; -} -/** - * - */ -export interface EnumChoice { - value: number | string; - label: string; -} - -/** - * - */ -export interface EnumValueMetadata extends ValueMetadata { - type: "enum"; - choices: EnumChoice[]; -} +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import type { ValueMetadata } from "./values/Metadata.js"; +import type { Value } from "./values/Values.js"; /** * @@ -163,7 +31,7 @@ export interface EditorProps extends BaseEditorProps { /** * */ -export interface SpecificEditorProps +export interface ConcreteEditorProps extends BaseEditorProps { value: TValue; } diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean-editor/BooleanEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx similarity index 65% rename from ui/components-react/src/components-react/newEditors/editors/boolean-editor/BooleanEditor.tsx rename to ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx index 8b858332b2f..a10a8142284 100644 --- a/ui/components-react/src/components-react/newEditors/editors/boolean-editor/BooleanEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { ToggleSwitch } from "@itwin/itwinui-react"; import type { EditorProps } from "../../Types.js"; diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean-editor/UseBooleanEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts similarity index 59% rename from ui/components-react/src/components-react/newEditors/editors/boolean-editor/UseBooleanEditorProps.ts rename to ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts index c241d807344..d4b67b458ff 100644 --- a/ui/components-react/src/components-react/newEditors/editors/boolean-editor/UseBooleanEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { BooleanValue, EditorProps, diff --git a/ui/components-react/src/components-react/newEditors/editors/date-editor/DateTimeEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx similarity index 70% rename from ui/components-react/src/components-react/newEditors/editors/date-editor/DateTimeEditor.tsx rename to ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx index dca8d87383c..e12600a259a 100644 --- a/ui/components-react/src/components-react/newEditors/editors/date-editor/DateTimeEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { Button, DatePicker, Popover } from "@itwin/itwinui-react"; import type { EditorProps } from "../../Types.js"; diff --git a/ui/components-react/src/components-react/newEditors/editors/date-editor/UseDateTimeEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts similarity index 59% rename from ui/components-react/src/components-react/newEditors/editors/date-editor/UseDateTimeEditorProps.ts rename to ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts index 86abd0f8c56..7777ae408cd 100644 --- a/ui/components-react/src/components-react/newEditors/editors/date-editor/UseDateTimeEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { DateValue, EditorProps, diff --git a/ui/components-react/src/components-react/newEditors/editors/enum-editor/EnumEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx similarity index 68% rename from ui/components-react/src/components-react/newEditors/editors/enum-editor/EnumEditor.tsx rename to ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx index 6ae150506a7..0804b15cad6 100644 --- a/ui/components-react/src/components-react/newEditors/editors/enum-editor/EnumEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { Select } from "@itwin/itwinui-react"; import type { EditorProps } from "../../Types.js"; diff --git a/ui/components-react/src/components-react/newEditors/editors/enum-editor/UseEnumEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts similarity index 71% rename from ui/components-react/src/components-react/newEditors/editors/enum-editor/UseEnumEditorProps.ts rename to ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts index 7da5854ac77..c833090f2ea 100644 --- a/ui/components-react/src/components-react/newEditors/editors/enum-editor/UseEnumEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { EditorProps, EnumChoice, diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/NumericEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/NumericEditor.tsx similarity index 64% rename from ui/components-react/src/components-react/newEditors/editors/numeric-editor/NumericEditor.tsx rename to ui/components-react/src/components-react/newEditors/editors/numeric/NumericEditor.tsx index 1447df11068..d062280e364 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/NumericEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/NumericEditor.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { Input } from "@itwin/itwinui-react"; import type { EditorProps } from "../../Types.js"; diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/ParsedNumericInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/ParsedNumericInput.tsx similarity index 87% rename from ui/components-react/src/components-react/newEditors/editors/numeric-editor/ParsedNumericInput.tsx rename to ui/components-react/src/components-react/newEditors/editors/numeric/ParsedNumericInput.tsx index dcbf1945947..7ec7d5d5805 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/ParsedNumericInput.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/ParsedNumericInput.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { Input } from "@itwin/itwinui-react"; import type { NumericValue } from "../../Types.js"; diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/QuantityInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx similarity index 100% rename from ui/components-react/src/components-react/newEditors/editors/numeric-editor/QuantityInput.tsx rename to ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/UseNumericEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/numeric/UseNumericEditorProps.ts similarity index 60% rename from ui/components-react/src/components-react/newEditors/editors/numeric-editor/UseNumericEditorProps.ts rename to ui/components-react/src/components-react/newEditors/editors/numeric/UseNumericEditorProps.ts index dbf35a5d4fa..5dcdeb98276 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric-editor/UseNumericEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/UseNumericEditorProps.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { EditorProps, NumericValue, diff --git a/ui/components-react/src/components-react/newEditors/editors/text-editor/TextEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx similarity index 58% rename from ui/components-react/src/components-react/newEditors/editors/text-editor/TextEditor.tsx rename to ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx index 7cee0c190c1..1bca375b0eb 100644 --- a/ui/components-react/src/components-react/newEditors/editors/text-editor/TextEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { Input } from "@itwin/itwinui-react"; import type { EditorProps } from "../../Types.js"; diff --git a/ui/components-react/src/components-react/newEditors/editors/text-editor/UseTextEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts similarity index 58% rename from ui/components-react/src/components-react/newEditors/editors/text-editor/UseTextEditorProps.ts rename to ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts index 23ced0cba8f..57aa8eef28e 100644 --- a/ui/components-react/src/components-react/newEditors/editors/text-editor/UseTextEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { EditorProps, SpecificEditorProps, diff --git a/ui/components-react/src/components-react/newEditors/DefaultEditors.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx similarity index 51% rename from ui/components-react/src/components-react/newEditors/DefaultEditors.tsx rename to ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx index 87dfcfa762d..6f56a974831 100644 --- a/ui/components-react/src/components-react/newEditors/DefaultEditors.tsx +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx @@ -1,9 +1,13 @@ -import { BooleanEditor } from "./editors/boolean-editor/BooleanEditor.js"; -import { DateTimeEditor } from "./editors/date-editor/DateTimeEditor.js"; -import { EnumEditor } from "./editors/enum-editor/EnumEditor.js"; -import { NumericEditor } from "./editors/numeric-editor/NumericEditor.js"; -import { TextEditor } from "./editors/text-editor/TextEditor.js"; -import type { EditorSpec } from "./Types.js"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import { BooleanEditor } from "../editors/boolean/BooleanEditor.js"; +import { DateTimeEditor } from "../editors/date/DateTimeEditor.js"; +import { EnumEditor } from "../editors/enum/EnumEditor.js"; +import { NumericEditor } from "../editors/numeric/NumericEditor.js"; +import { TextEditor } from "../editors/text/TextEditor.js"; +import type { EditorSpec } from "../Types.js"; export const TextEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "string", diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx index 57657b89705..6562f16986d 100644 --- a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { useContext, useMemo } from "react"; import type { EditorSpec } from "../Types.js"; @@ -17,7 +21,7 @@ export function EditorsRegistryProvider({ const value = useMemo(() => { return { - editors: [...parentContext.editors, ...editors], + editors: [...editors, ...parentContext.editors], }; }, [parentContext, editors]); diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts b/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts new file mode 100644 index 00000000000..d10e63239ba --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import type { EditorSpec } from "../Types.js"; +import type { ValueMetadata } from "../values/Metadata.js"; +import type { Value } from "../values/Values.js"; +import { defaultEditors } from "./DefaultEditors.js"; +import { useEditorsRegistry } from "./EditorsRegistry.js"; + +/** + * + */ +export function useEditor( + metadata: ValueMetadata, + value: Value | undefined +): EditorSpec["Editor"] | undefined { + const { editors } = useEditorsRegistry(); + + const registeredEditor = editors.find((editor) => + editor.applies(metadata, value) + )?.Editor; + if (registeredEditor) { + return registeredEditor; + } + + const defaultEditor = defaultEditors.find((editor) => + editor.applies(metadata, value) + )?.Editor; + if (defaultEditor) { + return defaultEditor; + } + + throw new Error(`No editor found for metadata: ${JSON.stringify(metadata)}`); +} diff --git a/ui/components-react/src/components-react/newEditors/index.ts b/ui/components-react/src/components-react/newEditors/index.ts index d78def59e02..78b7126556d 100644 --- a/ui/components-react/src/components-react/newEditors/index.ts +++ b/ui/components-react/src/components-react/newEditors/index.ts @@ -5,17 +5,16 @@ export * from "./Editor.js"; export * from "./CommittingEditor.js"; export * from "./Types.js"; -export * from "./DefaultEditors.js"; -export * from "./editors/boolean-editor/BooleanEditor.js"; -export * from "./editors/boolean-editor/UseBooleanEditorProps.js"; -export * from "./editors/date-editor/DateTimeEditor.js"; -export * from "./editors/date-editor/UseDateTimeEditorProps.js"; -export * from "./editors/numeric-editor/NumericEditor.js"; -export * from "./editors/numeric-editor/UseNumericEditorProps.js"; -export * from "./editors/numeric-editor/ParsedNumericInput.js"; -export * from "./editors/numeric-editor/QuantityInput.js"; -export * from "./editors/text-editor/TextEditor.js"; -export * from "./editors/text-editor/UseTextEditorProps.js"; -export * from "./editors/enum-editor/EnumEditor.js"; -export * from "./editors/enum-editor/UseEnumEditorProps.js"; +export * from "./editorsRegistry/DefaultEditors.js"; +export * from "./editors/boolean/BooleanEditor.js"; +export * from "./editors/boolean/UseBooleanEditorProps.js"; +export * from "./editors/date/DateTimeEditor.js"; +export * from "./editors/date/UseDateTimeEditorProps.js"; +export * from "./editors/numeric/NumericEditor.js"; +export * from "./editors/numeric/UseNumericEditorProps.js"; +export * from "./editors/numeric/ParsedNumericInput.js"; +export * from "./editors/text/TextEditor.js"; +export * from "./editors/text/UseTextEditorProps.js"; +export * from "./editors/enum/EnumEditor.js"; +export * from "./editors/enum/UseEnumEditorProps.js"; export * from "./editorsRegistry/EditorsRegistryProvider.js"; diff --git a/ui/components-react/src/components-react/newEditors/EditorInterop.ts b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts similarity index 89% rename from ui/components-react/src/components-react/newEditors/EditorInterop.ts rename to ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts index ab50d4418b5..7687006a111 100644 --- a/ui/components-react/src/components-react/newEditors/EditorInterop.ts +++ b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import type { PrimitiveValue, PropertyEditorParams, @@ -8,18 +12,17 @@ import type { BooleanValue, DateValue, Value as EditorValue, - EnumValueMetadata, NumericValue, TextValue, - ValueMetadata, -} from "./Types.js"; +} from "../values/Values.js"; import { isBooleanValue, isDateTimeValue, isEnumValue, isNumericValue, isTextValue, -} from "./Types.js"; +} from "../values/Values.js"; +import type { EnumValueMetadata, ValueMetadata } from "../values/Metadata.js"; export namespace EditorInterop { /** diff --git a/ui/components-react/src/components-react/newEditors/values/Metadata.ts b/ui/components-react/src/components-react/newEditors/values/Metadata.ts new file mode 100644 index 00000000000..ccc9767f838 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/values/Metadata.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import type { EnumChoice } from "./Values.js"; + +/** + * + */ +export type ValueType = "string" | "number" | "bool" | "date" | "enum"; + +/** + * + */ +export interface ValueMetadata { + type: ValueType; + preferredEditor?: string; +} + +/** + * + */ +export interface EnumValueMetadata extends ValueMetadata { + type: "enum"; + choices: EnumChoice[]; +} diff --git a/ui/components-react/src/components-react/newEditors/values/Values.ts b/ui/components-react/src/components-react/newEditors/values/Values.ts new file mode 100644 index 00000000000..52769d0e13f --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/values/Values.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +/** + * + */ +export interface NumericValue { + rawValue: number | undefined; + displayValue: string; + roundingError?: number; +} + +/** + * + */ +export interface InstanceKeyValue { + key: { id: string; className: string }; + label: string; +} + +/** + * + */ +export interface TextValue { + value: string; +} + +/** + * + */ +export interface BooleanValue { + value: boolean; +} + +/** + * + */ +export interface DateValue { + value: Date; +} + +/** + * + */ +export interface EnumValue { + choice: number | string; + label: string; +} + +/** + * + */ +export type Value = + | NumericValue + | InstanceKeyValue + | TextValue + | BooleanValue + | DateValue + | EnumValue; + +/** + * + */ +export function isTextValue( + value: Value | undefined +): value is TextValue | undefined { + return ( + value === undefined || ("value" in value && typeof value.value === "string") + ); +} + +/** + * + */ +export function isNumericValue( + value: Value | undefined +): value is NumericValue | undefined { + return ( + value === undefined || ("rawValue" in value && "displayValue" in value) + ); +} + +/** + * + */ +export function isBooleanValue( + value: Value | undefined +): value is BooleanValue | undefined { + return ( + value === undefined || + ("value" in value && typeof value.value === "boolean") + ); +} + +/** + * + */ +export function isDateTimeValue( + value: Value | undefined +): value is DateValue | undefined { + return ( + value === undefined || ("value" in value && value.value instanceof Date) + ); +} + +/** + * + */ +export function isEnumValue( + value: Value | undefined +): value is EnumValue | undefined { + return value === undefined || ("choice" in value && "label" in value); +} + +/** + * + */ +export interface EnumChoice { + value: number | string; + label: string; +} diff --git a/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx b/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx index bd1ba71a511..8e5da94a673 100644 --- a/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx +++ b/ui/components-react/src/components-react/propertygrid/internal/flat-properties/FlatPropertyRenderer.tsx @@ -20,7 +20,7 @@ import type { PropertyCategory } from "../../PropertyDataProvider.js"; import { FlatNonPrimitivePropertyRenderer } from "./FlatNonPrimitivePropertyRenderer.js"; import { CustomizablePropertyRenderer } from "../../../properties/renderers/CustomizablePropertyRenderer.js"; import { Orientation } from "../../../common/Orientation.js"; -import { EditorInterop } from "../../../newEditors/EditorInterop.js"; +import { EditorInterop } from "../../../newEditors/interop/EditorInterop.js"; import { CommittingEditor } from "../../../newEditors/CommittingEditor.js"; /** Properties of [[FlatPropertyRenderer]] React component diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/FormatOverrides.ts b/ui/imodel-components-react/src/imodel-components-react/inputs/FormatOverrides.ts deleted file mode 100644 index 7e43046a245..00000000000 --- a/ui/imodel-components-react/src/imodel-components-react/inputs/FormatOverrides.ts +++ /dev/null @@ -1,65 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Bentley Systems, Incorporated. All rights reserved. - * See LICENSE.md in the project root for license terms and full copyright notice. - *--------------------------------------------------------------------------------------------*/ -import type { FormatProps, UnitSystemKey } from "@itwin/core-quantity"; - -/** - * A data structure that associates unit systems with property value formatting props. The associations are used for - * assigning formatting props for specific phenomenon and unit system combinations (see [[FormatsMap]]). - * - * @public - */ -export interface UnitSystemFormat { - unitSystems: UnitSystemKey[]; - format: FormatProps; -} -/** - * A data structure that associates specific phenomenon with one or more formatting props for specific unit system. - * - * Example: - * ```json - * { - * length: [{ - * unitSystems: ["metric"], - * format: formatForCentimeters, - * }, { - * unitSystems: ["imperial", "usCustomary"], - * format: formatForInches, - * }, { - * unitSystems: ["usSurvey"], - * format: formatForUsSurveyInches, - * }] - * } - * ``` - * - * @public - */ -export interface FormatOverrides { - [phenomenon: string]: UnitSystemFormat | UnitSystemFormat[]; -} - -interface MatchingFormatOverrideProps { - overrides: FormatOverrides; - phenomenon: string; - unitSystem: UnitSystemKey; -} - -/** @public */ -export function getMatchingFormatOverride({ - overrides, - phenomenon, - unitSystem, -}: MatchingFormatOverrideProps): FormatProps | undefined { - const overridesForPhenomenon = overrides[phenomenon]; - if (!overridesForPhenomenon) { - return undefined; - } - - const overridesArray = Array.isArray(overridesForPhenomenon) - ? overridesForPhenomenon - : [overridesForPhenomenon]; - return overridesArray.find((override) => - override.unitSystems.includes(unitSystem) - )?.format; -} diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx index 1374a910271..635912c13f3 100644 --- a/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx @@ -5,10 +5,9 @@ import * as React from "react"; import { type QuantityTypeArg } from "@itwin/core-frontend"; import { useQuantityInfo } from "./UseQuantityInfo.js"; -import type { EditorSpec } from "@itwin/components-react"; +import type { EditorProps, EditorSpec } from "@itwin/components-react"; import { useNumericEditorProps } from "@itwin/components-react"; -import type { EditorPropsWithFormatOverrides } from "./WithFormatOverrides.js"; -import { QuantityInput } from "./QuantityInput.js"; +import { QuantityFormattedInput } from "./QuantityFormattedInput.js"; export const QuantityEditorSpec: EditorSpec = { applies: (metadata) => @@ -21,7 +20,7 @@ export const QuantityEditorSpec: EditorSpec = { /** * */ -export function QuantityEditor(props: EditorPropsWithFormatOverrides) { +export function QuantityEditor(props: EditorProps) { const { onChange, value, onFinish, size } = useNumericEditorProps(props); const quantityType = "quantityType" in props.metadata @@ -30,11 +29,10 @@ export function QuantityEditor(props: EditorPropsWithFormatOverrides) { const { formatter, parser } = useQuantityInfo({ type: quantityType, - formatOverrides: props.formatOverrides, }); return ( - { + const formatterSpec = + IModelApp.quantityFormatter.findFormatterSpecByQuantityType(type); + const parserSpec = + IModelApp.quantityFormatter.findParserSpecByQuantityType(type); - let disposed = false; - const loadFormatterParser = async () => { - const unitsProvider = IModelApp.quantityFormatter.unitsProvider; - const phenomenon = persistenceUnit.phenomenon; - const overrideFormatProps = getMatchingFormatOverride({ - overrides: formatOverrides ?? {}, - phenomenon, - unitSystem: IModelApp.quantityFormatter.activeUnitSystem, - }); - - const format = - overrideFormatProps !== undefined - ? await Format.createFromJSON("", unitsProvider, overrideFormatProps) - : defaultFormat; - - const newFormatter = await FormatterSpec.create( - "", - format, - unitsProvider, - persistenceUnit - ); - const newParser = await ParserSpec.create( - format, - unitsProvider, - persistenceUnit - ); - - if (disposed) { - return; - } - - setState({ formatter: newFormatter, parser: newParser }); + setState({ formatter: formatterSpec, parser: parserSpec }); }; - void loadFormatterParser(); + loadFormatterParser(); const removeListeners = [ IModelApp.quantityFormatter.onActiveFormattingUnitSystemChanged.addListener( - () => void loadFormatterParser() + loadFormatterParser ), IModelApp.quantityFormatter.onQuantityFormatsChanged.addListener( ({ quantityType }) => { if (quantityType === type) { - void loadFormatterParser(); + loadFormatterParser(); } } ), ]; return () => { - disposed = true; removeListeners.forEach((remove) => { remove(); }); }; - }, [type, formatOverrides]); + }, [type]); return { formatter, parser }; } diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/WithFormatOverrides.tsx b/ui/imodel-components-react/src/imodel-components-react/inputs/WithFormatOverrides.tsx deleted file mode 100644 index d9cd87ca1de..00000000000 --- a/ui/imodel-components-react/src/imodel-components-react/inputs/WithFormatOverrides.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Bentley Systems, Incorporated. All rights reserved. - * See LICENSE.md in the project root for license terms and full copyright notice. - *--------------------------------------------------------------------------------------------*/ -import * as React from "react"; -import type { ComponentType } from "react"; -import type { FormatOverrides } from "./FormatOverrides.js"; -import type { EditorProps } from "@itwin/components-react"; - -/** - * - */ -export interface EditorPropsWithFormatOverrides extends EditorProps { - formatOverrides?: FormatOverrides; -} - -/** - * - */ -export function withFormatOverrides( - BaseEditor: ComponentType, - formatOverrides: FormatOverrides -) { - return function EditorWithFormatOverrides(props: EditorProps) { - return ; - }; -} From 22c8d3b82d8a487a73569b9403f813aa443e5f17 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:02:33 +0200 Subject: [PATCH 08/12] Fix types --- .../editors/boolean/UseBooleanEditorProps.ts | 13 ++--- .../editors/date/UseDateTimeEditorProps.ts | 13 ++--- .../editors/enum/UseEnumEditorProps.ts | 16 ++---- ...ricInput.tsx => FormattedNumericInput.tsx} | 20 +++---- .../editors/numeric/QuantityInput.tsx | 10 ++-- .../editors/numeric/UseNumericEditorProps.ts | 13 ++--- .../editors/text/UseTextEditorProps.ts | 13 ++--- .../src/components-react/newEditors/index.ts | 4 +- .../newEditors/interop/EditorInterop.ts | 56 +++++-------------- .../newEditors/interop/Metadata.ts | 31 ++++++++++ .../editors/NewColorEditor.tsx | 8 ++- .../editors/NewWeightEditor.tsx | 8 ++- 12 files changed, 101 insertions(+), 104 deletions(-) rename ui/components-react/src/components-react/newEditors/editors/numeric/{ParsedNumericInput.tsx => FormattedNumericInput.tsx} (93%) create mode 100644 ui/components-react/src/components-react/newEditors/interop/Metadata.ts diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts index d4b67b458ff..f3d5d41de92 100644 --- a/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts @@ -2,13 +2,10 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { - BooleanValue, - EditorProps, - SpecificEditorProps, - Value, -} from "../../Types.js"; -import { isBooleanValue } from "../../Types.js"; + +import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; +import type { BooleanValue, Value } from "../../values/Values.js"; +import { isBooleanValue } from "../../values/Values.js"; /** * @@ -17,7 +14,7 @@ export function useBooleanEditorProps({ value, onChange, ...rest -}: EditorProps): SpecificEditorProps { +}: EditorProps): ConcreteEditorProps { return { ...rest, value: getBooleanValue(value), diff --git a/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts index 7777ae408cd..ed592c01f5d 100644 --- a/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts @@ -2,13 +2,10 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { - DateValue, - EditorProps, - SpecificEditorProps, - Value, -} from "../../Types.js"; -import { isDateTimeValue } from "../../Types.js"; + +import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; +import type { DateValue, Value } from "../../values/Values.js"; +import { isDateTimeValue } from "../../values/Values.js"; /** * @@ -17,7 +14,7 @@ export function useDateTimeEditorProps({ value, onChange, ...rest -}: EditorProps): SpecificEditorProps { +}: EditorProps): ConcreteEditorProps { return { ...rest, value: getDateTimeValue(value), diff --git a/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts index c833090f2ea..9ebfdd89f90 100644 --- a/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts @@ -2,15 +2,11 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { - EditorProps, - EnumChoice, - EnumValue, - EnumValueMetadata, - SpecificEditorProps, - Value, -} from "../../Types.js"; -import { isEnumValue } from "../../Types.js"; + +import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; +import type { EnumValueMetadata } from "../../values/Metadata.js"; +import type { EnumChoice, EnumValue, Value } from "../../values/Values.js"; +import { isEnumValue } from "../../values/Values.js"; /** * @@ -20,7 +16,7 @@ export function useEnumEditorProps({ value, onChange, ...rest -}: EditorProps): SpecificEditorProps & { choices: EnumChoice[] } { +}: EditorProps): ConcreteEditorProps & { choices: EnumChoice[] } { const choices = metadata.type === "enum" ? (metadata as EnumValueMetadata).choices : []; diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric/ParsedNumericInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/FormattedNumericInput.tsx similarity index 93% rename from ui/components-react/src/components-react/newEditors/editors/numeric/ParsedNumericInput.tsx rename to ui/components-react/src/components-react/newEditors/editors/numeric/FormattedNumericInput.tsx index 7ec7d5d5805..57d3fcd18eb 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric/ParsedNumericInput.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/FormattedNumericInput.tsx @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { Input } from "@itwin/itwinui-react"; -import type { NumericValue } from "../../Types.js"; +import type { NumericValue } from "../../values/Values.js"; -interface ParsedNumericInputProps { +interface FormattedNumericInputProps { value: NumericValue; onChange: (value: NumericValue) => void; parseValue: (value: string) => number | undefined; @@ -19,7 +19,7 @@ interface ParsedNumericInputProps { /** * */ -export function ParsedNumericInput({ +export function FormattedNumericInput({ onChange, value, parseValue, @@ -27,7 +27,7 @@ export function ParsedNumericInput({ disabled, onBlur, size, -}: ParsedNumericInputProps) { +}: FormattedNumericInputProps) { const { currentValue, inputProps } = useParsedNumberInput({ initialValue: value.rawValue, parseValue, @@ -47,17 +47,15 @@ export function ParsedNumericInput({ ); } -interface HookProps { - initialValue: number | undefined; - parseValue: (value: string) => number | undefined; - formatValue: (num: number) => string; -} - function useParsedNumberInput({ initialValue, formatValue, parseValue, -}: HookProps) { +}: { + initialValue: number | undefined; + parseValue: (value: string) => number | undefined; + formatValue: (num: number) => string; +}) { interface State { value: NumericValue; placeholder: string; diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx index a56b93c39ce..af7de52b46f 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx @@ -1,14 +1,14 @@ import * as React from "react"; -import { ParsedNumericInput } from "./ParsedNumericInput.js"; +import { FormattedNumericInput } from "./FormattedNumericInput.js"; import type { FormatterSpec, ParserSpec } from "@itwin/core-quantity"; -type ParsedNumericInputProps = React.ComponentPropsWithoutRef< - typeof ParsedNumericInput +type FormattedNumericInputProps = React.ComponentPropsWithoutRef< + typeof FormattedNumericInput >; interface QuantityInputProps extends Omit< - ParsedNumericInputProps, + FormattedNumericInputProps, "parseValue" | "formatValue" | "disabled" > { formatter?: FormatterSpec; @@ -46,7 +46,7 @@ export function QuantityInput({ ); return ( - { +}: EditorProps): ConcreteEditorProps { return { ...rest, value: getNumericValue(value), diff --git a/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts index 57aa8eef28e..b3acc93994b 100644 --- a/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts @@ -2,13 +2,10 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { - EditorProps, - SpecificEditorProps, - TextValue, - Value, -} from "../../Types.js"; -import { isTextValue } from "../../Types.js"; + +import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; +import type { TextValue, Value } from "../../values/Values.js"; +import { isTextValue } from "../../values/Values.js"; /** * @@ -17,7 +14,7 @@ export function useTextEditorProps({ value, onChange, ...rest -}: EditorProps): SpecificEditorProps { +}: EditorProps): ConcreteEditorProps { return { ...rest, value: getTextValue(value), diff --git a/ui/components-react/src/components-react/newEditors/index.ts b/ui/components-react/src/components-react/newEditors/index.ts index 78b7126556d..7f2e88a931c 100644 --- a/ui/components-react/src/components-react/newEditors/index.ts +++ b/ui/components-react/src/components-react/newEditors/index.ts @@ -5,6 +5,8 @@ export * from "./Editor.js"; export * from "./CommittingEditor.js"; export * from "./Types.js"; +export * from "./values/Metadata.js"; +export * from "./values/Values.js"; export * from "./editorsRegistry/DefaultEditors.js"; export * from "./editors/boolean/BooleanEditor.js"; export * from "./editors/boolean/UseBooleanEditorProps.js"; @@ -12,7 +14,7 @@ export * from "./editors/date/DateTimeEditor.js"; export * from "./editors/date/UseDateTimeEditorProps.js"; export * from "./editors/numeric/NumericEditor.js"; export * from "./editors/numeric/UseNumericEditorProps.js"; -export * from "./editors/numeric/ParsedNumericInput.js"; +export * from "./editors/numeric/FormattedNumericInput.js"; export * from "./editors/text/TextEditor.js"; export * from "./editors/text/UseTextEditorProps.js"; export * from "./editors/enum/EnumEditor.js"; diff --git a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts index 7687006a111..a9d6c40ba74 100644 --- a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts +++ b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts @@ -2,16 +2,13 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { - PrimitiveValue, - PropertyEditorParams, - PropertyRecord, -} from "@itwin/appui-abstract"; +import type { PrimitiveValue, PropertyRecord } from "@itwin/appui-abstract"; import { PropertyValueFormat } from "@itwin/appui-abstract"; import type { BooleanValue, DateValue, - Value as EditorValue, + EnumValue, + Value as NewEditorValue, NumericValue, TextValue, } from "../values/Values.js"; @@ -22,45 +19,22 @@ import { isNumericValue, isTextValue, } from "../values/Values.js"; -import type { EnumValueMetadata, ValueMetadata } from "../values/Metadata.js"; +import type { OldEditorMetadata } from "./Metadata.js"; export namespace EditorInterop { - /** - * - */ - export interface NumericEditorMetadata extends ValueMetadata { - type: "number"; - params: PropertyEditorParams[]; - } - - /** - * - */ - export function isNumericEditorMetadata( - metadata: ValueMetadata - ): metadata is NumericEditorMetadata { - return metadata.type === "number" && "params" in metadata; - } - /** * */ export function getMetadataAndValue(propertyRecord: PropertyRecord): { - metadata: ValueMetadata | undefined; - value: EditorValue | undefined; + metadata: OldEditorMetadata | undefined; + value: NewEditorValue | undefined; } { - const baseMetadata: Omit = { + const baseMetadata: Omit = { preferredEditor: propertyRecord.property.editor?.name, - ...(propertyRecord.property.editor - ? { params: propertyRecord.property.editor.params } - : {}), - ...(propertyRecord.property.enum - ? { choices: propertyRecord.property.enum.choices } - : {}), - ...(propertyRecord.property.quantityType - ? { quantityType: propertyRecord.property.quantityType } - : {}), - ...propertyRecord.extendedData, + params: propertyRecord.property.editor?.params, + extendedData: propertyRecord.extendedData, + enum: propertyRecord.property.enum, + typename: propertyRecord.property.typename, }; const primitiveValue = propertyRecord.value as PrimitiveValue; @@ -107,7 +81,7 @@ export namespace EditorInterop { metadata: { ...baseMetadata, type: "number", - } as ValueMetadata, + }, value: { rawValue: primitiveValue.value as number, displayValue: primitiveValue.displayValue ?? "", @@ -118,11 +92,11 @@ export namespace EditorInterop { metadata: { ...baseMetadata, type: "enum", - } as EnumValueMetadata, + }, value: { choice: primitiveValue.value as number | string, label: primitiveValue.displayValue as string, - }, + } satisfies EnumValue, }; } @@ -136,7 +110,7 @@ export namespace EditorInterop { * */ export function convertToPrimitiveValue( - newValue: EditorValue + newValue: NewEditorValue ): PrimitiveValue { if (isTextValue(newValue)) { return { diff --git a/ui/components-react/src/components-react/newEditors/interop/Metadata.ts b/ui/components-react/src/components-react/newEditors/interop/Metadata.ts new file mode 100644 index 00000000000..56e55ebb10b --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/interop/Metadata.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ +import type { + EnumerationChoicesInfo, + PropertyEditorParams, +} from "@itwin/appui-abstract"; +import type { ValueMetadata } from "../values/Metadata.js"; + +/** + * Metadata that is created by mapping `PropertyRecord` used to render old editor into the new editor metadata. + * @internal + */ +export interface OldEditorMetadata extends ValueMetadata { + params?: PropertyEditorParams[]; + extendedData?: { [key: string]: unknown }; + enum?: EnumerationChoicesInfo; + quantityType?: string; + typename: string; +} + +/** + * Type guard for `OldEditorMetadata`. + * @internal + */ +export function isOldEditorMetadata( + metadata: ValueMetadata +): metadata is OldEditorMetadata { + return (metadata as OldEditorMetadata).typename !== undefined; +} diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx index 977eec5a44f..63da9f9f44d 100644 --- a/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { type ColorEditorParams, @@ -12,10 +16,10 @@ import { Popover, } from "@itwin/itwinui-react"; import type { + ConcreteEditorProps, EditorProps, EditorSpec, NumericValue, - SpecificEditorProps, ValueMetadata, } from "@itwin/components-react"; import { isNumericValue } from "@itwin/components-react"; @@ -80,7 +84,7 @@ function useColorEditorProps({ value, onChange, ...props -}: EditorProps): SpecificEditorProps & { colors: number[] } { +}: EditorProps): ConcreteEditorProps & { colors: number[] } { const params = (metadata as ColorValueMetadata).params[0]; const colors = params.colorValues; diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx index 871c51219da..dff3776276c 100644 --- a/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx @@ -1,12 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { StandardEditorNames } from "@itwin/appui-abstract"; import { WeightPickerButton } from "../lineweight/WeightPickerButton.js"; +import type { ConcreteEditorProps } from "@itwin/components-react"; import { type EditorProps, type EditorSpec, isNumericValue, type NumericValue, - type SpecificEditorProps, } from "@itwin/components-react"; export const WeightEditorSpec: EditorSpec = { @@ -40,7 +44,7 @@ function useWeightEditorProps({ value, onChange, ...props -}: EditorProps): SpecificEditorProps { +}: EditorProps): ConcreteEditorProps { return { ...props, value: From c52b778036c499400f5adfb66a804c7de709e1f3 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:03:31 +0200 Subject: [PATCH 09/12] Implement EnumGroupEditor that is compatible with current tools --- apps/test-app/src/frontend/App.tsx | 61 +------ .../uiprovider/ComponentGenerator.tsx | 1 + .../components-react/newEditors/Editor.tsx | 2 + .../editors/boolean/BooleanEditor.tsx | 6 +- .../editors/date/DateTimeEditor.tsx | 7 +- .../newEditors/editors/enum/EnumEditor.tsx | 6 +- .../editors/numeric/NumericEditor.tsx | 8 +- .../newEditors/editors/text/TextEditor.tsx | 6 +- .../editorsRegistry/DefaultEditors.tsx | 9 + .../newEditors/editorsRegistry/UseEditor.ts | 9 +- .../newEditors/interop/EditorInterop.ts | 24 ++- .../interop/old-editors/enum/Enum.tsx | 30 ++++ .../old-editors/enum/EnumButtonGroup.tsx | 160 ++++++++++++++++++ .../old-editors/enum/UseEnumChoices.tsx | 44 +++++ .../newEditors/values/Metadata.ts | 8 +- .../lineweight/WeightPickerButton.tsx | 1 + 16 files changed, 313 insertions(+), 69 deletions(-) create mode 100644 ui/components-react/src/components-react/newEditors/interop/old-editors/enum/Enum.tsx create mode 100644 ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx create mode 100644 ui/components-react/src/components-react/newEditors/interop/old-editors/enum/UseEnumChoices.tsx diff --git a/apps/test-app/src/frontend/App.tsx b/apps/test-app/src/frontend/App.tsx index 1eb8dac9b3a..42b512a7729 100644 --- a/apps/test-app/src/frontend/App.tsx +++ b/apps/test-app/src/frontend/App.tsx @@ -17,20 +17,11 @@ import { import { ThemeProvider as IUI2_ThemeProvider } from "@itwin/itwinui-react-v2"; import { useEngagementTime } from "./appui/useEngagementTime"; import { AppLocalizationProvider } from "./Localization"; +import { EditorSpec, EditorsRegistryProvider } from "@itwin/components-react"; import { ColorEditorSpec, - QuantityEditorSpec, WeightEditorSpec, } from "@itwin/imodel-components-react"; -import { ButtonGroup, IconButton } from "@itwin/itwinui-react"; -import { SvgPlaceholder } from "@itwin/itwinui-icons-react"; -import { - EditorProps, - EditorSpec, - EditorsRegistryProvider, - NumericEditorSpec, - useEnumEditorProps, -} from "@itwin/components-react"; interface AppProps { featureOverrides?: React.ComponentProps< @@ -47,12 +38,10 @@ export function App({ featureOverrides }: AppProps) { - - } - childWindow={ChildWindow} - /> - + } + childWindow={ChildWindow} + /> @@ -67,41 +56,5 @@ function ChildWindow(props: React.PropsWithChildren) { return {props.children}; } -const editors: EditorSpec[] = [ - NumericEditorSpec, - WeightEditorSpec, - ColorEditorSpec, -]; - -const rootEditors: EditorSpec[] = [ - { - applies: (metadata) => - metadata.type === "enum" && - metadata.preferredEditor === "enum-buttongroup", - Editor: CustomEnumEditor, - }, - QuantityEditorSpec, -]; - -function CustomEnumEditor(props: EditorProps) { - const { value, onChange, choices, size, onFinish } = - useEnumEditorProps(props); - return ( - - {choices.map((c) => ( - { - onChange({ choice: c.value, label: c.label }); - onFinish(); - }} - isActive={value.choice === c.value} - size={size} - label={c.label} - > - - - ))} - - ); -} +// add custom editors from `@itwin/imodel-components-react` to the registry +const rootEditors: EditorSpec[] = [WeightEditorSpec, ColorEditorSpec]; diff --git a/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx b/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx index 4931f8dd82e..020cfd62f02 100644 --- a/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx +++ b/ui/appui-react/src/appui-react/uiprovider/ComponentGenerator.tsx @@ -229,6 +229,7 @@ function EditorRenderer({ }); }} onCancel={onCancel} + disabled={propertyRecord.isReadonly || propertyRecord.isDisabled} size="small" /> ); diff --git a/ui/components-react/src/components-react/newEditors/Editor.tsx b/ui/components-react/src/components-react/newEditors/Editor.tsx index d71ac0a2ca9..e847104326f 100644 --- a/ui/components-react/src/components-react/newEditors/Editor.tsx +++ b/ui/components-react/src/components-react/newEditors/Editor.tsx @@ -14,6 +14,7 @@ export function Editor({ value, onChange, onFinish, + disabled, size, }: EditorProps) { const TypeEditor = useEditor(metadata, value); @@ -29,6 +30,7 @@ export function Editor({ onChange={onChange} size={size} onFinish={onFinish ?? noopOnFinish} + disabled={disabled} /> ); } diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx index a10a8142284..719a5cbf349 100644 --- a/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx @@ -11,7 +11,8 @@ import { useBooleanEditorProps } from "./UseBooleanEditorProps.js"; * */ export function BooleanEditor(props: EditorProps) { - const { value, onChange, onFinish } = useBooleanEditorProps(props); + const { value, onChange, onFinish, size, disabled } = + useBooleanEditorProps(props); const handleChange = (e: React.ChangeEvent) => { const newValue = { value: e.target.checked }; @@ -23,7 +24,8 @@ export function BooleanEditor(props: EditorProps) { ); } diff --git a/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx index e12600a259a..7fcfc5eda9f 100644 --- a/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx @@ -11,7 +11,8 @@ import { useDateTimeEditorProps } from "./UseDateTimeEditorProps.js"; * */ export function DateTimeEditor(props: EditorProps) { - const { value, onChange, onFinish } = useDateTimeEditorProps(props); + const { value, onChange, onFinish, size, disabled } = + useDateTimeEditorProps(props); const dateStr = value.value.toLocaleDateString(); return ( @@ -31,7 +32,9 @@ export function DateTimeEditor(props: EditorProps) { } }} > - + ); } diff --git a/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx index 0804b15cad6..291db609129 100644 --- a/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx @@ -11,7 +11,8 @@ import { useEnumEditorProps } from "./UseEnumEditorProps.js"; * */ export function EnumEditor(props: EditorProps) { - const { value, onChange, onFinish, choices } = useEnumEditorProps(props); + const { value, onChange, onFinish, choices, disabled, size } = + useEnumEditorProps(props); const handleChange = (newChoice: number | string) => { const choice = choices.find((c) => c.value === newChoice); @@ -22,10 +23,11 @@ export function EnumEditor(props: EditorProps) { return ( onChange({ @@ -23,7 +24,8 @@ export function NumericEditor(props: EditorProps) { }) } onBlur={onFinish} - size={props.size} + size={size} + disabled={disabled} /> ); } diff --git a/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx index 1bca375b0eb..7b72890640b 100644 --- a/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx @@ -11,13 +11,15 @@ import { useTextEditorProps } from "./UseTextEditorProps.js"; * */ export function TextEditor(props: EditorProps) { - const { value, onChange, onFinish } = useTextEditorProps(props); + const { value, onChange, onFinish, size, disabled } = + useTextEditorProps(props); return ( onChange({ value: e.target.value })} - size={props.size} onBlur={onFinish} + size={size} + disabled={disabled} /> ); } diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx index 6f56a974831..a20be7916a6 100644 --- a/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx @@ -7,6 +7,9 @@ import { DateTimeEditor } from "../editors/date/DateTimeEditor.js"; import { EnumEditor } from "../editors/enum/EnumEditor.js"; import { NumericEditor } from "../editors/numeric/NumericEditor.js"; import { TextEditor } from "../editors/text/TextEditor.js"; +import { OldEnumEditorSpec } from "../interop/old-editors/enum/Enum.js"; +import { EnumButtonGroupEditorSpec } from "../interop/old-editors/enum/EnumButtonGroup.js"; + import type { EditorSpec } from "../Types.js"; export const TextEditorSpec: EditorSpec = { @@ -41,3 +44,9 @@ export const defaultEditors: EditorSpec[] = [ DateEditorSpec, EnumEditorSpec, ]; + +// editors that are rewritten based on the old version that accepts editor params from `PropertyRecord` +export const interopEditors: EditorSpec[] = [ + EnumButtonGroupEditorSpec, + OldEnumEditorSpec, +]; diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts b/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts index d10e63239ba..e45450fa983 100644 --- a/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts @@ -5,7 +5,7 @@ import type { EditorSpec } from "../Types.js"; import type { ValueMetadata } from "../values/Metadata.js"; import type { Value } from "../values/Values.js"; -import { defaultEditors } from "./DefaultEditors.js"; +import { defaultEditors, interopEditors } from "./DefaultEditors.js"; import { useEditorsRegistry } from "./EditorsRegistry.js"; /** @@ -24,6 +24,13 @@ export function useEditor( return registeredEditor; } + const oldEditor = interopEditors.find((editor) => + editor.applies(metadata, value) + )?.Editor; + if (oldEditor) { + return oldEditor; + } + const defaultEditor = defaultEditors.find((editor) => editor.applies(metadata, value) )?.Editor; diff --git a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts index a9d6c40ba74..f27cc4cb8ec 100644 --- a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts +++ b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts @@ -2,12 +2,17 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { PrimitiveValue, PropertyRecord } from "@itwin/appui-abstract"; +import type { + Primitives, + PrimitiveValue, + PropertyRecord, +} from "@itwin/appui-abstract"; import { PropertyValueFormat } from "@itwin/appui-abstract"; import type { BooleanValue, DateValue, EnumValue, + InstanceKeyValue, Value as NewEditorValue, NumericValue, TextValue, @@ -84,7 +89,11 @@ export namespace EditorInterop { }, value: { rawValue: primitiveValue.value as number, - displayValue: primitiveValue.displayValue ?? "", + displayValue: + primitiveValue.displayValue !== undefined && + primitiveValue.displayValue !== "" + ? `${parseFloat(primitiveValue.displayValue)}` + : `${(primitiveValue.value as number) ?? ""}`, } satisfies NumericValue, }; case "enum": @@ -98,6 +107,17 @@ export namespace EditorInterop { label: primitiveValue.displayValue as string, } satisfies EnumValue, }; + case "navigation": + return { + metadata: { + ...baseMetadata, + type: "instanceKey", + }, + value: { + key: primitiveValue.value as Primitives.InstanceKey, + label: primitiveValue.displayValue ?? "", + } satisfies InstanceKeyValue, + }; } return { diff --git a/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/Enum.tsx b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/Enum.tsx new file mode 100644 index 00000000000..e5ec0014380 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/Enum.tsx @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import * as React from "react"; +import type { EditorProps, EditorSpec } from "../../../Types.js"; +import { useEnumChoices } from "./UseEnumChoices.js"; +import type { EnumValueMetadata } from "../../../values/Metadata.js"; +import { EnumEditor as NewEnumEditor } from "../../../editors/enum/EnumEditor.js"; +import { isOldEditorMetadata } from "../../Metadata.js"; + +export const EnumEditorSpec: EditorSpec = { + applies: (metadata) => + isOldEditorMetadata(metadata) && metadata.type === "enum", + Editor: EnumEditor, +}; + +function EnumEditor(props: EditorProps) { + const choices = useEnumChoices(props.metadata); + const newMetadata = React.useMemo( + () => ({ + type: "enum" as const, + choices: choices.map(({ value, label }) => ({ value, label })), + }), + [choices] + ); + + return ; +} diff --git a/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx new file mode 100644 index 00000000000..0ac47eab95e --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import * as React from "react"; +import type { + ConcreteEditorProps, + EditorProps, + EditorSpec, +} from "../../../Types.js"; +import { type EnumValue, isEnumValue } from "../../../values/Values.js"; +import type { ButtonGroupEditorParams } from "@itwin/appui-abstract"; +import { + type EnumerationChoice, + type PropertyEditorParams, + PropertyEditorParamTypes, + StandardEditorNames, +} from "@itwin/appui-abstract"; +import type { OldEditorMetadata } from "../../Metadata.js"; +import { isOldEditorMetadata } from "../../Metadata.js"; +import { ButtonGroup, IconButton } from "@itwin/itwinui-react"; +import { SvgPlaceholder } from "@itwin/itwinui-icons-react"; +import { useEnumChoices } from "./UseEnumChoices.js"; + +export const EnumButtonGroupEditorSpec: EditorSpec = { + applies: (metadata) => + isOldEditorMetadata(metadata) && + metadata.type === "enum" && + metadata.preferredEditor === StandardEditorNames.EnumButtonGroup, + Editor: EnumButtonGroupEditor, +}; + +function EnumButtonGroupEditor(props: EditorProps) { + const { value, onChange, choices, enumIcons, onFinish, size, disabled } = + useEnumButtonGroupEditorProps(props); + + return ( + + {choices.map((choice) => { + const icon = findIcon(enumIcons?.get(choice.value)); + return ( + { + onChange({ choice: choice.value, label: choice.label }); + onFinish(); + }} + label={choice.label} + isActive={choice.value === value.choice} + size={size} + disabled={disabled} + > + {icon} + + ); + })} + + ); +} + +function useEnumButtonGroupEditorProps( + props: EditorProps +): ConcreteEditorProps & { + choices: EnumerationChoice[]; + enumIcons?: Map; +} { + const { metadata, value, ...rest } = props; + const choices = useEnumChoices(metadata); + + const firstChoice = choices[0] as EnumerationChoice | undefined; + const currentValue = + value && isEnumValue(value) + ? value + : { choice: firstChoice?.value ?? 0, label: firstChoice?.label ?? "" }; + + return { + ...rest, + metadata, + value: currentValue, + choices, + enumIcons: React.useMemo(() => { + const params = findButtonGroupParams( + (metadata as OldEditorMetadata).params + ); + return params ? createIconsMap(choices, params) : undefined; + }, [metadata, choices]), + }; +} + +function findButtonGroupParams(params?: PropertyEditorParams[]) { + if (!params) { + return undefined; + } + + const matchingParams = params.find( + (param) => param.type === PropertyEditorParamTypes.ButtonGroupData.valueOf() + ); + if (!matchingParams) { + return undefined; + } + return matchingParams as ButtonGroupEditorParams; +} + +function createIconsMap( + choices: EnumerationChoice[], + params: ButtonGroupEditorParams +) { + const icons = new Map(); + for (let i = 0; i < choices.length; i++) { + const iconDef = params.buttons[i]; + icons.set(choices[i].value, iconDef.iconSpec); + } + return icons; +} + +// TODO: Move to the other place that could be shared between components that depend on font icons +function findIcon(iconName?: string) { + if (!iconName) { + return ; + } + + const icon = webfontIconsMap[iconName]; + return icon ? icon : ; +} + +const webfontIconsMap: { + [key: string]: React.JSX.Element; +} = { + "icon-select-single": ( + + + + ), + "icon-select-line": ( + + + + ), + "icon-select-box": ( + + + + ), + "icon-replace": ( + + + + ), + "icon-select-plus": ( + + + + ), + "icon-select-minus": ( + + + + ), +}; diff --git a/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/UseEnumChoices.tsx b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/UseEnumChoices.tsx new file mode 100644 index 00000000000..1c99896a544 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/UseEnumChoices.tsx @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import type { EnumerationChoice } from "@itwin/appui-abstract"; +import type { ValueMetadata } from "../../../values/Metadata.js"; +import * as React from "react"; +import { isOldEditorMetadata } from "../../Metadata.js"; + +/** + * + */ +export function useEnumChoices(metadata: ValueMetadata) { + const [choices, setChoices] = React.useState([]); + + React.useEffect(() => { + if (!isOldEditorMetadata(metadata)) { + throw new Error("EnumButtonGroupEditor missing metadata."); + } + + let disposed = false; + const loadChoices = async () => { + const loadedChoices = + metadata.enum === undefined + ? [] + : metadata.enum.choices instanceof Promise + ? [...(await metadata.enum.choices)] + : [...metadata.enum.choices]; + + if (!disposed) { + setChoices(loadedChoices); + } + }; + + void loadChoices(); + + return () => { + disposed = true; + }; + }, [metadata]); + + return choices; +} diff --git a/ui/components-react/src/components-react/newEditors/values/Metadata.ts b/ui/components-react/src/components-react/newEditors/values/Metadata.ts index ccc9767f838..e635da05b1d 100644 --- a/ui/components-react/src/components-react/newEditors/values/Metadata.ts +++ b/ui/components-react/src/components-react/newEditors/values/Metadata.ts @@ -7,7 +7,13 @@ import type { EnumChoice } from "./Values.js"; /** * */ -export type ValueType = "string" | "number" | "bool" | "date" | "enum"; +export type ValueType = + | "string" + | "number" + | "bool" + | "date" + | "enum" + | "instanceKey"; /** * diff --git a/ui/imodel-components-react/src/imodel-components-react/lineweight/WeightPickerButton.tsx b/ui/imodel-components-react/src/imodel-components-react/lineweight/WeightPickerButton.tsx index 79772ac95c7..80866b8be89 100644 --- a/ui/imodel-components-react/src/imodel-components-react/lineweight/WeightPickerButton.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/lineweight/WeightPickerButton.tsx @@ -237,6 +237,7 @@ export class WeightPickerButton extends React.PureComponent< focusTarget={`#${this.buildIdForWeight(this.props.activeWeight)}`} moveFocus={true} target={this.state.targetElement} + portalTarget={this.state.targetElement ?? undefined} closeOnNestedPopupOutsideClick > {this.renderPopup(this.props.dropDownTitle)} From 5fdebac3227eadd0c9e4cccb6e2f703053d3d5e5 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:53:24 +0200 Subject: [PATCH 10/12] Document core APIs --- .../src/components-react/newEditors/Types.ts | 21 ++++++--- .../editors/boolean/BooleanEditor.tsx | 3 +- .../editors/boolean/UseBooleanEditorProps.ts | 7 +-- .../editors/date/DateTimeEditor.tsx | 9 ++-- .../editors/date/UseDateEditorProps.ts | 28 ++++++++++++ .../editors/date/UseDateTimeEditorProps.ts | 27 ------------ .../newEditors/editors/enum/EnumEditor.tsx | 3 +- .../editors/enum/UseEnumEditorProps.ts | 13 +++--- .../editors/numeric/FormattedNumericInput.tsx | 9 +++- .../editors/numeric/NumericEditor.tsx | 3 +- .../editors/numeric/QuantityInput.tsx | 7 ++- .../editors/numeric/UseNumericEditorProps.ts | 7 +-- .../newEditors/editors/text/TextEditor.tsx | 3 +- .../editors/text/UseTextEditorProps.ts | 7 +-- .../editorsRegistry/DefaultEditors.tsx | 39 +++++++++++++--- .../editorsRegistry/EditorsRegistry.tsx | 17 ------- .../editorsRegistry/EditorsRegistryContext.ts | 19 ++++++++ .../EditorsRegistryProvider.tsx | 7 ++- .../newEditors/editorsRegistry/UseEditor.ts | 8 +++- .../src/components-react/newEditors/index.ts | 2 +- .../newEditors/interop/EditorInterop.ts | 4 +- .../old-editors/enum/EnumButtonGroup.tsx | 13 +++--- .../newEditors/values/Metadata.ts | 17 +++++-- .../newEditors/values/Values.ts | 44 ++++++++++--------- .../editors/NewColorEditor.tsx | 20 +++++---- .../editors/NewWeightEditor.tsx | 15 ++++--- 26 files changed, 217 insertions(+), 135 deletions(-) create mode 100644 ui/components-react/src/components-react/newEditors/editors/date/UseDateEditorProps.ts delete mode 100644 ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts delete mode 100644 ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx create mode 100644 ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryContext.ts diff --git a/ui/components-react/src/components-react/newEditors/Types.ts b/ui/components-react/src/components-react/newEditors/Types.ts index 568991f321e..204ea1a4fd9 100644 --- a/ui/components-react/src/components-react/newEditors/Types.ts +++ b/ui/components-react/src/components-react/newEditors/Types.ts @@ -6,13 +6,17 @@ import type { ValueMetadata } from "./values/Metadata.js"; import type { Value } from "./values/Values.js"; /** - * + * An editor specification defining single editor with a predicate that determines if the editor can be used for a given value. + * @beta */ export interface EditorSpec { applies: (metaData: ValueMetadata, value: Value | undefined) => boolean; Editor: React.ComponentType; } +/** + * Base editor props that are supplied to every editor when rendering it. + */ interface BaseEditorProps { metadata: ValueMetadata; onChange: (value: TValue) => void; @@ -22,16 +26,19 @@ interface BaseEditorProps { } /** - * + * Generic editor props that are supplied to the editor for rendering. + * @beta */ export interface EditorProps extends BaseEditorProps { value?: TValue; } /** - * + * A type that makes a specific properties required in a type. + * @beta */ -export interface ConcreteEditorProps - extends BaseEditorProps { - value: TValue; -} +export type RequiredProps = Omit< + TProps, + TKey +> & + Required>; diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx index 719a5cbf349..997cc61226c 100644 --- a/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/boolean/BooleanEditor.tsx @@ -8,7 +8,8 @@ import type { EditorProps } from "../../Types.js"; import { useBooleanEditorProps } from "./UseBooleanEditorProps.js"; /** - * + * Simple editor for editing boolean values. + * @beta */ export function BooleanEditor(props: EditorProps) { const { value, onChange, onFinish, size, disabled } = diff --git a/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts index f3d5d41de92..f2f05e2ac77 100644 --- a/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/boolean/UseBooleanEditorProps.ts @@ -3,18 +3,19 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; +import type { EditorProps, RequiredProps } from "../../Types.js"; import type { BooleanValue, Value } from "../../values/Values.js"; import { isBooleanValue } from "../../values/Values.js"; /** - * + * Hooks that converts generic `EditorProps` into editor props with boolean value. If value is not boolean, it will be converted into `false`. + * @beta */ export function useBooleanEditorProps({ value, onChange, ...rest -}: EditorProps): ConcreteEditorProps { +}: EditorProps): RequiredProps, "value"> { return { ...rest, value: getBooleanValue(value), diff --git a/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx index 7fcfc5eda9f..e011b1314ee 100644 --- a/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/date/DateTimeEditor.tsx @@ -5,14 +5,15 @@ import * as React from "react"; import { Button, DatePicker, Popover } from "@itwin/itwinui-react"; import type { EditorProps } from "../../Types.js"; -import { useDateTimeEditorProps } from "./UseDateTimeEditorProps.js"; +import { useDateEditorProps } from "./UseDateEditorProps.js"; /** - * + * Simple editor for editing date values. + * @beta */ -export function DateTimeEditor(props: EditorProps) { +export function DateEditor(props: EditorProps) { const { value, onChange, onFinish, size, disabled } = - useDateTimeEditorProps(props); + useDateEditorProps(props); const dateStr = value.value.toLocaleDateString(); return ( diff --git a/ui/components-react/src/components-react/newEditors/editors/date/UseDateEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/date/UseDateEditorProps.ts new file mode 100644 index 00000000000..2f0a638e8d6 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editors/date/UseDateEditorProps.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import type { EditorProps, RequiredProps } from "../../Types.js"; +import type { DateValue, Value } from "../../values/Values.js"; +import { isDateValue } from "../../values/Values.js"; + +/** + * Hooks that converts generic `EditorProps` into editor props with date value. If value is not date time, it will be converted into current date. + * @beta + */ +export function useDateEditorProps({ + value, + onChange, + ...rest +}: EditorProps): RequiredProps, "value"> { + return { + ...rest, + value: getDateValue(value), + onChange, + }; +} + +function getDateValue(value: Value | undefined): DateValue { + return value && isDateValue(value) ? value : { value: new Date() }; +} diff --git a/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts deleted file mode 100644 index ed592c01f5d..00000000000 --- a/ui/components-react/src/components-react/newEditors/editors/date/UseDateTimeEditorProps.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Bentley Systems, Incorporated. All rights reserved. - * See LICENSE.md in the project root for license terms and full copyright notice. - *--------------------------------------------------------------------------------------------*/ - -import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; -import type { DateValue, Value } from "../../values/Values.js"; -import { isDateTimeValue } from "../../values/Values.js"; - -/** - * - */ -export function useDateTimeEditorProps({ - value, - onChange, - ...rest -}: EditorProps): ConcreteEditorProps { - return { - ...rest, - value: getDateTimeValue(value), - onChange, - }; -} - -function getDateTimeValue(value: Value | undefined): DateValue { - return value && isDateTimeValue(value) ? value : { value: new Date() }; -} diff --git a/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx index 291db609129..5de3f08daa9 100644 --- a/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/enum/EnumEditor.tsx @@ -8,7 +8,8 @@ import type { EditorProps } from "../../Types.js"; import { useEnumEditorProps } from "./UseEnumEditorProps.js"; /** - * + * Simple editor for editing enum values. + * @beta */ export function EnumEditor(props: EditorProps) { const { value, onChange, onFinish, choices, disabled, size } = diff --git a/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts index 9ebfdd89f90..4edcb6c9323 100644 --- a/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/enum/UseEnumEditorProps.ts @@ -3,20 +3,23 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; -import type { EnumValueMetadata } from "../../values/Metadata.js"; -import type { EnumChoice, EnumValue, Value } from "../../values/Values.js"; +import type { EditorProps, RequiredProps } from "../../Types.js"; +import type { EnumChoice, EnumValueMetadata } from "../../values/Metadata.js"; +import type { EnumValue, Value } from "../../values/Values.js"; import { isEnumValue } from "../../values/Values.js"; /** - * + * Hooks that converts generic `EditorProps` into editor props with enum value. If value is not enum, it will be converted into empty enum value. + * @beta */ export function useEnumEditorProps({ metadata, value, onChange, ...rest -}: EditorProps): ConcreteEditorProps & { choices: EnumChoice[] } { +}: EditorProps): RequiredProps, "value"> & { + choices: EnumChoice[]; +} { const choices = metadata.type === "enum" ? (metadata as EnumValueMetadata).choices : []; diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric/FormattedNumericInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/FormattedNumericInput.tsx index 57d3fcd18eb..b53b2a6cb74 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric/FormattedNumericInput.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/FormattedNumericInput.tsx @@ -6,18 +6,23 @@ import * as React from "react"; import { Input } from "@itwin/itwinui-react"; import type { NumericValue } from "../../values/Values.js"; +/** + * Props for FormattedNumericInput component. + * @beta + */ interface FormattedNumericInputProps { value: NumericValue; onChange: (value: NumericValue) => void; parseValue: (value: string) => number | undefined; formatValue: (num: number) => string; + onBlur: () => void; disabled?: boolean; size?: "small" | "large"; - onBlur: () => void; } /** - * + * A numeric input that allows to pass custom parsing/formatting logic to handle values with units etc. + * @beta */ export function FormattedNumericInput({ onChange, diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric/NumericEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/NumericEditor.tsx index 56d150d772f..7a8cbd1e473 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric/NumericEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/NumericEditor.tsx @@ -8,7 +8,8 @@ import type { EditorProps } from "../../Types.js"; import { useNumericEditorProps } from "./UseNumericEditorProps.js"; /** - * + * Simple editor for editing numeric values. + * @beta */ export function NumericEditor(props: EditorProps) { const { value, onChange, onFinish, size, disabled } = diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx b/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx index af7de52b46f..e0e10d22bd1 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/QuantityInput.tsx @@ -1,3 +1,7 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ import * as React from "react"; import { FormattedNumericInput } from "./FormattedNumericInput.js"; import type { FormatterSpec, ParserSpec } from "@itwin/core-quantity"; @@ -16,7 +20,8 @@ interface QuantityInputProps } /** - * + * A component that wraps `FormattedNumericInput` and provides a formatter and parser for quantity values. + * @beta */ export function QuantityInput({ formatter, diff --git a/ui/components-react/src/components-react/newEditors/editors/numeric/UseNumericEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/numeric/UseNumericEditorProps.ts index b51621b5da7..8cc7e958b5a 100644 --- a/ui/components-react/src/components-react/newEditors/editors/numeric/UseNumericEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/numeric/UseNumericEditorProps.ts @@ -3,18 +3,19 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; +import type { EditorProps, RequiredProps } from "../../Types.js"; import type { NumericValue, Value } from "../../values/Values.js"; import { isNumericValue } from "../../values/Values.js"; /** - * + * Hooks that converts generic `EditorProps` into editor props with numeric value. If value is not numeric, it will be converted into empty numeric value. + * @beta */ export function useNumericEditorProps({ value, onChange, ...rest -}: EditorProps): ConcreteEditorProps { +}: EditorProps): RequiredProps, "value"> { return { ...rest, value: getNumericValue(value), diff --git a/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx b/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx index 7b72890640b..34d86d16811 100644 --- a/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx +++ b/ui/components-react/src/components-react/newEditors/editors/text/TextEditor.tsx @@ -8,7 +8,8 @@ import type { EditorProps } from "../../Types.js"; import { useTextEditorProps } from "./UseTextEditorProps.js"; /** - * + * Simple editor for editing text values. + * @beta */ export function TextEditor(props: EditorProps) { const { value, onChange, onFinish, size, disabled } = diff --git a/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts b/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts index b3acc93994b..564e513c1a1 100644 --- a/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts +++ b/ui/components-react/src/components-react/newEditors/editors/text/UseTextEditorProps.ts @@ -3,18 +3,19 @@ * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { ConcreteEditorProps, EditorProps } from "../../Types.js"; +import type { EditorProps, RequiredProps } from "../../Types.js"; import type { TextValue, Value } from "../../values/Values.js"; import { isTextValue } from "../../values/Values.js"; /** - * + * Hooks that converts generic `EditorProps` into editor props with text value. If value is not text, it will be converted into empty text value. + * @beta */ export function useTextEditorProps({ value, onChange, ...rest -}: EditorProps): ConcreteEditorProps { +}: EditorProps): RequiredProps, "value"> { return { ...rest, value: getTextValue(value), diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx index a20be7916a6..ee5a13c8f6e 100644 --- a/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/DefaultEditors.tsx @@ -2,41 +2,66 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ + import { BooleanEditor } from "../editors/boolean/BooleanEditor.js"; -import { DateTimeEditor } from "../editors/date/DateTimeEditor.js"; +import { DateEditor } from "../editors/date/DateTimeEditor.js"; import { EnumEditor } from "../editors/enum/EnumEditor.js"; import { NumericEditor } from "../editors/numeric/NumericEditor.js"; import { TextEditor } from "../editors/text/TextEditor.js"; -import { OldEnumEditorSpec } from "../interop/old-editors/enum/Enum.js"; +import { EnumEditorSpec as InterOpEnumEditorSpec } from "../interop/old-editors/enum/Enum.js"; import { EnumButtonGroupEditorSpec } from "../interop/old-editors/enum/EnumButtonGroup.js"; import type { EditorSpec } from "../Types.js"; +/** + * Specification for default text editor. It applies for values whose type is "string". + * @beta + */ export const TextEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "string", Editor: TextEditor, }; +/** + * Specification for default date editor. It applies for values whose type is "date". + * @beta + */ export const DateEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "date", - Editor: DateTimeEditor, + Editor: DateEditor, }; +/** + * Specification for default boolean editor. It applies for values whose type is "bool". + * @beta + */ export const BoolEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "bool", Editor: BooleanEditor, }; +/** + * Specification for default numeric editor. It applies for values whose type is "number". + * @beta + */ export const NumericEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "number", Editor: NumericEditor, }; +/** + * Specification for default enum editor. It applies for values whose type is "enum". + * @beta + */ export const EnumEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "enum", Editor: EnumEditor, }; +/** + * List of default editors that are used as fallback if EditorRegistry does not provide a custom editor. + * @internal + */ export const defaultEditors: EditorSpec[] = [ TextEditorSpec, BoolEditorSpec, @@ -45,8 +70,12 @@ export const defaultEditors: EditorSpec[] = [ EnumEditorSpec, ]; -// editors that are rewritten based on the old version that accepts editor params from `PropertyRecord` +/** + * Editors that are rewritten based on the old version that accepts editor params from `PropertyRecord`. Needed to support + * editing customizations used through `PropertyRecord`. + * @internal + */ export const interopEditors: EditorSpec[] = [ EnumButtonGroupEditorSpec, - OldEnumEditorSpec, + InterOpEnumEditorSpec, ]; diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx deleted file mode 100644 index d456bc23f05..00000000000 --- a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistry.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { createContext, useContext } from "react"; -import type { EditorSpec } from "../Types.js"; - -interface EditorsRegistry { - editors: EditorSpec[]; -} - -export const editorsRegistryContext = createContext({ - editors: [], -}); - -/** - * - */ -export function useEditorsRegistry() { - return useContext(editorsRegistryContext); -} diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryContext.ts b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryContext.ts new file mode 100644 index 00000000000..eb0f8e518d9 --- /dev/null +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryContext.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Bentley Systems, Incorporated. All rights reserved. + * See LICENSE.md in the project root for license terms and full copyright notice. + *--------------------------------------------------------------------------------------------*/ + +import * as React from "react"; +import type { EditorSpec } from "../Types.js"; + +interface EditorsRegistry { + editors: EditorSpec[]; +} + +/** + * Context for storing registered editors. + * @internal + */ +export const editorsRegistryContext = React.createContext({ + editors: [], +}); diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx index 6562f16986d..d83d5ed2072 100644 --- a/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/EditorsRegistryProvider.tsx @@ -2,13 +2,16 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ + import * as React from "react"; import { useContext, useMemo } from "react"; import type { EditorSpec } from "../Types.js"; -import { editorsRegistryContext } from "./EditorsRegistry.js"; +import { editorsRegistryContext } from "./EditorsRegistryContext.js"; /** - * + * Provider that adds supplied editors into `EditorsRegistry`. Multiple providers can be nested together. + * Editors added through the lowest level provider will have the highest priority. + * @beta */ export function EditorsRegistryProvider({ children, diff --git a/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts b/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts index e45450fa983..2fe1ea79797 100644 --- a/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts +++ b/ui/components-react/src/components-react/newEditors/editorsRegistry/UseEditor.ts @@ -2,20 +2,24 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ +import * as React from "react"; import type { EditorSpec } from "../Types.js"; import type { ValueMetadata } from "../values/Metadata.js"; import type { Value } from "../values/Values.js"; import { defaultEditors, interopEditors } from "./DefaultEditors.js"; -import { useEditorsRegistry } from "./EditorsRegistry.js"; +import { editorsRegistryContext } from "./EditorsRegistryContext.js"; /** + * Custom hooks that returns editor for specified metadata and value. It uses `EditorsRegistry` context to find registered editors. + * If no registered editor is found, it will use applicable default editor. * + * @beta */ export function useEditor( metadata: ValueMetadata, value: Value | undefined ): EditorSpec["Editor"] | undefined { - const { editors } = useEditorsRegistry(); + const { editors } = React.useContext(editorsRegistryContext); const registeredEditor = editors.find((editor) => editor.applies(metadata, value) diff --git a/ui/components-react/src/components-react/newEditors/index.ts b/ui/components-react/src/components-react/newEditors/index.ts index 7f2e88a931c..41e5431a27c 100644 --- a/ui/components-react/src/components-react/newEditors/index.ts +++ b/ui/components-react/src/components-react/newEditors/index.ts @@ -11,7 +11,7 @@ export * from "./editorsRegistry/DefaultEditors.js"; export * from "./editors/boolean/BooleanEditor.js"; export * from "./editors/boolean/UseBooleanEditorProps.js"; export * from "./editors/date/DateTimeEditor.js"; -export * from "./editors/date/UseDateTimeEditorProps.js"; +export * from "./editors/date/UseDateEditorProps.js"; export * from "./editors/numeric/NumericEditor.js"; export * from "./editors/numeric/UseNumericEditorProps.js"; export * from "./editors/numeric/FormattedNumericInput.js"; diff --git a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts index f27cc4cb8ec..7364c069fdf 100644 --- a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts +++ b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts @@ -19,7 +19,7 @@ import type { } from "../values/Values.js"; import { isBooleanValue, - isDateTimeValue, + isDateValue, isEnumValue, isNumericValue, isTextValue, @@ -153,7 +153,7 @@ export namespace EditorInterop { displayValue: newValue.value.toString(), }; } - if (isDateTimeValue(newValue)) { + if (isDateValue(newValue)) { return { valueFormat: PropertyValueFormat.Primitive, value: newValue.value, diff --git a/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx index 0ac47eab95e..ed5dff936e5 100644 --- a/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx +++ b/ui/components-react/src/components-react/newEditors/interop/old-editors/enum/EnumButtonGroup.tsx @@ -4,11 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as React from "react"; -import type { - ConcreteEditorProps, - EditorProps, - EditorSpec, -} from "../../../Types.js"; +import type { EditorProps, EditorSpec, RequiredProps } from "../../../Types.js"; import { type EnumValue, isEnumValue } from "../../../values/Values.js"; import type { ButtonGroupEditorParams } from "@itwin/appui-abstract"; import { @@ -59,9 +55,10 @@ function EnumButtonGroupEditor(props: EditorProps) { ); } -function useEnumButtonGroupEditorProps( - props: EditorProps -): ConcreteEditorProps & { +function useEnumButtonGroupEditorProps(props: EditorProps): RequiredProps< + EditorProps, + "value" +> & { choices: EnumerationChoice[]; enumIcons?: Map; } { diff --git a/ui/components-react/src/components-react/newEditors/values/Metadata.ts b/ui/components-react/src/components-react/newEditors/values/Metadata.ts index e635da05b1d..f41f9b5e0ca 100644 --- a/ui/components-react/src/components-react/newEditors/values/Metadata.ts +++ b/ui/components-react/src/components-react/newEditors/values/Metadata.ts @@ -2,10 +2,10 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ -import type { EnumChoice } from "./Values.js"; /** - * + * Value types supported by editor system. + * @beta */ export type ValueType = | "string" @@ -16,7 +16,8 @@ export type ValueType = | "instanceKey"; /** - * + * Additional metadata that is used along side value to determine applicable editor. + * @beta */ export interface ValueMetadata { type: ValueType; @@ -24,7 +25,15 @@ export interface ValueMetadata { } /** - * + * Type definition for available enum choice that can be supplied for editing enum values. + */ +export interface EnumChoice { + value: number | string; + label: string; +} + +/** + * Additional metadata that is used along side enum value to determine applicable editor. */ export interface EnumValueMetadata extends ValueMetadata { type: "enum"; diff --git a/ui/components-react/src/components-react/newEditors/values/Values.ts b/ui/components-react/src/components-react/newEditors/values/Values.ts index 52769d0e13f..9407309e90d 100644 --- a/ui/components-react/src/components-react/newEditors/values/Values.ts +++ b/ui/components-react/src/components-react/newEditors/values/Values.ts @@ -2,8 +2,10 @@ * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ + /** - * + * Type definition for numeric value that can be handled by editor. + * @beta */ export interface NumericValue { rawValue: number | undefined; @@ -12,7 +14,8 @@ export interface NumericValue { } /** - * + * Type definition for instance key value that can be handled by editor. + * @beta */ export interface InstanceKeyValue { key: { id: string; className: string }; @@ -20,28 +23,32 @@ export interface InstanceKeyValue { } /** - * + * Type definition for text value that can be handled by editor. + * @beta */ export interface TextValue { value: string; } /** - * + * Type definition for boolean value that can be handled by editor. + * @beta */ export interface BooleanValue { value: boolean; } /** - * + * Type definition for date value that can be handled by editor. + * @beta */ export interface DateValue { value: Date; } /** - * + * Type definition for enum value that can be handled by editor. + * @beta */ export interface EnumValue { choice: number | string; @@ -60,7 +67,8 @@ export type Value = | EnumValue; /** - * + * Type guard for text value. + * @beta */ export function isTextValue( value: Value | undefined @@ -71,7 +79,8 @@ export function isTextValue( } /** - * + * Type guard for numeric value. + * @beta */ export function isNumericValue( value: Value | undefined @@ -82,7 +91,8 @@ export function isNumericValue( } /** - * + * Type guard for boolean value. + * @beta */ export function isBooleanValue( value: Value | undefined @@ -94,9 +104,10 @@ export function isBooleanValue( } /** - * + * Type guard for date value. + * @beta */ -export function isDateTimeValue( +export function isDateValue( value: Value | undefined ): value is DateValue | undefined { return ( @@ -105,18 +116,11 @@ export function isDateTimeValue( } /** - * + * Type guard for enum value. + * @beta */ export function isEnumValue( value: Value | undefined ): value is EnumValue | undefined { return value === undefined || ("choice" in value && "label" in value); } - -/** - * - */ -export interface EnumChoice { - value: number | string; - label: string; -} diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx index 63da9f9f44d..76ce691b51f 100644 --- a/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/editors/NewColorEditor.tsx @@ -16,33 +16,35 @@ import { Popover, } from "@itwin/itwinui-react"; import type { - ConcreteEditorProps, EditorProps, EditorSpec, NumericValue, + RequiredProps, ValueMetadata, } from "@itwin/components-react"; import { isNumericValue } from "@itwin/components-react"; +/** + * Editor specification for color editor. + * @beta + */ export const ColorEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "number" && metadata.preferredEditor === StandardEditorNames.ColorPicker && (metadata as ColorValueMetadata).params !== undefined, - Editor: NewColorEditor, + Editor: ColorEditor, }; /** - * + * Metadata for color editor. + * @beta */ export interface ColorValueMetadata extends ValueMetadata { params: ColorEditorParams[]; } -/** - * - */ -export function NewColorEditor(props: EditorProps) { +function ColorEditor(props: EditorProps) { const { value, onChange, onFinish, colors, size } = useColorEditorProps(props); @@ -84,7 +86,9 @@ function useColorEditorProps({ value, onChange, ...props -}: EditorProps): ConcreteEditorProps & { colors: number[] } { +}: EditorProps): RequiredProps, "value"> & { + colors: number[]; +} { const params = (metadata as ColorValueMetadata).params[0]; const colors = params.colorValues; diff --git a/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx index dff3776276c..325da82c238 100644 --- a/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/editors/NewWeightEditor.tsx @@ -5,7 +5,7 @@ import * as React from "react"; import { StandardEditorNames } from "@itwin/appui-abstract"; import { WeightPickerButton } from "../lineweight/WeightPickerButton.js"; -import type { ConcreteEditorProps } from "@itwin/components-react"; +import type { RequiredProps } from "@itwin/components-react"; import { type EditorProps, type EditorSpec, @@ -13,17 +13,18 @@ import { type NumericValue, } from "@itwin/components-react"; +/** + * Editor specification for weight editor. + * @beta + */ export const WeightEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "number" && metadata.preferredEditor === StandardEditorNames.WeightPicker, - Editor: NewWeightEditor, + Editor: WeightEditor, }; -/** - * - */ -export function NewWeightEditor(props: EditorProps) { +function WeightEditor(props: EditorProps) { const { value, onChange, onFinish } = useWeightEditorProps(props); const activeWeight = value.rawValue ?? 0; @@ -44,7 +45,7 @@ function useWeightEditorProps({ value, onChange, ...props -}: EditorProps): ConcreteEditorProps { +}: EditorProps): RequiredProps, "value"> { return { ...props, value: From 12c3105ea1d7e8accb660756e698825d0840849d Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Fri, 20 Dec 2024 14:06:48 +0200 Subject: [PATCH 11/12] Cleanup after rebase --- .../newEditors/interop/EditorInterop.ts | 7 +++---- .../src/imodel-components-react.ts | 2 +- .../inputs/{ => newEditors}/QuantityEditor.tsx | 13 +++++++------ .../inputs/newEditors}/QuantityInput.tsx | 2 +- .../inputs/{ => newEditors}/UseQuantityInfo.ts | 9 +++------ 5 files changed, 15 insertions(+), 18 deletions(-) rename ui/imodel-components-react/src/imodel-components-react/inputs/{ => newEditors}/QuantityEditor.tsx (86%) rename ui/{components-react/src/components-react/newEditors/editors/numeric => imodel-components-react/src/imodel-components-react/inputs/newEditors}/QuantityInput.tsx (96%) rename ui/imodel-components-react/src/imodel-components-react/inputs/{ => newEditors}/UseQuantityInfo.ts (90%) diff --git a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts index 7364c069fdf..1444b89b7c0 100644 --- a/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts +++ b/ui/components-react/src/components-react/newEditors/interop/EditorInterop.ts @@ -90,10 +90,9 @@ export namespace EditorInterop { value: { rawValue: primitiveValue.value as number, displayValue: - primitiveValue.displayValue !== undefined && - primitiveValue.displayValue !== "" - ? `${parseFloat(primitiveValue.displayValue)}` - : `${(primitiveValue.value as number) ?? ""}`, + primitiveValue.value !== undefined + ? `${primitiveValue.value as number}` + : primitiveValue.displayValue ?? "", } satisfies NumericValue, }; case "enum": diff --git a/ui/imodel-components-react/src/imodel-components-react.ts b/ui/imodel-components-react/src/imodel-components-react.ts index f7f90b70136..d62420c016c 100644 --- a/ui/imodel-components-react/src/imodel-components-react.ts +++ b/ui/imodel-components-react/src/imodel-components-react.ts @@ -58,7 +58,7 @@ export { export { QuantityEditor, QuantityEditorSpec, -} from "./imodel-components-react/inputs/QuantityEditor.js"; +} from "./imodel-components-react/inputs/newEditors/QuantityEditor.js"; export { LineWeightSwatch, diff --git a/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx b/ui/imodel-components-react/src/imodel-components-react/inputs/newEditors/QuantityEditor.tsx similarity index 86% rename from ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx rename to ui/imodel-components-react/src/imodel-components-react/inputs/newEditors/QuantityEditor.tsx index 635912c13f3..0433742e9d7 100644 --- a/ui/imodel-components-react/src/imodel-components-react/inputs/QuantityEditor.tsx +++ b/ui/imodel-components-react/src/imodel-components-react/inputs/newEditors/QuantityEditor.tsx @@ -7,8 +7,12 @@ import { type QuantityTypeArg } from "@itwin/core-frontend"; import { useQuantityInfo } from "./UseQuantityInfo.js"; import type { EditorProps, EditorSpec } from "@itwin/components-react"; import { useNumericEditorProps } from "@itwin/components-react"; -import { QuantityFormattedInput } from "./QuantityFormattedInput.js"; +import { QuantityInput } from "./QuantityInput.js"; +/** + * Editor specification for quantity values based on `IModelApp.quantityFormatter`. + * @beta + */ export const QuantityEditorSpec: EditorSpec = { applies: (metadata) => metadata.type === "number" && @@ -17,10 +21,7 @@ export const QuantityEditorSpec: EditorSpec = { Editor: QuantityEditor, }; -/** - * - */ -export function QuantityEditor(props: EditorProps) { +function QuantityEditor(props: EditorProps) { const { onChange, value, onFinish, size } = useNumericEditorProps(props); const quantityType = "quantityType" in props.metadata @@ -32,7 +33,7 @@ export function QuantityEditor(props: EditorProps) { }); return ( - { - const defaultFormatterSpec = - type !== undefined - ? IModelApp.quantityFormatter.findFormatterSpecByQuantityType(type) - : undefined; - if (defaultFormatterSpec === undefined) { + if (type === undefined) { setState({ formatter: undefined, parser: undefined, From 6a28caba9ef5b6f463f37b188b41bab24d8d0c23 Mon Sep 17 00:00:00 2001 From: "Saulius.Skliutas" <24278440+saskliutas@users.noreply.github.com> Date: Fri, 20 Dec 2024 16:01:57 +0200 Subject: [PATCH 12/12] No need for base editor props --- .../src/components-react/newEditors/Types.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ui/components-react/src/components-react/newEditors/Types.ts b/ui/components-react/src/components-react/newEditors/Types.ts index 204ea1a4fd9..56d53780c4d 100644 --- a/ui/components-react/src/components-react/newEditors/Types.ts +++ b/ui/components-react/src/components-react/newEditors/Types.ts @@ -15,24 +15,18 @@ export interface EditorSpec { } /** - * Base editor props that are supplied to every editor when rendering it. + * Generic editor props that are supplied to the editor for rendering. + * @beta */ -interface BaseEditorProps { +export interface EditorProps { metadata: ValueMetadata; + value?: TValue; onChange: (value: TValue) => void; onFinish: () => void; disabled?: boolean; size?: "small" | "large"; } -/** - * Generic editor props that are supplied to the editor for rendering. - * @beta - */ -export interface EditorProps extends BaseEditorProps { - value?: TValue; -} - /** * A type that makes a specific properties required in a type. * @beta