@rytass/bpm-core-react 0.21.2 → 0.21.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/chunks/{FormBuilderView-CGKkuVFi.cjs → FormBuilderView-Bq4TXdxF.cjs} +2 -2
- package/dist/chunks/{FormBuilderView-CGKkuVFi.cjs.map → FormBuilderView-Bq4TXdxF.cjs.map} +1 -1
- package/dist/chunks/{FormBuilderView-6KdApi-D.js → FormBuilderView-DNsFwZgk.js} +2 -2
- package/dist/chunks/{FormBuilderView-6KdApi-D.js.map → FormBuilderView-DNsFwZgk.js.map} +1 -1
- package/dist/chunks/{FormRendererView-BcQKUi1A.js → FormRendererView-BF8Ge0Zk.js} +178 -153
- package/dist/chunks/FormRendererView-BF8Ge0Zk.js.map +1 -0
- package/dist/chunks/FormRendererView-ZIfSRWPi.cjs +6 -0
- package/dist/chunks/FormRendererView-ZIfSRWPi.cjs.map +1 -0
- package/dist/chunks/admin-pickers-BjbkBpSJ.cjs.map +1 -1
- package/dist/chunks/admin-pickers-DfGxsjpp.js.map +1 -1
- package/dist/chunks/approval-instance-list-page-BI5fIXc-.js.map +1 -1
- package/dist/chunks/approval-instance-list-page-Cv5I1YPX.cjs.map +1 -1
- package/dist/chunks/auth-provider-4BeCw7cI.cjs.map +1 -1
- package/dist/chunks/auth-provider-C2b-5mGt.js.map +1 -1
- package/dist/chunks/categories-CyyqG6wG.cjs.map +1 -1
- package/dist/chunks/categories-f7E-RJEc.js.map +1 -1
- package/dist/chunks/{compose-M-GR6smM.js → compose-C_Y38ENa.js} +4 -4
- package/dist/chunks/{compose-M-GR6smM.js.map → compose-C_Y38ENa.js.map} +1 -1
- package/dist/chunks/{compose-CtWuunwB.cjs → compose-NHbyjV73.cjs} +2 -2
- package/dist/chunks/{compose-CtWuunwB.cjs.map → compose-NHbyjV73.cjs.map} +1 -1
- package/dist/chunks/dashboard-page-BoqNhjT_.js.map +1 -1
- package/dist/chunks/dashboard-page-D7YJ3udk.cjs.map +1 -1
- package/dist/chunks/delegations-ClRCwD1I.cjs.map +1 -1
- package/dist/chunks/delegations-CqND-Qtz.js.map +1 -1
- package/dist/chunks/delegations-D43VDSz1.cjs.map +1 -1
- package/dist/chunks/delegations-zisffLwk.js.map +1 -1
- package/dist/chunks/{designer-BnMeiMVe.js → designer-BRYIpeTC.js} +3 -3
- package/dist/chunks/{designer-BnMeiMVe.js.map → designer-BRYIpeTC.js.map} +1 -1
- package/dist/chunks/{designer-D6fCDq_q.cjs → designer-oDGvHWBJ.cjs} +2 -2
- package/dist/chunks/{designer-D6fCDq_q.cjs.map → designer-oDGvHWBJ.cjs.map} +1 -1
- package/dist/chunks/{detail-C5KrUC7s.cjs → detail-BLhZp1mO.cjs} +2 -2
- package/dist/chunks/detail-BLhZp1mO.cjs.map +1 -0
- package/dist/chunks/{detail-Dnc4PPWO.js → detail-CkRgfU7l.js} +2 -2
- package/dist/chunks/detail-CkRgfU7l.js.map +1 -0
- package/dist/chunks/login-D6cc3bo7.js.map +1 -1
- package/dist/chunks/login-uvVcltrS.cjs.map +1 -1
- package/dist/chunks/notifications-Bc4mgSds.cjs.map +1 -1
- package/dist/chunks/notifications-Cvme5Qbl.js.map +1 -1
- package/dist/chunks/orgs-B_2J2tLX.cjs.map +1 -1
- package/dist/chunks/orgs-DqxMds26.js.map +1 -1
- package/dist/chunks/users-BBtiZ9QQ.js.map +1 -1
- package/dist/chunks/users-EqSzDhuj.cjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/instances/detail/index.cjs +1 -1
- package/dist/pages/instances/detail/index.js +1 -1
- package/dist/pages/templates/compose/index.cjs +1 -1
- package/dist/pages/templates/compose/index.js +1 -1
- package/dist/pages/templates/designer/index.cjs +1 -1
- package/dist/pages/templates/designer/index.js +1 -1
- package/dist/views/forms/builder/index.cjs +1 -1
- package/dist/views/forms/builder/index.js +1 -1
- package/dist/views/forms/renderer/form-control-menu.d.cts +7 -0
- package/dist/views/forms/renderer/form-control-menu.d.ts +7 -0
- package/dist/views/forms/renderer/form-data-source-field.d.cts +12 -0
- package/dist/views/forms/renderer/form-data-source-field.d.ts +12 -0
- package/dist/views/forms/renderer/index.cjs +1 -1
- package/dist/views/forms/renderer/index.js +1 -1
- package/dist/views/instances/detail/index.cjs +1 -1
- package/dist/views/instances/detail/index.js +1 -1
- package/dist/views/instances/new/index.cjs +1 -1
- package/dist/views/instances/new/index.js +1 -1
- package/dist/views/templates/compose/index.cjs +1 -1
- package/dist/views/templates/compose/index.js +1 -1
- package/dist/views/templates/designer/index.cjs +1 -1
- package/dist/views/templates/designer/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunks/FormRendererView-B-xycC9S.cjs +0 -6
- package/dist/chunks/FormRendererView-B-xycC9S.cjs.map +0 -1
- package/dist/chunks/FormRendererView-BcQKUi1A.js.map +0 -1
- package/dist/chunks/detail-C5KrUC7s.cjs.map +0 -1
- package/dist/chunks/detail-Dnc4PPWO.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormRendererView-ZIfSRWPi.cjs","names":["isFormDataSourceFieldDefinition","useMemo","readFormDataSourceValueSignature","readFormDataSourceSelectedValues","buildFormRendererValues","useState","readMissingFormDataSourceDependencies","useRef","mergeFormDataSourceOptions","useCallback","previewFormFieldOptions","readFormFieldOptions","readSelectedFormDataSourceOptions","readFormDataSourceErrorMessage","readFormDataSourceErrorCode","previewResolveFormFieldOptions","resolveFormFieldOptions","readMissingFormDataSourceOptionValues","useState","useCallback","useMemo","useRef","Upload","formatMemberLabel","readFormMemberIds","useState","useRef","resolveMembers","searchMembers","AutoComplete","useState","useRef","resolveOrgUnitOptions","searchOrgUnitOptions","AutoComplete","Typography","useMemo","useState","buildFormRendererValues","useRef","readVisibleFormRendererFields","Typography","isTableFieldDefinition","isFormRendererFieldRequired","isFormRendererFieldReadonly","isFormDataSourceFieldDefinition","BPMFormField","Button","readFormTableRows","readFormTableRowBounds","createFormTableRow","readFormTableCellPath","TrashIcon","Table","PlusIcon","isFormTableCellValue","Textarea","Toggle","isFormStaticOptionFieldDefinition","readFieldOptionAsSelectOption","Select","readSelectOption","DatePicker","formatDatePickerValue","readDatePickerValue","DateTimePicker","formatDateTimePickerValue","Input","clampOptionalNumber","parseOptionalNumberInput","readSelectedFormDataSourceOptions","readFormFieldSelectionMode","AutoComplete","RadioGroup","CheckboxGroup","require_bpm_form_field","require_format_member_label","react","_mezzanine_ui_react","_rytass_bpm_core_client","react_jsx_runtime","_rytass_bpm_core_client_form","_mezzanine_ui_icons","_rytass_bpm_core_shared_form","_rytass_bpm_core_client_organization","EMPTY_FIELD_STATE","NO_INVALID_VALUES","MIN_SCROLLABLE_OPTION_COUNT","FORM_DATA_SOURCE_EXPRESSION_REFRESH_DEBOUNCE_MS","RECOVERABLE_ERROR_CODES","readOwnFormKey","input","fieldKey","separatorIndex","omitKey","key","values","candidate","readBindingRefreshInput","from","binding","scope","readUnknownBindingRefreshInput","stringifyStable","sortObjectKeys","value","entry","left","right","useFormDataSourceField","dynamicField","queryFieldKey","snapshot","dynamicFieldSignature","snapshotSignature","snapshotOptions","formDataSignature","initialFormDataSignature","schemaSignature","uiSchemaSignature","contextKind","contextInstanceId","contextTemplateId","rowValuesSignature","initialRowValuesSignature","currentValue","currentValueSignature","selectedValues","hasValue","bindingsRefreshSignature","valueRefreshSignature","dataSourceRefreshSignature","settledBindingsRefreshSignature","useSettledSignature","refreshSettling","effectiveContext","valueChanged","bindingsChanged","hasExpressionBinding","heldValueBaseline","setHeldValueBaseline","expressionInputsChanged","isStale","needsResolve","quiescentStatus","readQuiescentFormDataSourceStatus","autoQueries","pendingQueryStatus","readPendingQueryStatus","quiescentStatusRef","remoteOptions","setRemoteOptions","searchText","setSearchText","nextCursor","setNextCursor","resolution","setResolution","status","setStatus","error","setError","loadingNextPage","setLoadingNextPage","loadedOptionCount","requestSequence","resolveSequence","optionsAbort","resolveAbort","queriedSinceReset","lastRequest","queryFormData","readDefinedValues","queryRowValues","mergedOptions","currentResolution","resolvePending","selectionExpired","invalidValues","readInvalidFormDataSourceValues","loadOptions","requestId","nextSearchTextToLoad","controller","cursor","isSupersededRequest","selectedOptions","currentOptions","result","loadOptionsRef","message","requestError","code","readFailedQueryStatus","runResolve","signature","valueSignature","runResolveRef","currentCursor","currentSearchText","currentError","onSearch","onOpen","nextPageRequest","onReachBottom","pageSearchText","retry","readFormDataSourceExposedStatus","settled","setSettled","delayMs","timer","readFormDataSourceFieldStatusMessage","state","isFormDataSourceFieldSubmissionBlocked","readFormDataSourceSubmissionBlockMessage","blocked","states","waiting","recoverable","sequence","defined","readStringArrayValue","item","readFileUploadSubmissionBlockMessage","useFileUploadFieldStates","setStates","onFileUploadStateChange","current","UNKNOWN_ATTACHMENT_NAME","UPLOAD_UNAVAILABLE_HINT","FileUploadFieldControl","pendingFiles","setPendingFiles","ownFiles","setOwnFiles","listOrder","setListOrder","valueIds","valueKey","attachmentsById","attachments","attachment","files","position","id","index","createListedFile","uploadListRef","mountedRef","handleChangeRef","handleChange","stableOnChange","nextFiles","pendingCount","file","failedCount","onStateChangeRef","onStateChange","finished","nextIds","attachedIds","justUploaded","onChange","uploadFile","onUploadAttachment","field","handleReload","replacement","readUploadErrorMessage","fileId","handleDownload","downloadLocalFile","onDownloadAttachment","canUpload","readonly","hints","selectedFiles","createDisplayFile","size","url","anchor","UNICODE_WHITESPACE_PATTERN","hasUnsafeCharacter","codePoint","character","ABSOLUTE_HTTP_PATTERN","resolveFormFieldLink","isSafeSiteRelativePath","isSafeAbsoluteHttpUrl","PLACEHOLDER_BASE","resolved","resolveMemberProfilesPerIdOnFailure","maxPerIdRequests","limits","concurrency","resolveMembers","memberIds","askedIds","settleWithConcurrency","memberId","excludeFailedMemberIds","failedMemberIds","results","nextIndex","worker","items","run","UNKNOWN_MEMBER_NAME","readMemberFieldOption","member","readUnresolvedMemberFieldOption","mergeMemberFieldOptions","resolvedById","nextOptions","option","currentIds","addedOptions","upgradedOptions","readUnresolvedMemberIds","knownIds","knownOptions","readSelectedMemberFieldOptions","readMemberFieldValue","selected","multiple","MemberFieldControl","setKnownOptions","searchResults","setSearchResults","loading","setLoading","latestSearchRef","givenUpIdsRef","unresolvedKey","cancelled","profiles","placeholders","skippedIds","placeholder","handleSearch","options","commonProps","open","nextOption","UNKNOWN_ORG_UNIT_NAME","DELETED_ORG_UNIT_SUFFIX","readOrgUnitFieldOption","record","readPendingOrgUnitFieldOption","orgUnitId","readUnknownOrgUnitFieldOption","readStoredOrgUnitId","readOrgUnitIdKey","isSameOrgUnitId","readSelectedOrgUnitFieldOption","readUnresolvedOrgUnitId","mergeOrgUnitFieldOptions","namedById","readOrgUnitFieldValue","ORG_UNIT_SEARCH_FAILED_TEXT","OrgUnitFieldControl","searchFailed","setSearchFailed","unresolvedId","records","FORM_RENDERER_GRID_STYLE","FORM_RENDERER_EMPTY_STYLE","FORM_RENDERER_FIELD_STYLE","TEXTAREA_STYLE","applyFullWidthTextareaHost","element","FormRenderer","fileUpload","internalValues","setInternalValues","schema","valuesRef","visibleFields","uiSchema","readOwnFieldAccess","fieldAccess","currentValues","updateValue","nextValues","nextValue","singleColumn","FormRendererField","errors","fieldLabelMarkers","readFieldAccessReadonly","readFieldColumnSpan","emptyText","access","formReadonly","required","fields","fieldReadonly","dataSourceState","dataSourceContext","dataSourceInitialValues","dataSourceStatusMessage","dataSourceStateSignature","onDataSourceStateChange","labelMarker","FormTableField","renderControl","DATA_SOURCE_FEEDBACK_STYLE","EXTERNAL_LINK_CUE","EXTERNAL_LINK_TITLE","LINK_STYLE","TABLE_FIELD_STYLE","TABLE_FIELD_ACTIONS_STYLE","TABLE_COLUMN_MIN_WIDTH","TABLE_CELL_STYLE","rows","bounds","rowIdSequenceRef","rowIds","setRowIds","createRowId","commitRows","nextRows","handleAddRow","handleRemoveRow","rowIndex","handleCellChange","readNextTableRow","row","columnKey","cellValue","dataSource","columns","column","FormTableCell","readInitialTableRow","actions","readTableCellValue","rowValues","initialRowValues","path","statusMessage","stateSignature","sequenceRef","renderDynamicOptionControl","renderStaticControl","link","event","readStringValue","readInputPlaceholder","readNumberInputValue","autoCompleteLoading","waitingForDependencies","interactiveSearch","optionControlDisabled","autoCompleteDisabled","type"],"sources":["../../src/views/forms/renderer/form-data-source-field.ts","../../src/views/forms/renderer/form-value-readers.ts","../../src/views/forms/renderer/file-upload-field.tsx","../../src/views/forms/renderer/form-field-link.ts","../../src/lib/member-profile-resolution.ts","../../src/views/forms/renderer/member-field-options.ts","../../src/views/forms/renderer/member-field.tsx","../../src/views/forms/renderer/org-unit-field-options.ts","../../src/views/forms/renderer/org-unit-field.tsx","../../src/views/forms/renderer/FormRendererView.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n FormDataSourceBinding,\n FormDataSourceBindingSource,\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldOption,\n FormFieldValue,\n FormUiSchema,\n isFormDataSourceFieldDefinition,\n} from '@rytass/bpm-core-shared/form';\nimport {\n buildFormRendererValues,\n FormDataSourceErrorCode,\n FormDataSourceFieldStatus,\n FormRendererValues,\n mergeFormDataSourceOptions,\n previewResolveFormFieldOptions,\n readFormDataSourceErrorCode,\n readFormDataSourceErrorMessage,\n readMissingFormDataSourceDependencies,\n readMissingFormDataSourceOptionValues,\n previewFormFieldOptions,\n readFormDataSourceSelectedValues,\n readFormDataSourceValueSignature,\n readSelectedFormDataSourceOptions,\n readFormFieldOptions,\n resolveFormFieldOptions,\n} from '@rytass/bpm-core-client/form';\n\nexport type FormRendererDataSourceContext =\n | { readonly kind: 'preview' }\n | {\n readonly instanceId?: string | null;\n readonly kind: 'runtime';\n readonly templateId?: string | null;\n };\n\nexport interface FormDataSourceFieldState {\n /**\n * False when nothing can be re-requested — a read-only or context-less field\n * has no query to retry, so the view must not offer a dead retry action.\n */\n readonly canRetry: boolean;\n readonly error: string | null;\n readonly hasValue: boolean;\n readonly hasNextPage: boolean;\n /**\n * True while the pending query is a next page rather than a first page. An\n * AutoComplete in async mode empties its list whenever it is told it is\n * loading, so the view keeps the loaded options on screen while paging.\n */\n readonly loadingNextPage: boolean;\n readonly invalidValues: readonly string[];\n /**\n * Call when the control's menu opens. A control that only queries on search\n * (AutoComplete) loads its first page here, so opening it lists options\n * instead of an empty menu; it is a no-op once that page is loaded.\n */\n readonly onOpen: () => void;\n readonly onReachBottom: () => void;\n readonly onSearch: (searchText: string) => void;\n readonly options: readonly FormFieldOption[];\n readonly retry: () => void;\n /**\n * True when the held value was valid for the form it was picked in, an\n * `EXPRESSION` binding's inputs have changed since, and the host's resolve\n * no longer accounts for it. The status is then `INVALID` and the message is\n * the host's `FORM_DATA_SOURCE_VALUE_NOT_RESOLVED` copy — the same sentence\n * a `FIELD` binding's stale value gets when the submission is refused.\n */\n readonly selectionExpired?: boolean;\n readonly status: FormDataSourceFieldStatus;\n}\n\n/**\n * What makes a table cell different from a top-level field: it is addressed by\n * a schema path, snapshotted under an instance path, and its `ROW_FIELD`\n * bindings read from its own row rather than the form (ADR 16 §3.3, §3.5).\n */\nexport interface UseFormDataSourceFieldRow {\n /** The row as it was loaded, so a returned case can tell what changed. */\n readonly initialValues?: FormRendererValues;\n readonly values: FormRendererValues;\n}\n\nexport interface UseFormDataSourceFieldInput {\n readonly context?: FormRendererDataSourceContext;\n readonly field: FormFieldDefinition;\n /** Schema path used to address the field; defaults to its own key. */\n readonly fieldPath?: string;\n readonly formData: FormRendererValues;\n readonly initialFormData?: FormRendererValues;\n readonly initialValue?: FormFieldValue | undefined;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n /** Set only for a table cell. */\n readonly row?: UseFormDataSourceFieldRow;\n readonly schema: FormDefinitionSchema;\n /** Snapshot map key; defaults to the field key. */\n readonly snapshotKey?: string;\n readonly uiSchema: FormUiSchema;\n}\n\ninterface FormDataSourceResolution {\n readonly signature: string;\n readonly unresolvedValues: readonly string[];\n /**\n * The value it answered for. A new case leaves the value out of\n * `signature` (and an `EXPRESSION` binding leaves the field's own key out\n * of its refresh input), so without this a fresh pick would inherit the\n * verdict on the value it replaced.\n */\n readonly valueSignature: string;\n}\n\nconst EMPTY_FIELD_STATE: FormDataSourceFieldState = {\n canRetry: false,\n error: null,\n hasValue: false,\n hasNextPage: false,\n loadingNextPage: false,\n invalidValues: [],\n onOpen: (): void => undefined,\n onReachBottom: (): void => undefined,\n onSearch: (): void => undefined,\n options: [],\n retry: (): void => undefined,\n status: 'IDLE',\n};\n\nconst NO_INVALID_VALUES: readonly string[] = [];\n\n/**\n * A dropdown only fires `onReachBottom` once its content overflows. A source\n * whose first page is smaller than the menu is therefore unreachable past page\n * one — `demo.cost-centers` serves 3 at a time, which fits exactly, so nothing\n * ever scrolls and the remaining options cannot be selected at all. Keep\n * pulling pages until there are enough rows to scroll, or the source runs out.\n */\nconst MIN_SCROLLABLE_OPTION_COUNT = 10;\n\n/**\n * How long an `EXPRESSION` binding's re-query waits for the form to settle.\n * Its refresh input is the whole form: the browser never parses CEL\n * (docs/24 §2.5), and the host does not report which fields an expression\n * reads — `waitingForFieldKeys` only lists the ones still empty, so it is no\n * dependency list. Without this every keystroke anywhere would query the\n * host. Aligned with the 300 ms `searchDebounceTime` of the renderer's\n * searchable DataSource controls.\n */\nconst FORM_DATA_SOURCE_EXPRESSION_REFRESH_DEBOUNCE_MS = 300;\n\n/**\n * Failures the filler can clear on their own by typing differently. They are\n * reported as an inline hint and never turn the field unavailable, because a\n * short search string says nothing about the validity of an existing value.\n */\nconst RECOVERABLE_ERROR_CODES: readonly FormDataSourceErrorCode[] = [\n 'FORM_DATA_SOURCE_SEARCH_NOT_SUPPORTED',\n 'FORM_DATA_SOURCE_SEARCH_TOO_SHORT',\n];\n\ninterface BindingRefreshScope {\n readonly formData: FormRendererValues;\n /** The form key holding this field's own value: never a dependency of it. */\n readonly ownFormKey: string | null;\n /** The row key holding this cell's own value, inside a table. */\n readonly ownRowKey: string | null;\n readonly rowValues: FormRendererValues | undefined;\n}\n\n/**\n * The form key this field's own value lives under: the field itself at the top\n * level, the table for a cell (`<tableKey>.<columnKey>` schema path, ADR 16\n * §3.3). Leaving it out keeps picking a value from re-querying the options.\n */\nfunction readOwnFormKey(\n fieldKey: string,\n input: UseFormDataSourceFieldInput,\n): string | null {\n if (!input.row) {\n return fieldKey;\n }\n\n const separatorIndex = input.fieldPath?.indexOf('.') ?? -1;\n\n return input.fieldPath && separatorIndex > 0\n ? input.fieldPath.slice(0, separatorIndex)\n : null;\n}\n\nfunction omitKey(\n values: FormRendererValues,\n key: string | null,\n): FormRendererValues {\n return key === null\n ? values\n : Object.fromEntries(\n Object.entries(values).filter(([candidate]) => candidate !== key),\n );\n}\n\n/**\n * What one binding feeds its parameter from, for the refresh signature. An\n * `EXPRESSION` is evaluated only by the backend, which never tells the browser\n * what it reads, so the whole form (and row) stands in for its dependencies;\n * the signature then changes whenever anything it could read does. A source\n * kind this version does not know is listed as such and never read.\n */\nfunction readBindingRefreshInput(\n binding: FormDataSourceBinding,\n scope: BindingRefreshScope,\n): unknown {\n const from: FormDataSourceBindingSource = binding.from;\n\n switch (from.kind) {\n case 'FIELD':\n return { fieldKey: from.fieldKey, value: scope.formData[from.fieldKey] };\n case 'CONSTANT':\n return { value: from.value };\n case 'ROW_FIELD':\n return {\n columnKey: from.columnKey,\n value: scope.rowValues?.[from.columnKey],\n };\n case 'EXPRESSION':\n return {\n expression: from.expression,\n form: omitKey(scope.formData, scope.ownFormKey),\n row: scope.rowValues ? omitKey(scope.rowValues, scope.ownRowKey) : null,\n };\n default:\n return readUnknownBindingRefreshInput(from);\n }\n}\n\nfunction readUnknownBindingRefreshInput(from: never): unknown {\n void from;\n return { unsupported: true };\n}\n\n/**\n * JSON with object keys sorted, so two forms holding the same values compare\n * equal whatever order their keys arrived in — a stored instance comes back\n * from the database in its own key order, the renderer builds its values in\n * schema order.\n */\nfunction stringifyStable(value: unknown): string {\n return JSON.stringify(sortObjectKeys(value)) ?? 'null';\n}\n\nfunction sortObjectKeys(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map(sortObjectKeys);\n }\n\n if (value !== null && typeof value === 'object') {\n return Object.fromEntries(\n Object.entries(value)\n .filter(([, entry]) => typeof entry !== 'undefined')\n .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))\n .map(([key, entry]) => [key, sortObjectKeys(entry)]),\n );\n }\n\n return value;\n}\n\n/**\n * The refresh signature a held value was last known good against: the one in\n * force when it was picked. A form without an `initialFormData` (a new case)\n * has nothing else to compare with.\n */\ninterface HeldValueBaseline {\n readonly bindings: string;\n readonly value: string;\n}\n\nexport function useFormDataSourceField(\n input: UseFormDataSourceFieldInput,\n): FormDataSourceFieldState {\n const dynamicField = isFormDataSourceFieldDefinition(input.field)\n ? input.field\n : null;\n const queryFieldKey = input.fieldPath ?? dynamicField?.fieldKey ?? '';\n const snapshot = dynamicField\n ? input.optionSnapshots?.[input.snapshotKey ?? dynamicField.fieldKey]\n : undefined;\n // The field object is rebuilt on every parent render, so depending on its\n // identity re-ran the load effect three extra times per change — each request\n // aborting the last. Depend on what actually decides the query instead.\n const dynamicFieldSignature = dynamicField\n ? JSON.stringify({\n dataSource: dynamicField.dataSource,\n fieldKey: dynamicField.fieldKey,\n type: dynamicField.type,\n })\n : '';\n const snapshotSignature = JSON.stringify(snapshot ?? null);\n const snapshotOptions = useMemo(\n (): readonly FormFieldOption[] => snapshot?.options ?? [],\n [snapshotSignature],\n );\n const formDataSignature = JSON.stringify(input.formData);\n const initialFormDataSignature = JSON.stringify(input.initialFormData ?? {});\n const schemaSignature = JSON.stringify(input.schema);\n const uiSchemaSignature = JSON.stringify(input.uiSchema);\n const contextKind = input.context?.kind;\n const contextInstanceId =\n input.context?.kind === 'runtime' ? input.context.instanceId : undefined;\n const contextTemplateId =\n input.context?.kind === 'runtime' ? input.context.templateId : undefined;\n const rowValuesSignature = JSON.stringify(input.row?.values ?? null);\n const initialRowValuesSignature = JSON.stringify(\n input.row?.initialValues ?? null,\n );\n // A cell's value lives on its row, never on the form data — a top-level field\n // may even share the column's key.\n const currentValue = dynamicField\n ? input.row\n ? input.row.values[dynamicField.fieldKey]\n : input.formData[dynamicField.fieldKey]\n : undefined;\n const currentValueSignature = readFormDataSourceValueSignature(currentValue);\n const selectedValues = useMemo(\n (): readonly string[] => readFormDataSourceSelectedValues(currentValue),\n [currentValueSignature],\n );\n const hasValue = selectedValues.length > 0;\n const bindingsRefreshSignature = dynamicField\n ? JSON.stringify(\n dynamicField.dataSource.bindings.map((binding) =>\n readBindingRefreshInput(binding, {\n formData: input.formData,\n ownFormKey: readOwnFormKey(dynamicField.fieldKey, input),\n ownRowKey: input.row ? dynamicField.fieldKey : null,\n rowValues: input.row?.values,\n }),\n ),\n )\n : '';\n const valueRefreshSignature = dynamicField\n ? JSON.stringify(\n typeof input.initialFormData !== 'undefined' ||\n typeof input.initialValue !== 'undefined'\n ? (currentValue ?? null)\n : null,\n )\n : '';\n // Every answer is tagged with this one, so a response to an older form\n // state is never taken for the current one.\n const dataSourceRefreshSignature = dynamicField\n ? `${bindingsRefreshSignature}\\u0000${valueRefreshSignature}`\n : '';\n // Only a field with an `EXPRESSION` binding waits; every other binding\n // re-queries at once, as before.\n const settledBindingsRefreshSignature = useSettledSignature(\n bindingsRefreshSignature,\n dynamicField?.dataSource.bindings.some(\n (binding) => binding.from.kind === 'EXPRESSION',\n )\n ? FORM_DATA_SOURCE_EXPRESSION_REFRESH_DEBOUNCE_MS\n : 0,\n );\n // While the form settles the options and any earlier answer describe an\n // older form: the field reports itself pending, so a value in it blocks\n // the submission until the re-query answers (and the host re-resolves\n // every dynamic value on submit regardless).\n const refreshSettling =\n settledBindingsRefreshSignature !== bindingsRefreshSignature;\n const effectiveContext = useMemo(\n (): FormRendererDataSourceContext | undefined => {\n if (contextKind === 'preview') {\n return { kind: 'preview' };\n }\n\n if (contextKind === 'runtime' && !input.readonly) {\n return {\n instanceId: contextInstanceId,\n kind: 'runtime',\n templateId: contextTemplateId,\n };\n }\n\n return undefined;\n },\n [\n contextInstanceId,\n contextKind,\n contextTemplateId,\n input.readonly,\n ],\n );\n const valueChanged = dynamicField\n ? (typeof input.initialFormData !== 'undefined' ||\n typeof input.initialValue !== 'undefined') &&\n readFormDataSourceValueSignature(input.initialValue) !==\n currentValueSignature\n : false;\n const bindingsChanged = dynamicField\n ? typeof input.initialFormData !== 'undefined' &&\n dynamicField.dataSource.bindings.some((binding) => {\n if (binding.from.kind === 'FIELD') {\n return (\n readFormDataSourceValueSignature(\n input.initialFormData?.[binding.from.fieldKey],\n ) !==\n readFormDataSourceValueSignature(\n input.formData[binding.from.fieldKey],\n )\n );\n }\n\n if (binding.from.kind === 'ROW_FIELD' && input.row?.initialValues) {\n return (\n readFormDataSourceValueSignature(\n input.row.initialValues[binding.from.columnKey],\n ) !==\n readFormDataSourceValueSignature(\n input.row.values[binding.from.columnKey],\n )\n );\n }\n\n // The browser cannot tell which fields an expression reads (it never\n // parses CEL, docs/24 §2.5), so any change to the rest of the form or\n // row may have changed its value: the kept value is re-resolved.\n //\n // Only where something can be re-resolved: a read-only field has no\n // query, so calling it stale would only print a warning nobody can\n // act on. Both sides go through the renderer's own value builder and\n // a key-order-free comparison, because the stored instance differs\n // from what the renderer holds in ways that are not edits — its key\n // order, the defaults the renderer fills in, keys outside the schema.\n if (binding.from.kind === 'EXPRESSION') {\n if (!effectiveContext) {\n return false;\n }\n\n const scope = {\n ownFormKey: readOwnFormKey(dynamicField.fieldKey, input),\n ownRowKey: input.row ? dynamicField.fieldKey : null,\n };\n\n return (\n stringifyStable(\n readBindingRefreshInput(binding, {\n ...scope,\n formData: buildFormRendererValues(\n input.schema.fields,\n input.initialFormData ?? {},\n ),\n rowValues: input.row?.initialValues ?? input.row?.values,\n }),\n ) !==\n stringifyStable(\n readBindingRefreshInput(binding, {\n ...scope,\n formData: buildFormRendererValues(\n input.schema.fields,\n input.formData,\n ),\n rowValues: input.row?.values,\n }),\n )\n );\n }\n\n return false;\n })\n : false;\n const hasExpressionBinding = Boolean(\n dynamicField?.dataSource.bindings.some(\n (binding) => binding.from.kind === 'EXPRESSION',\n ),\n );\n // Re-taken whenever the value itself changes (a pick, a clear), so it always\n // names the form the current value was chosen in. Kept in settled terms: a\n // value picked while the form was still settling was picked from options\n // for the older form, and is re-resolved once it settles.\n const [heldValueBaseline, setHeldValueBaseline] = useState<HeldValueBaseline>(\n (): HeldValueBaseline => ({\n bindings: settledBindingsRefreshSignature,\n value: currentValueSignature,\n }),\n );\n\n if (heldValueBaseline.value !== currentValueSignature) {\n setHeldValueBaseline({\n bindings: settledBindingsRefreshSignature,\n value: currentValueSignature,\n });\n }\n\n // A `FIELD` binding's stale value is caught by the host when the case is\n // submitted (`FORM_DATA_SOURCE_VALUE_NOT_RESOLVED`). An `EXPRESSION` one is\n // re-resolved here instead, once the form settles: its options are only\n // re-queried after the debounce, and without a resolve nothing would ever\n // say whether the held value survived the change. Compared with the settled\n // signature, so the status (and with it the query effect) does not move\n // before the debounce has passed.\n const expressionInputsChanged = Boolean(\n hasExpressionBinding &&\n effectiveContext &&\n hasValue &&\n heldValueBaseline.value === currentValueSignature &&\n heldValueBaseline.bindings !== settledBindingsRefreshSignature,\n );\n const isStale = Boolean(\n (snapshot || hasValue) &&\n (valueChanged || bindingsChanged || expressionInputsChanged),\n );\n // A stale field with a value is only trustworthy once the host has resolved\n // it: display options may still carry the value through the snapshot merge.\n const needsResolve = Boolean(dynamicField && effectiveContext && isStale && hasValue);\n const quiescentStatus = readQuiescentFormDataSourceStatus({\n hasSnapshot: Boolean(snapshot),\n hasValue,\n isStale,\n readonly: input.readonly,\n });\n // AutoComplete only queries on search, so every other control is the one that\n // loads by itself as soon as it has a context.\n const autoQueries = Boolean(\n dynamicField && effectiveContext && dynamicField.type !== 'autocomplete',\n );\n // Optimistic, and only until the first answer arrives: a binding pointing at\n // an empty field is usually blocking, and showing the control as usable for\n // one round trip would flash an enabled, empty list. AutoComplete is excluded\n // because nothing would ever correct the guess — it cannot be searched while\n // disabled, so an optional binding would lock it for good.\n const waitsForDependencies =\n autoQueries &&\n dynamicField !== null &&\n readMissingFormDataSourceDependencies(\n dynamicField,\n input.formData,\n input.row?.values,\n ).length > 0;\n const pendingQueryStatus = readPendingQueryStatus(isStale, waitsForDependencies);\n const quiescentStatusRef = useRef(quiescentStatus);\n quiescentStatusRef.current = quiescentStatus;\n const [remoteOptions, setRemoteOptions] = useState<readonly FormFieldOption[]>(\n [],\n );\n const [searchText, setSearchText] = useState('');\n const [nextCursor, setNextCursor] = useState<string | null>(null);\n const [resolution, setResolution] = useState<FormDataSourceResolution | null>(\n null,\n );\n const [status, setStatus] = useState<FormDataSourceFieldStatus>(\n (): FormDataSourceFieldStatus => {\n if (!dynamicField) {\n return 'IDLE';\n }\n\n // The very first paint already reflects the query the effect is about to\n // fire, so the control never appears usable before its options exist.\n return autoQueries ? pendingQueryStatus : quiescentStatus;\n },\n );\n const [error, setError] = useState<string | null>(null);\n const [loadingNextPage, setLoadingNextPage] = useState(false);\n const loadedOptionCount = useRef(0);\n const requestSequence = useRef(0);\n const resolveSequence = useRef(0);\n const optionsAbort = useRef<AbortController | null>(null);\n const resolveAbort = useRef<AbortController | null>(null);\n // Whether anything was queried since the options were last reset, so that\n // opening a search-only control loads its first page exactly once.\n const queriedSinceReset = useRef(false);\n const lastRequest = useRef<{ readonly cursor: string | null; readonly searchText: string }>({\n cursor: null,\n searchText: '',\n });\n\n const queryFormData = useMemo(\n (): Readonly<Record<string, FormFieldValue>> =>\n readDefinedValues(input.formData),\n [formDataSignature],\n );\n const queryRowValues = useMemo(\n (): Readonly<Record<string, FormFieldValue>> | undefined =>\n input.row ? readDefinedValues(input.row.values) : undefined,\n [rowValuesSignature],\n );\n const mergedOptions = useMemo(\n (): readonly FormFieldOption[] =>\n mergeFormDataSourceOptions(snapshotOptions, remoteOptions),\n [remoteOptions, snapshotOptions],\n );\n const currentResolution =\n resolution &&\n resolution.signature === dataSourceRefreshSignature &&\n resolution.valueSignature === currentValueSignature\n ? resolution\n : null;\n const resolvePending = needsResolve && !currentResolution;\n const selectionExpired = Boolean(\n expressionInputsChanged &&\n currentResolution &&\n currentResolution.unresolvedValues.length > 0,\n );\n const invalidValues = readInvalidFormDataSourceValues({\n options: mergedOptions,\n resolution: currentResolution,\n resolvePending,\n value: currentValue,\n });\n\n const loadOptions = useCallback(\n (nextSearchTextToLoad: string, cursor: string | null): void => {\n if (!dynamicField || !effectiveContext) {\n return;\n }\n\n const requestId = requestSequence.current + 1;\n requestSequence.current = requestId;\n queriedSinceReset.current = true;\n lastRequest.current = {\n cursor,\n searchText: nextSearchTextToLoad,\n };\n optionsAbort.current?.abort();\n const controller = new AbortController();\n optionsAbort.current = controller;\n setStatus(pendingQueryStatus);\n setLoadingNextPage(cursor !== null);\n setError(null);\n\n const request =\n effectiveContext.kind === 'preview'\n ? previewFormFieldOptions({\n cursor,\n fieldKey: queryFieldKey,\n formData: queryFormData,\n rowValues: queryRowValues,\n schema: input.schema,\n searchText: nextSearchTextToLoad,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n })\n : readFormFieldOptions({\n cursor,\n fieldKey: queryFieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\n rowValues: queryRowValues,\n searchText: nextSearchTextToLoad,\n signal: controller.signal,\n templateId: effectiveContext.templateId ?? null,\n });\n\n void request\n .then((result): void => {\n if (isSupersededRequest(controller, requestSequence, requestId)) {\n return;\n }\n\n setLoadingNextPage(false);\n setRemoteOptions((currentOptions) => {\n const selectedOptions = readSelectedFormDataSourceOptions(\n currentValue,\n currentOptions,\n );\n\n return mergeFormDataSourceOptions(\n selectedOptions,\n cursor ? currentOptions : [],\n result.options,\n );\n });\n setNextCursor(result.nextCursor);\n setError(null);\n loadedOptionCount.current = cursor\n ? loadedOptionCount.current + result.options.length\n : result.options.length;\n\n // An empty page that still hands back a cursor would loop forever.\n if (\n result.nextCursor &&\n result.options.length > 0 &&\n loadedOptionCount.current < MIN_SCROLLABLE_OPTION_COUNT\n ) {\n loadOptionsRef.current(nextSearchTextToLoad, result.nextCursor);\n }\n\n // Only the host knows which bound parameters are required, so a\n // control is disabled on its answer instead of on a browser guess.\n setStatus(\n result.waitingForFieldKeys.length\n ? 'WAITING_FOR_DEPENDENCIES'\n : 'VALID',\n );\n })\n .catch((requestError: unknown): void => {\n if (isSupersededRequest(controller, requestSequence, requestId)) {\n return;\n }\n\n // Anything that is not a DataSource code — a transport failure, or a\n // server-side error the host deliberately reduced to a generic\n // message — must not be rendered verbatim; it is English technical\n // copy the filler cannot act on.\n // A failed first page leaves nothing loaded, so the next open retries\n // it; a failed later page keeps what is already listed.\n if (cursor === null) {\n queriedSinceReset.current = false;\n }\n setLoadingNextPage(false);\n\n const message =\n readFormDataSourceErrorMessage(requestError) ??\n '選項來源暫時無法使用。';\n const code = readFormDataSourceErrorCode(requestError);\n\n setError(message);\n setStatus(\n readFailedQueryStatus(\n Boolean(code && RECOVERABLE_ERROR_CODES.includes(code)),\n hasValue,\n ),\n );\n });\n },\n [\n currentValueSignature,\n dynamicFieldSignature,\n effectiveContext,\n hasValue,\n pendingQueryStatus,\n queryFieldKey,\n queryFormData,\n queryRowValues,\n schemaSignature,\n uiSchemaSignature,\n ],\n );\n const loadOptionsRef = useRef(loadOptions);\n loadOptionsRef.current = loadOptions;\n\n const runResolve = useCallback((): void => {\n if (!dynamicField || !effectiveContext || selectedValues.length === 0) {\n return;\n }\n\n const signature = dataSourceRefreshSignature;\n const valueSignature = currentValueSignature;\n const requestId = resolveSequence.current + 1;\n resolveSequence.current = requestId;\n resolveAbort.current?.abort();\n const controller = new AbortController();\n resolveAbort.current = controller;\n setError(null);\n\n const request =\n effectiveContext.kind === 'preview'\n ? previewResolveFormFieldOptions({\n fieldKey: queryFieldKey,\n formData: queryFormData,\n rowValues: queryRowValues,\n schema: input.schema,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n values: selectedValues,\n })\n : resolveFormFieldOptions({\n fieldKey: queryFieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\n rowValues: queryRowValues,\n signal: controller.signal,\n templateId: effectiveContext.templateId ?? null,\n values: selectedValues,\n });\n\n void request\n .then((result): void => {\n if (isSupersededRequest(controller, resolveSequence, requestId)) {\n return;\n }\n\n if (result.waitingForFieldKeys.length) {\n // The host could not consult the provider, so this is no answer about\n // the value: leave the field waiting rather than call it resolved.\n setStatus('WAITING_FOR_DEPENDENCIES');\n\n return;\n }\n\n // Authoritative labels for the values that survived, merged so the old\n // snapshot label of an unresolved value stays visible for recognition.\n setRemoteOptions((currentOptions) =>\n mergeFormDataSourceOptions(currentOptions, result.options),\n );\n setResolution({\n signature,\n unresolvedValues: result.unresolvedValues,\n valueSignature,\n });\n // `invalidValues` turns this into `INVALID` when anything went missing.\n setStatus('VALID');\n })\n .catch((requestError: unknown): void => {\n if (isSupersededRequest(controller, resolveSequence, requestId)) {\n return;\n }\n\n setStatus('UNAVAILABLE');\n setError(\n readFormDataSourceErrorMessage(requestError) ??\n '選項來源暫時無法使用。',\n );\n });\n }, [\n currentValueSignature,\n dataSourceRefreshSignature,\n dynamicFieldSignature,\n effectiveContext,\n queryFieldKey,\n queryFormData,\n queryRowValues,\n schemaSignature,\n selectedValues,\n uiSchemaSignature,\n ]);\n const runResolveRef = useRef(runResolve);\n runResolveRef.current = runResolve;\n\n useEffect((): void => {\n requestSequence.current += 1;\n queriedSinceReset.current = false;\n optionsAbort.current?.abort();\n setLoadingNextPage(false);\n setRemoteOptions((currentOptions) =>\n readSelectedFormDataSourceOptions(currentValue, currentOptions),\n );\n setNextCursor((currentCursor) =>\n currentCursor === null ? currentCursor : null,\n );\n setSearchText((currentSearchText) =>\n currentSearchText ? '' : currentSearchText,\n );\n setError((currentError) => (currentError ? null : currentError));\n\n if (!dynamicField) {\n setStatus('IDLE');\n\n return;\n }\n\n if (!autoQueries) {\n // AutoComplete queries on search only, so it stays quiescent until the\n // filler types; a context-less field has nothing to query at all.\n setStatus(quiescentStatusRef.current);\n\n return;\n }\n\n loadOptionsRef.current('', null);\n // The pending and quiescent statuses are read, not depended on: both\n // follow `isStale`, which flips when the filler picks a new value for an\n // expired `EXPRESSION` selection — and a pick must never re-query the\n // options (the field's own key is left out of its refresh input for that\n // reason). Re-querying there left the field `LOADING` right after the\n // pick, so an immediate submit was refused with the \"wait\" copy.\n }, [\n autoQueries,\n dynamicFieldSignature,\n effectiveContext,\n settledBindingsRefreshSignature,\n valueRefreshSignature,\n initialFormDataSignature,\n initialRowValuesSignature,\n input.readonly,\n snapshotSignature,\n ]);\n\n // A field nobody queries still has to follow its quiescent status (a value\n // turning stale, or no longer stale), which the effect above now only reads.\n useEffect((): void => {\n if (dynamicField && !autoQueries) {\n setStatus(quiescentStatus);\n }\n }, [autoQueries, dynamicFieldSignature, quiescentStatus]);\n\n useEffect((): void => {\n // An `EXPRESSION` field resolves only once the form has settled, like its\n // option re-query; every other binding settles at once.\n if (!needsResolve || refreshSettling) {\n return;\n }\n\n runResolveRef.current();\n }, [\n effectiveContext,\n needsResolve,\n refreshSettling,\n settledBindingsRefreshSignature,\n valueRefreshSignature,\n ]);\n\n useEffect((): (() => void) => {\n return (): void => {\n optionsAbort.current?.abort();\n resolveAbort.current?.abort();\n };\n }, []);\n\n const onSearch = useCallback(\n (nextSearchTextToLoad: string): void => {\n setSearchText(nextSearchTextToLoad);\n setRemoteOptions((currentOptions) =>\n readSelectedFormDataSourceOptions(currentValue, currentOptions),\n );\n setNextCursor(null);\n loadOptions(nextSearchTextToLoad, null);\n },\n [loadOptions],\n );\n const onOpen = useCallback((): void => {\n // A field that loads by itself, or that cannot query, has nothing to do.\n if (autoQueries || !effectiveContext || input.readonly) {\n return;\n }\n\n // `clearSearchText` empties the input when the menu closes, so a list\n // still filtered by the last search would no longer match what is shown.\n if (!queriedSinceReset.current || searchText !== '') {\n onSearch('');\n }\n }, [autoQueries, effectiveContext, input.readonly, onSearch, searchText]);\n // Read through a ref so the callback can keep one identity. The menu's\n // scrollbar keeps the handler it was given when the menu first rendered;\n // a control that only queries on open (AutoComplete) renders that menu\n // before its first page lands, so a handler closing over `nextCursor` would\n // still see `null` when the list is scrolled to the bottom.\n const nextPageRequest = useRef({ cursor: nextCursor, searchText });\n nextPageRequest.current = { cursor: nextCursor, searchText };\n const onReachBottom = useCallback((): void => {\n const { cursor, searchText: pageSearchText } = nextPageRequest.current;\n\n if (cursor) {\n loadOptionsRef.current(pageSearchText, cursor);\n }\n }, []);\n const retry = useCallback((): void => {\n if (!effectiveContext) {\n return;\n }\n\n loadOptions(lastRequest.current.searchText, lastRequest.current.cursor);\n\n if (needsResolve) {\n runResolveRef.current();\n }\n }, [effectiveContext, loadOptions, needsResolve]);\n\n if (!dynamicField) {\n return EMPTY_FIELD_STATE;\n }\n\n return {\n canRetry: Boolean(effectiveContext),\n error,\n hasValue,\n hasNextPage: Boolean(nextCursor),\n loadingNextPage,\n invalidValues,\n onOpen,\n onReachBottom,\n onSearch,\n options: mergedOptions,\n retry,\n selectionExpired,\n status:\n refreshSettling && effectiveContext && (autoQueries || hasValue)\n ? autoQueries\n ? pendingQueryStatus\n : 'STALE'\n : readFormDataSourceExposedStatus({\n invalidValues,\n resolvePending,\n status,\n }),\n };\n}\n\n/**\n * `signature` once it has stayed the same for `delayMs`; with no delay, the\n * signature itself. The first value is taken at once, so a field still loads\n * on its first paint.\n */\nfunction useSettledSignature(signature: string, delayMs: number): string {\n const [settled, setSettled] = useState(signature);\n\n useEffect((): (() => void) | undefined => {\n if (delayMs <= 0) {\n return undefined;\n }\n\n const timer = setTimeout((): void => {\n setSettled(signature);\n }, delayMs);\n\n return (): void => {\n clearTimeout(timer);\n };\n }, [delayMs, signature]);\n\n return delayMs > 0 ? settled : signature;\n}\n\nexport function readFormDataSourceFieldStatusMessage(\n state: FormDataSourceFieldState,\n): string | null {\n switch (state.status) {\n case 'WAITING_FOR_DEPENDENCIES':\n return '請先填寫相依欄位。';\n case 'STALE':\n return '選項需要重新載入或驗證。';\n case 'INVALID':\n if (state.selectionExpired) {\n // The host's own copy for the code, not a second wording of it.\n return readFormDataSourceErrorMessage(\n 'FORM_DATA_SOURCE_VALUE_NOT_RESOLVED',\n );\n }\n\n return state.invalidValues.length\n ? `無法辨識選項:${state.invalidValues.join('、')}`\n : '目前選項已失效,請重新選擇。';\n case 'UNAVAILABLE':\n return state.error ?? '選項來源暫時無法使用。';\n case 'VALID':\n // A recoverable search hint outranks the empty-result copy: it is what\n // tells the filler how to get results at all.\n return state.error ?? (state.options.length ? null : '目前沒有可用選項。');\n default:\n return state.error;\n }\n}\n\nexport function isFormDataSourceFieldSubmissionBlocked(\n state: Pick<FormDataSourceFieldState, 'hasValue' | 'status'>,\n): boolean {\n return state.hasValue && state.status !== 'VALID';\n}\n\n/**\n * The copy to show when a submission is refused because of dynamic options.\n * `LOADING` and `STALE` clear on their own once the pending query answers, so\n * saying \"finish validating\" reads as a dead end for what is really a \"wait a\n * moment\"; the caller would otherwise show the same permanent-sounding line for\n * both cases.\n *\n * Returns `null` when nothing blocks the submission.\n */\nexport function readFormDataSourceSubmissionBlockMessage(\n states: readonly Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'selectionExpired' | 'status'\n >[],\n): string | null {\n const blocked = states.filter(isFormDataSourceFieldSubmissionBlocked);\n\n if (blocked.length === 0) {\n return null;\n }\n\n // An expired selection reads the same as the field (and as a FIELD binding\n // the server refuses): the host's copy for FORM_DATA_SOURCE_VALUE_NOT_RESOLVED.\n if (\n blocked.some(\n (state) => state.status === 'INVALID' && state.selectionExpired === true,\n )\n ) {\n return readFormDataSourceErrorMessage('FORM_DATA_SOURCE_VALUE_NOT_RESOLVED');\n }\n\n return blocked.every(\n (state) => state.status === 'LOADING' || state.status === 'STALE',\n )\n ? '選項驗證中,請稍候再送出。'\n : '請先完成動態選項驗證。';\n}\n\n/**\n * The values the field holds that no option accounts for.\n *\n * A completed resolve is the authority — the loaded pages are not, because the\n * option snapshot is merged into them for display and would vouch for a value\n * the source has already dropped.\n */\nfunction readInvalidFormDataSourceValues(input: {\n readonly options: readonly FormFieldOption[];\n readonly resolution: FormDataSourceResolution | null;\n readonly resolvePending: boolean;\n readonly value: FormFieldValue | undefined;\n}): readonly string[] {\n if (input.resolution) {\n return input.resolution.unresolvedValues;\n }\n\n // While the host is being asked, its answer is the only one worth showing.\n if (input.resolvePending) {\n return NO_INVALID_VALUES;\n }\n\n return readMissingFormDataSourceOptionValues(input.value, input.options);\n}\n\n/**\n * Status of a field nobody is querying — read-only history, a Designer preview\n * before the first search, or a control the host gave no context for.\n */\nfunction readQuiescentFormDataSourceStatus(input: {\n readonly hasSnapshot: boolean;\n readonly hasValue: boolean;\n readonly isStale: boolean;\n readonly readonly: boolean;\n}): FormDataSourceFieldStatus {\n if (input.isStale) {\n return 'STALE';\n }\n\n if (input.hasSnapshot) {\n return 'VALID';\n }\n\n // A read-only value with no snapshot cannot be labelled from anywhere, which\n // is worth saying. An empty read-only field is simply blank — warning about a\n // source nobody is going to query would be noise.\n return input.readonly && input.hasValue ? 'UNAVAILABLE' : 'IDLE';\n}\n\n/**\n * Status held while a query is in flight.\n *\n * `waiting` is the browser's optimistic guess and only survives until the host\n * answers: `waitingForFieldKeys` from the response replaces it either way, so an\n * optional binding left empty clears within one round trip instead of locking\n * the control.\n */\nfunction readPendingQueryStatus(\n isStale: boolean,\n waiting: boolean,\n): FormDataSourceFieldStatus {\n if (waiting) {\n return 'WAITING_FOR_DEPENDENCIES';\n }\n\n return isStale ? 'STALE' : 'LOADING';\n}\n\n/**\n * Status after a rejected query. A recoverable failure leaves the field as\n * usable as it was — only a broken source makes it unavailable.\n */\nfunction readFailedQueryStatus(\n recoverable: boolean,\n hasValue: boolean,\n): FormDataSourceFieldStatus {\n if (!recoverable) {\n return 'UNAVAILABLE';\n }\n\n return hasValue ? 'VALID' : 'IDLE';\n}\n\nfunction readFormDataSourceExposedStatus(input: {\n readonly invalidValues: readonly string[];\n readonly resolvePending: boolean;\n readonly status: FormDataSourceFieldStatus;\n}): FormDataSourceFieldStatus {\n if (\n input.status === 'LOADING' ||\n input.status === 'UNAVAILABLE' ||\n input.status === 'WAITING_FOR_DEPENDENCIES'\n ) {\n return input.status;\n }\n\n if (input.invalidValues.length) {\n return 'INVALID';\n }\n\n return input.resolvePending ? 'STALE' : input.status;\n}\n\n/**\n * True when a response no longer belongs to the field: its request was aborted\n * by a newer one, or a newer one started before it settled. Such a response\n * must not touch state — an aborted request is not a failure to report.\n */\nfunction isSupersededRequest(\n controller: AbortController,\n sequence: { readonly current: number },\n requestId: number,\n): boolean {\n return controller.signal.aborted || sequence.current !== requestId;\n}\n\nfunction readDefinedValues(\n values: FormRendererValues,\n): Readonly<Record<string, FormFieldValue>> {\n return Object.entries(values).reduce<\n Readonly<Record<string, FormFieldValue>>\n >((defined, [key, value]) => {\n if (typeof value === 'undefined') {\n return defined;\n }\n\n return { ...defined, [key]: value };\n }, {});\n}\n","import type { FormFieldValue } from '@rytass/bpm-core-shared/form';\n\n/**\n * A multi-value field's ids or option values, or `[]` for anything else.\n * A table value is also an array, so the element check is what separates a\n * multi-select or `file_upload` value from table rows.\n */\nexport function readStringArrayValue(\n value: FormFieldValue | undefined,\n): readonly string[] {\n return Array.isArray(value) &&\n value.every((item): boolean => typeof item === 'string')\n ? (value as readonly string[])\n : [];\n}\n","'use client';\n\nimport {\n ReactElement,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { Upload } from '@mezzanine-ui/react';\nimport type { UploadFile } from '@mezzanine-ui/react/Upload';\nimport type {\n FileUploadFieldDefinition,\n FormFieldDefinition,\n FormFieldValue,\n} from '@rytass/bpm-core-shared/form';\nimport { readStringArrayValue } from './form-value-readers';\n\n/**\n * What a `file_upload` field needs to know about an attachment it did not\n * upload itself (a resubmitted case, a stored instance) to list it. The shape\n * is a subset of `AttachmentRecord`, so a host passes its attachment list as is.\n */\nexport interface FormRendererAttachment {\n readonly filename: string;\n readonly id: string;\n readonly sizeBytes: number | string;\n}\n\n/**\n * Where a `file_upload` field stands, reported to the host through\n * `FormRenderer`'s `onFileUploadStateChange`.\n */\nexport interface FileUploadFieldState {\n /** Files that failed to upload. They are listed, but not in the value. */\n readonly failedCount: number;\n /** Files still uploading. They are listed, but not in the value yet. */\n readonly pendingCount: number;\n}\n\n/**\n * The copy to show when a submission is refused because a `file_upload` field\n * has not settled. An upload still running is a \"wait a moment\"; a failed one\n * needs the filler to retry or remove it. Returns `null` when nothing blocks.\n *\n * Without this check a submit made while a file is uploading sends the form\n * without that attachment, although the field lists the file.\n */\nexport function readFileUploadSubmissionBlockMessage(\n states: readonly Pick<FileUploadFieldState, 'failedCount' | 'pendingCount'>[],\n): string | null {\n if (states.some((state): boolean => state.pendingCount > 0)) {\n return '附件仍在上傳中,請稍候再送出。';\n }\n\n if (states.some((state): boolean => state.failedCount > 0)) {\n return '有附件上傳失敗,請重試或移除後再送出。';\n }\n\n return null;\n}\n\nexport interface FileUploadFieldStates {\n /** `readFileUploadSubmissionBlockMessage` over every field reported so far. */\n readonly blockMessage: string | null;\n /** Pass to `FormRenderer`'s `onFileUploadStateChange`. */\n readonly onFileUploadStateChange: (\n fieldKey: string,\n state: FileUploadFieldState,\n ) => void;\n}\n\n/**\n * Collects what a form's `file_upload` fields report, for a host that has to\n * refuse a submit while one of them is uploading or holds a failed upload.\n */\nexport function useFileUploadFieldStates(): FileUploadFieldStates {\n const [states, setStates] = useState<\n Readonly<Record<string, FileUploadFieldState>>\n >({});\n const onFileUploadStateChange = useCallback(\n (fieldKey: string, state: FileUploadFieldState): void =>\n setStates((current): Readonly<Record<string, FileUploadFieldState>> => ({\n ...current,\n [fieldKey]: state,\n })),\n [],\n );\n return {\n blockMessage: readFileUploadSubmissionBlockMessage(Object.values(states)),\n onFileUploadStateChange,\n };\n}\n\nexport interface FileUploadFieldControlProps {\n /** Metadata for listing attachments this control did not upload. */\n readonly attachments?: readonly FormRendererAttachment[];\n readonly field: FileUploadFieldDefinition;\n /**\n * Receives the attachment ids the field now holds. Like a controlled input,\n * the host renders the value back: the list shows the value it is given.\n */\n readonly onChange: (value: FormFieldValue | undefined) => void;\n /** Opens an attachment the value holds. Without it, only files picked here download. */\n readonly onDownloadAttachment?: (attachmentId: string) => void;\n readonly onStateChange?: (state: FileUploadFieldState) => void;\n readonly onUploadAttachment?: (\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>;\n readonly readonly: boolean;\n readonly value: FormFieldValue | undefined;\n}\n\nconst UNKNOWN_ATTACHMENT_NAME = '無法讀取檔名的附件';\nconst UPLOAD_UNAVAILABLE_HINT = '此處無法上傳附件';\n\n/**\n * The `file_upload` control.\n *\n * Mezzanine's `Upload` is fully controlled: it renders `files` and resets its\n * working list to that prop after every render, so the list is built here.\n *\n * What is attached is the host's value, and only that: every id it holds is\n * listed — a file picked here by its own `File`, any other id by the name and\n * size in `attachments` (or as an unnamed attachment without them) — so each\n * one can be removed and counts towards `maxFiles`. Uploads still running and\n * uploads that failed are listed too, in the order `Upload` keeps, but are not\n * in the value; `onStateChange` reports them so the host can refuse a submit.\n *\n * The value emitted on a change is read off the list `Upload` hands back —\n * the finished files in it, in order — never off this control's state. That\n * list is `Upload`'s own working copy, which it updates the moment it emits,\n * so it is current even when two uploads finish before the host re-renders.\n * `Upload` calls the `onChange` it captured when a file was picked, so it is\n * given a stable function that forwards to the latest handler.\n *\n * Without `onUploadAttachment` nothing can be uploaded, so the control is\n * disabled and says so rather than listing a file no attachment stands behind.\n */\nexport function FileUploadFieldControl({\n attachments,\n field,\n onChange,\n onDownloadAttachment,\n onStateChange,\n onUploadAttachment,\n readonly,\n value,\n}: FileUploadFieldControlProps): ReactElement {\n // Uploads running or failed; they are listed but not attached.\n const [pendingFiles, setPendingFiles] = useState<readonly UploadFile[]>([]);\n // Files picked here that finished, by attachment id: they keep their bytes.\n const [ownFiles, setOwnFiles] = useState<ReadonlyMap<string, UploadFile>>(\n () => new Map(),\n );\n // The order `Upload` keeps its list in (picked order), by id.\n const [listOrder, setListOrder] = useState<readonly string[]>([]);\n const valueIds = readStringArrayValue(value);\n const valueKey = valueIds.join('\\n');\n const attachmentsById = useMemo(\n (): ReadonlyMap<string, FormRendererAttachment> =>\n new Map(\n (attachments ?? []).map(\n (attachment): [string, FormRendererAttachment] => [\n attachment.id,\n attachment,\n ],\n ),\n ),\n [attachments],\n );\n const files = useMemo((): UploadFile[] => {\n const position = new Map(\n listOrder.map((id, index): [string, number] => [id, index]),\n );\n const listed = [\n ...valueIds.map(\n (id): UploadFile =>\n ownFiles.get(id) ?? createListedFile(id, attachmentsById.get(id)),\n ),\n ...pendingFiles,\n ];\n\n // Stable: what `Upload` has never listed keeps its place at the front.\n return [...listed].sort(\n (left, right): number =>\n (position.get(left.id) ?? -1) - (position.get(right.id) ?? -1),\n );\n // `valueKey` stands for `valueIds`, a new array on every render.\n }, [attachmentsById, listOrder, ownFiles, pendingFiles, valueKey]);\n // `Upload`'s working list as of now: what it last emitted, or `files` once\n // it has taken them in. Only a retry, which `Upload` does not run itself,\n // needs it.\n const uploadListRef = useRef<readonly UploadFile[]>(files);\n const mountedRef = useRef(true);\n const handleChangeRef = useRef(handleChange);\n\n handleChangeRef.current = handleChange;\n\n useEffect((): void => {\n uploadListRef.current = files;\n }, [files]);\n\n const stableOnChange = useCallback(\n (nextFiles: UploadFile[]): void => handleChangeRef.current(nextFiles),\n [],\n );\n const pendingCount = pendingFiles.filter(\n (file): boolean => file.status === 'loading',\n ).length;\n const failedCount = pendingFiles.filter(\n (file): boolean => file.status === 'error',\n ).length;\n const onStateChangeRef = useRef(onStateChange);\n\n onStateChangeRef.current = onStateChange;\n\n useEffect((): void => {\n onStateChangeRef.current?.({ failedCount, pendingCount });\n }, [failedCount, pendingCount]);\n\n // A field that unmounts (hidden by a condition) stops blocking a submit and\n // ignores uploads that finish afterwards: the field is no longer the form's.\n useEffect((): (() => void) => {\n mountedRef.current = true;\n\n return (): void => {\n mountedRef.current = false;\n onStateChangeRef.current?.({ failedCount: 0, pendingCount: 0 });\n };\n }, []);\n\n function handleChange(nextFiles: readonly UploadFile[]): void {\n if (!mountedRef.current) {\n return;\n }\n\n const finished = nextFiles.filter(\n (file): boolean => file.status === 'done',\n );\n const nextIds = finished.map((file): string => file.id);\n const attachedIds = new Set(valueIds);\n const justUploaded = finished.filter(\n (file): boolean => file.file !== undefined && !attachedIds.has(file.id),\n );\n\n uploadListRef.current = nextFiles;\n setListOrder(nextFiles.map((file): string => file.id));\n setPendingFiles(\n nextFiles.filter((file): boolean => file.status !== 'done'),\n );\n\n if (justUploaded.length > 0) {\n setOwnFiles(\n (current): ReadonlyMap<string, UploadFile> =>\n new Map([\n ...current,\n ...justUploaded.map((file): [string, UploadFile] => [\n file.id,\n file,\n ]),\n ]),\n );\n }\n\n // A file picked but still uploading changes the list, not the value.\n if (nextIds.join('\\n') !== valueKey) {\n onChange(nextIds.length ? nextIds : undefined);\n }\n }\n\n async function uploadFile(file: File): Promise<UploadFile> {\n if (!onUploadAttachment) {\n throw new Error(UPLOAD_UNAVAILABLE_HINT);\n }\n\n const uploaded = await onUploadAttachment(field, file);\n\n return { file, id: uploaded.id, progress: 100, status: 'done' };\n }\n\n async function handleReload(fileId: string, file: File): Promise<void> {\n // `Upload` has already listed the file as uploading again.\n const replacement = await uploadFile(file).catch(\n (error: unknown): UploadFile => ({\n errorMessage: readUploadErrorMessage(error),\n file,\n id: fileId,\n status: 'error',\n }),\n );\n\n handleChangeRef.current(\n uploadListRef.current.map((entry): UploadFile =>\n entry.id === fileId ? replacement : entry,\n ),\n );\n }\n\n function handleDownload(fileId: string, file: File): void {\n // A file picked here holds the bytes that were uploaded.\n if (ownFiles.has(fileId)) {\n downloadLocalFile(file);\n\n return;\n }\n\n // A listed attachment's `File` is a name and size only; its content is\n // the host's to fetch, and without a host handler there is none to give.\n onDownloadAttachment?.(fileId);\n }\n\n const canUpload = Boolean(onUploadAttachment) && !readonly;\n // `Upload` shows a file's `errorMessage` on picture cards only; in a list a\n // failed file would look like an uploaded one but for its retry icon.\n const hints = [\n ...(!readonly && !onUploadAttachment\n ? [{ label: UPLOAD_UNAVAILABLE_HINT, type: 'info' as const }]\n : []),\n ...pendingFiles\n .filter((file): boolean => file.status === 'error')\n .map((file): { label: string; type: 'error' } => ({\n label: `${file.file?.name ?? '檔案'}:${\n file.errorMessage ?? '上傳失敗'\n },未附加。請重試或刪除。`,\n type: 'error',\n })),\n ];\n\n return (\n <Upload\n accept={field.acceptedMimeTypes?.join(',')}\n disabled={!canUpload}\n files={files}\n {...(hints.length > 0 ? { hints } : {})}\n maxFiles={field.maxFiles}\n mode=\"button-list\"\n {...((field.maxFiles ?? 1) > 1 ? { multiple: true } : {})}\n onChange={stableOnChange}\n onDownload={handleDownload}\n onReload={(fileId, file): void => {\n void handleReload(fileId, file);\n }}\n onUpload={async (selectedFiles): Promise<UploadFile[]> =>\n Promise.all(\n selectedFiles.map(async (file): Promise<UploadFile> =>\n uploadFile(file).catch((error: unknown): UploadFile => ({\n errorMessage: readUploadErrorMessage(error),\n file,\n // `Upload` maps results by position, so the id only has to\n // be unique within this list.\n id: `failed-${file.name}-${file.lastModified}-${Math.random()}`,\n status: 'error',\n })),\n ),\n )\n }\n size=\"sub\"\n />\n );\n}\n\n/**\n * An attachment the value holds but this control did not upload. With\n * metadata it is listed by name and size through a content-less `File`;\n * without, by a placeholder name only.\n */\nfunction createListedFile(\n id: string,\n attachment: FormRendererAttachment | undefined,\n): UploadFile {\n return attachment\n ? { file: createDisplayFile(attachment), id, progress: 100, status: 'done' }\n : {\n // Listed by name only: a `url` with no size or content behind it.\n id,\n progress: 100,\n status: 'done',\n url: UNKNOWN_ATTACHMENT_NAME,\n };\n}\n\n/**\n * A content-less `File` that shows the attachment's real name and size. Its\n * type is left empty on purpose: an image type would make `Upload` render a\n * thumbnail from these empty bytes, a broken image.\n */\nfunction createDisplayFile(attachment: FormRendererAttachment): File {\n const file = new File([], attachment.filename);\n const size = Number(attachment.sizeBytes);\n\n if (Number.isFinite(size) && size >= 0) {\n Object.defineProperty(file, 'size', { value: size });\n }\n\n return file;\n}\n\nfunction readUploadErrorMessage(error: unknown): string {\n return error instanceof Error && error.message\n ? `上傳失敗:${error.message}`\n : '上傳失敗,請重試。';\n}\n\nfunction downloadLocalFile(file: File): void {\n const url = URL.createObjectURL(file);\n const anchor = document.createElement('a');\n\n anchor.href = url;\n anchor.download = file.name;\n anchor.style.display = 'none';\n // Some browsers only download through an anchor in the document, and start\n // the download after `click` returns, so the URL outlives the click.\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n setTimeout((): void => URL.revokeObjectURL(url), 30_000);\n}\n","export interface FormFieldLink {\n /** Absolute links open in a new tab; site-relative ones stay in this tab. */\n readonly external: boolean;\n readonly href: string;\n}\n\nconst UNICODE_WHITESPACE_PATTERN = /\\s/u;\n\n// Whitespace and control characters are refused anywhere in the value: URL\n// parsers silently drop tabs and newlines, so \"/\\t/evil.example\" would turn\n// into the protocol-relative \"//evil.example\" after our `//` check passed.\nfunction hasUnsafeCharacter(value: string): boolean {\n return [...value].some((character): boolean => {\n const codePoint = character.codePointAt(0) ?? 0;\n\n return (\n // C0 controls and the space, DEL, C1 controls and the no-break space.\n codePoint <= 0x20 ||\n (codePoint >= 0x7f && codePoint <= 0xa0) ||\n UNICODE_WHITESPACE_PATTERN.test(character)\n );\n });\n}\n\nconst ABSOLUTE_HTTP_PATTERN = /^https?:\\/\\//iu;\n\n/**\n * Decides whether a `displayAs: 'link'` text value may become an anchor.\n * Returns `null` for everything that must stay plain text.\n *\n * Allowed: a site-relative path (`/` first, never `//`) or an absolute\n * `http(s)://` URL with a host and no embedded credentials. Everything else —\n * `javascript:`, `data:`, protocol-relative `//host`, backslash tricks\n * (`/\\host` is `//host` to a browser), padded or multi-line values, non-strings\n * — is refused rather than repaired.\n */\nexport function resolveFormFieldLink(value: unknown): FormFieldLink | null {\n if (\n typeof value !== 'string' ||\n !value ||\n value.includes('\\\\') ||\n hasUnsafeCharacter(value)\n ) {\n return null;\n }\n\n if (value.startsWith('/')) {\n return !value.startsWith('//') && isSafeSiteRelativePath(value)\n ? { external: false, href: value }\n : null;\n }\n\n return ABSOLUTE_HTTP_PATTERN.test(value) && isSafeAbsoluteHttpUrl(value)\n ? { external: true, href: value }\n : null;\n}\n\nconst PLACEHOLDER_BASE = 'http://placeholder.invalid';\n\n/**\n * Dot segments collapse before navigation: `/.//evil.example` resolves to the\n * path `//evil.example`, which a naive `startsWith('//')` check misses. Resolve\n * the value against a throw-away origin and require that it stays on that\n * origin and does not come out as a protocol-relative path.\n */\nfunction isSafeSiteRelativePath(value: string): boolean {\n try {\n const resolved = new URL(value, PLACEHOLDER_BASE);\n\n return (\n resolved.origin === PLACEHOLDER_BASE && !resolved.pathname.startsWith('//')\n );\n } catch {\n return false;\n }\n}\n\nfunction isSafeAbsoluteHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n\n return (\n (url.protocol === 'http:' || url.protocol === 'https:') &&\n Boolean(url.hostname) &&\n !url.username &&\n !url.password\n );\n } catch {\n return false;\n }\n}\n","import type { MemberProfileRecord } from '@rytass/bpm-core-client/template';\n\n/** Most ids asked for on their own after a failed batch, per round. */\nexport const MAX_PER_ID_REQUESTS = 20;\n\n/** Most per-id requests in flight at once. */\nexport const PER_ID_CONCURRENCY = 4;\n\nexport type MemberProfileResolution = Readonly<{\n /** Ids whose lookup failed even when asked for on their own. */\n failedIds: readonly string[];\n profiles: readonly MemberProfileRecord[];\n /**\n * Ids left unasked because a failed batch held more than the per-id budget.\n * They were not tried, so a later round may ask for them again.\n */\n skippedIds: readonly string[];\n}>;\n\nexport type MemberProfileResolutionLimits = Readonly<{\n concurrency?: number;\n maxPerIdRequests?: number;\n}>;\n\n/**\n * Looks up member profiles in one batch, and degrades per id when the batch\n * request fails.\n *\n * The host fails a whole lookup when it contains an id it cannot handle (a\n * foreign id stored in a template, say). Treating that as \"nobody resolved\"\n * labels every approver of the template 「未知會員」 for the sake of one bad\n * id, so a failed batch is retried one id at a time and only the ids that fail\n * on their own are reported back in `failedIds`.\n *\n * The retry is bounded: at most `maxPerIdRequests` ids are asked for on their\n * own (the rest come back in `skippedIds` without a request), and at most\n * `concurrency` of those requests run at once, so one bad id in a large\n * template cannot turn into a burst of requests.\n *\n * A single-id batch is not retried: it already is the per-id request.\n */\nexport async function resolveMemberProfilesPerIdOnFailure(\n memberIds: readonly string[],\n resolveMembers: (\n memberIds: readonly string[],\n ) => Promise<readonly MemberProfileRecord[]>,\n limits: MemberProfileResolutionLimits = {},\n): Promise<MemberProfileResolution> {\n const maxPerIdRequests = limits.maxPerIdRequests ?? MAX_PER_ID_REQUESTS;\n const concurrency = Math.max(1, limits.concurrency ?? PER_ID_CONCURRENCY);\n\n try {\n return {\n failedIds: [],\n profiles: await resolveMembers(memberIds),\n skippedIds: [],\n };\n } catch {\n if (memberIds.length <= 1) {\n return { failedIds: memberIds, profiles: [], skippedIds: [] };\n }\n }\n\n const askedIds = memberIds.slice(0, maxPerIdRequests);\n const settled = await settleWithConcurrency(\n askedIds,\n concurrency,\n (memberId) => resolveMembers([memberId]),\n );\n\n return {\n failedIds: askedIds.filter(\n (_, index) => settled[index].status === 'rejected',\n ),\n profiles: settled.flatMap((result) =>\n result.status === 'fulfilled' ? result.value : [],\n ),\n skippedIds: memberIds.slice(maxPerIdRequests),\n };\n}\n\n/**\n * The ids still worth asking the directory for: those that already failed on\n * their own in this session are left out, so they stay 「未知會員」 instead of\n * being re-requested every time the option list changes.\n *\n * A failed id stays 「未知會員」 for the rest of the session, even when the\n * failure was transient (a network blip, say), unless a later member search\n * returns it and replaces the placeholder. Ids skipped over the per-id cap\n * were never asked and must not be added to `failedMemberIds`.\n */\nexport function excludeFailedMemberIds(\n memberIds: readonly string[],\n failedMemberIds: ReadonlySet<string>,\n): readonly string[] {\n return memberIds.filter((memberId) => !failedMemberIds.has(memberId));\n}\n\nasync function settleWithConcurrency<TItem, TResult>(\n items: readonly TItem[],\n concurrency: number,\n run: (item: TItem) => Promise<TResult>,\n): Promise<readonly PromiseSettledResult<TResult>[]> {\n const results: PromiseSettledResult<TResult>[] = [];\n let nextIndex = 0;\n\n async function worker(): Promise<void> {\n while (nextIndex < items.length) {\n const index = nextIndex;\n\n nextIndex += 1;\n results[index] = await run(items[index]).then(\n (value): PromiseSettledResult<TResult> => ({\n status: 'fulfilled',\n value,\n }),\n (reason: unknown): PromiseSettledResult<TResult> => ({\n reason,\n status: 'rejected',\n }),\n );\n }\n }\n\n await Promise.all(\n Array.from({ length: Math.min(concurrency, items.length) }, worker),\n );\n\n return results;\n}\n","import type { FormFieldValue } from '@rytass/bpm-core-shared/form';\nimport { readFormMemberIds } from '@rytass/bpm-core-shared/form';\nimport { formatMemberLabel } from '../../../lib/format-member-label';\n\n/**\n * One pickable member, shaped for Mezzanine `AutoComplete` (`id` + `name`).\n * `unresolved` marks the placeholder written when a stored id has no profile,\n * so a real profile arriving later replaces it instead of the placeholder\n * sticking (the designer does the same for its approver list).\n */\nexport type MemberFieldOption = Readonly<{\n id: string;\n name: string;\n unresolved?: boolean;\n}>;\n\nexport const UNKNOWN_MEMBER_NAME = '未知會員';\n\nexport function readMemberFieldOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberFieldOption {\n return {\n id: member.memberId,\n name: formatMemberLabel(member.name, member.email),\n };\n}\n\nexport function readUnresolvedMemberFieldOption(\n memberId: string,\n): MemberFieldOption {\n return { id: memberId, name: UNKNOWN_MEMBER_NAME, unresolved: true };\n}\n\n/**\n * Adds `nextOptions` to `currentOptions`. A resolved option replaces an\n * unresolved placeholder with the same id; otherwise the first one seen wins.\n * Returns `currentOptions` itself when nothing changed, so state setters can\n * bail out of a re-render.\n */\nexport function mergeMemberFieldOptions(\n currentOptions: readonly MemberFieldOption[],\n nextOptions: readonly MemberFieldOption[],\n): readonly MemberFieldOption[] {\n const resolvedById = new Map(\n nextOptions\n .filter((option) => !option.unresolved)\n .map((option) => [option.id, option] as const),\n );\n const currentIds = new Set(currentOptions.map((option) => option.id));\n const addedOptions = nextOptions.filter(\n (option, index) =>\n !currentIds.has(option.id) &&\n nextOptions.findIndex((candidate) => candidate.id === option.id) ===\n index,\n );\n const upgradedOptions = currentOptions.map(\n (option) =>\n (option.unresolved ? resolvedById.get(option.id) : undefined) ?? option,\n );\n const changed =\n addedOptions.length > 0 ||\n upgradedOptions.some((option, index) => option !== currentOptions[index]);\n\n return changed ? [...upgradedOptions, ...addedOptions] : currentOptions;\n}\n\n/** Ids in the stored value that no known option covers yet. */\nexport function readUnresolvedMemberIds(\n value: FormFieldValue | undefined,\n knownOptions: readonly MemberFieldOption[],\n): readonly string[] {\n const knownIds = new Set(\n knownOptions\n .filter((option) => !option.unresolved)\n .map((option) => option.id),\n );\n\n return readFormMemberIds(value).filter((memberId) => !knownIds.has(memberId));\n}\n\n/**\n * The stored value as options, in stored order. An id with no known profile\n * still shows up (as 「未知會員」) so the filler can see and remove it.\n */\nexport function readSelectedMemberFieldOptions(\n value: FormFieldValue | undefined,\n knownOptions: readonly MemberFieldOption[],\n): readonly MemberFieldOption[] {\n return readFormMemberIds(value).map(\n (memberId) =>\n knownOptions.find((option) => option.id === memberId) ??\n readUnresolvedMemberFieldOption(memberId),\n );\n}\n\n/**\n * What a picker change writes back. Nothing picked is `undefined`, the same as\n * every other optional field, so the renderer's own initial value (`[]` for a\n * multiple member field) is restored by `buildFormRendererValues`.\n */\nexport function readMemberFieldValue(\n selected: readonly MemberFieldOption[],\n multiple: boolean,\n): FormFieldValue | undefined {\n if (selected.length === 0) {\n return undefined;\n }\n\n return multiple ? selected.map((option) => option.id) : selected[0]?.id;\n}\n","'use client';\n\nimport { ReactElement, useEffect, useRef, useState } from 'react';\nimport { AutoComplete } from '@mezzanine-ui/react';\nimport { resolveMembers, searchMembers } from '@rytass/bpm-core-client';\nimport type { FormFieldValue } from '@rytass/bpm-core-shared/form';\nimport {\n excludeFailedMemberIds,\n resolveMemberProfilesPerIdOnFailure,\n} from '../../../lib/member-profile-resolution';\nimport {\n mergeMemberFieldOptions,\n readMemberFieldOption,\n readMemberFieldValue,\n readSelectedMemberFieldOptions,\n readUnresolvedMemberFieldOption,\n readUnresolvedMemberIds,\n type MemberFieldOption,\n} from './member-field-options';\nimport { FORM_CONTROL_MENU_MAX_HEIGHT } from './form-control-menu';\n\nexport interface MemberFieldControlProps {\n readonly multiple: boolean;\n readonly name: string;\n readonly onChange: (value: FormFieldValue | undefined) => void;\n readonly placeholder?: string;\n readonly readonly: boolean;\n readonly size?: 'main' | 'sub';\n readonly value: FormFieldValue | undefined;\n}\n\n/**\n * 人員選擇 picker. Searching and name lookup reuse the directory queries the\n * admin member picker and the workflow designer already call\n * (`searchMembers` / `resolveMembers`); nothing new is added server side.\n *\n * Stored values are member ids. The names shown here are resolved on mount and\n * whenever a stored id has no profile yet, so a read-only view of a submitted\n * form (instance detail) shows names, not ids.\n */\nexport function MemberFieldControl({\n multiple,\n name,\n onChange,\n placeholder,\n readonly,\n size,\n value,\n}: MemberFieldControlProps): ReactElement {\n const [knownOptions, setKnownOptions] = useState<\n readonly MemberFieldOption[]\n >([]);\n const [searchResults, setSearchResults] = useState<\n readonly MemberFieldOption[]\n >([]);\n const [loading, setLoading] = useState(false);\n const latestSearchRef = useRef(0);\n // Ids the directory could not name; they stay 「未知會員」 and are not asked\n // for again when the other ids of the value resolve and the key changes.\n // That holds for the rest of the session, a transient failure included,\n // unless a later search returns the member (the merge then replaces the\n // placeholder). Ids skipped over the per-id cap were never asked, so they are\n // not given up on.\n const givenUpIdsRef = useRef<Set<string>>(new Set());\n const unresolvedKey = readUnresolvedMemberIds(value, knownOptions).join('\\n');\n\n useEffect((): (() => void) => {\n const memberIds = excludeFailedMemberIds(\n unresolvedKey ? unresolvedKey.split('\\n') : [],\n givenUpIdsRef.current,\n );\n let cancelled = false;\n\n if (memberIds.length === 0) {\n return (): void => undefined;\n }\n\n // One id the directory cannot handle fails the whole batch, so a failed\n // batch is retried per id: the other members keep their names.\n void resolveMemberProfilesPerIdOnFailure(memberIds, resolveMembers).then(\n ({ profiles, skippedIds }): void => {\n if (cancelled) {\n return;\n }\n\n const resolved = profiles.map(readMemberFieldOption);\n // Ids the directory did not return stay visible as placeholders.\n const placeholders = memberIds\n .filter(\n (memberId) => !resolved.some((option) => option.id === memberId),\n )\n .map(readUnresolvedMemberFieldOption);\n\n // Only the ids that were asked for and still came back without a\n // profile are given up on, so a failed lookup is not retried in a loop.\n // Ids skipped over the per-id cap were never asked: they keep a\n // placeholder but stay eligible for the next round, as the designer\n // does.\n placeholders\n .filter((placeholder) => !skippedIds.includes(placeholder.id))\n .forEach((placeholder) => givenUpIdsRef.current.add(placeholder.id));\n\n setKnownOptions((current) =>\n mergeMemberFieldOptions(current, [...resolved, ...placeholders]),\n );\n },\n );\n\n return (): void => {\n cancelled = true;\n };\n }, [unresolvedKey]);\n\n async function handleSearch(searchText: string): Promise<void> {\n const requestId = latestSearchRef.current + 1;\n\n latestSearchRef.current = requestId;\n setLoading(true);\n\n try {\n const options = (await searchMembers(searchText)).map(\n readMemberFieldOption,\n );\n\n if (latestSearchRef.current === requestId) {\n setSearchResults(options);\n setKnownOptions((current) => mergeMemberFieldOptions(current, options));\n }\n } catch {\n if (latestSearchRef.current === requestId) {\n setSearchResults([]);\n }\n } finally {\n if (latestSearchRef.current === requestId) {\n setLoading(false);\n }\n }\n }\n\n const selected = readSelectedMemberFieldOptions(value, knownOptions);\n // Selected members stay in the option list so a picked member can be\n // un-ticked from the dropdown, not only through its tag.\n const options = [...mergeMemberFieldOptions(selected, searchResults)];\n const commonProps = {\n asyncData: true,\n clearSearchText: true,\n disabled: readonly,\n disabledOptionsFilter: true,\n emptyText: '沒有符合的成員',\n globalPortal: true,\n inputProps: {\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n },\n loading,\n loadingText: '搜尋成員中...',\n menuMaxHeight: FORM_CONTROL_MENU_MAX_HEIGHT,\n onSearch: handleSearch,\n onVisibilityChange: (open: boolean): void => {\n if (open) {\n setSearchResults([]);\n void handleSearch('');\n }\n },\n options,\n placeholder: placeholder ?? '搜尋姓名或信箱',\n readOnly: readonly,\n searchDebounceTime: 300,\n size,\n } as const;\n\n return multiple ? (\n <AutoComplete\n {...commonProps}\n mode=\"multiple\"\n onChange={(nextOptions): void =>\n onChange(readMemberFieldValue(nextOptions, true))\n }\n value={[...selected]}\n />\n ) : (\n <AutoComplete\n {...commonProps}\n mode=\"single\"\n onChange={(nextOption): void =>\n onChange(readMemberFieldValue(nextOption ? [nextOption] : [], false))\n }\n value={selected[0] ?? null}\n />\n );\n}\n","import type { FormFieldValue } from '@rytass/bpm-core-shared/form';\nimport type { OrgUnitOptionRecord } from '@rytass/bpm-core-client/organization';\n\n/**\n * One pickable organization unit, shaped for Mezzanine `AutoComplete` (`id` +\n * `name`). `unresolved` marks a placeholder written while the stored id has no\n * name yet (the id itself is shown) or when the server cannot give one\n * (「未知組織」), so a real name arriving later replaces the placeholder.\n */\nexport type OrgUnitFieldOption = Readonly<{\n id: string;\n name: string;\n unresolved?: boolean;\n}>;\n\nexport const UNKNOWN_ORG_UNIT_NAME = '未知組織';\nexport const DELETED_ORG_UNIT_SUFFIX = '(已刪除)';\n\nexport function readOrgUnitFieldOption(\n record: OrgUnitOptionRecord,\n): OrgUnitFieldOption {\n return {\n id: record.id,\n name: record.deleted\n ? `${record.name}${DELETED_ORG_UNIT_SUFFIX}`\n : record.name,\n };\n}\n\n/** The id stands in for the name until the lookup comes back. */\nexport function readPendingOrgUnitFieldOption(\n orgUnitId: string,\n): OrgUnitFieldOption {\n return { id: orgUnitId, name: orgUnitId, unresolved: true };\n}\n\nexport function readUnknownOrgUnitFieldOption(\n orgUnitId: string,\n): OrgUnitFieldOption {\n return { id: orgUnitId, name: UNKNOWN_ORG_UNIT_NAME, unresolved: true };\n}\n\nfunction readStoredOrgUnitId(value: FormFieldValue | undefined): string | null {\n return typeof value === 'string' && value.trim() ? value : null;\n}\n\n// The server lowercases the ids it is given and answers with the lowercase\n// form, so a stored id is matched without regard to case.\nfunction readOrgUnitIdKey(orgUnitId: string): string {\n return orgUnitId.toLowerCase();\n}\n\nfunction isSameOrgUnitId(left: string, right: string): boolean {\n return readOrgUnitIdKey(left) === readOrgUnitIdKey(right);\n}\n\n/**\n * The stored value as an option. An id nobody has named yet shows as itself;\n * one the server could not name shows as 「未知組織」 (it is in `knownOptions` as\n * a placeholder), so the filler can still see and replace it.\n */\nexport function readSelectedOrgUnitFieldOption(\n value: FormFieldValue | undefined,\n knownOptions: readonly OrgUnitFieldOption[],\n): OrgUnitFieldOption | null {\n const orgUnitId = readStoredOrgUnitId(value);\n\n if (orgUnitId === null) {\n return null;\n }\n\n return (\n knownOptions.find((option) => isSameOrgUnitId(option.id, orgUnitId)) ??\n readPendingOrgUnitFieldOption(orgUnitId)\n );\n}\n\n/**\n * The stored id that no known option covers yet, or `null`. An id the server\n * could not name is covered by its 「未知組織」 placeholder, so it is not asked\n * for again.\n */\nexport function readUnresolvedOrgUnitId(\n value: FormFieldValue | undefined,\n knownOptions: readonly OrgUnitFieldOption[],\n): string | null {\n const orgUnitId = readStoredOrgUnitId(value);\n\n return orgUnitId !== null &&\n !knownOptions.some((option) => isSameOrgUnitId(option.id, orgUnitId))\n ? orgUnitId\n : null;\n}\n\n/**\n * Adds `nextOptions` to `currentOptions`. A named option replaces an unresolved\n * placeholder with the same id, compared without regard to case like every\n * other id match here; otherwise the first one seen wins. Returns\n * `currentOptions` itself when nothing changed, so state setters can bail out\n * of a re-render.\n */\nexport function mergeOrgUnitFieldOptions(\n currentOptions: readonly OrgUnitFieldOption[],\n nextOptions: readonly OrgUnitFieldOption[],\n): readonly OrgUnitFieldOption[] {\n const namedById = new Map(\n nextOptions\n .filter((option) => !option.unresolved)\n .map((option) => [readOrgUnitIdKey(option.id), option] as const),\n );\n const currentIds = new Set(\n currentOptions.map((option) => readOrgUnitIdKey(option.id)),\n );\n const addedOptions = nextOptions.filter(\n (option, index) =>\n !currentIds.has(readOrgUnitIdKey(option.id)) &&\n nextOptions.findIndex((candidate) =>\n isSameOrgUnitId(candidate.id, option.id),\n ) === index,\n );\n const upgradedOptions = currentOptions.map(\n (option) =>\n (option.unresolved\n ? namedById.get(readOrgUnitIdKey(option.id))\n : undefined) ?? option,\n );\n const changed =\n addedOptions.length > 0 ||\n upgradedOptions.some((option, index) => option !== currentOptions[index]);\n\n return changed ? [...upgradedOptions, ...addedOptions] : currentOptions;\n}\n\n/**\n * What a picker change writes back. Nothing picked is `undefined`, the same as\n * every other optional field.\n */\nexport function readOrgUnitFieldValue(\n selected: OrgUnitFieldOption | null,\n): FormFieldValue | undefined {\n return selected ? selected.id : undefined;\n}\n","'use client';\n\nimport { ReactElement, useEffect, useRef, useState } from 'react';\nimport { AutoComplete, Typography } from '@mezzanine-ui/react';\nimport {\n resolveOrgUnitOptions,\n searchOrgUnitOptions,\n} from '@rytass/bpm-core-client/organization';\nimport type { FormFieldValue } from '@rytass/bpm-core-shared/form';\nimport {\n mergeOrgUnitFieldOptions,\n readOrgUnitFieldOption,\n readOrgUnitFieldValue,\n readSelectedOrgUnitFieldOption,\n readUnknownOrgUnitFieldOption,\n readUnresolvedOrgUnitId,\n type OrgUnitFieldOption,\n} from './org-unit-field-options';\nimport { FORM_CONTROL_MENU_MAX_HEIGHT } from './form-control-menu';\n\nexport interface OrgUnitFieldControlProps {\n readonly name: string;\n readonly onChange: (value: FormFieldValue | undefined) => void;\n readonly placeholder?: string;\n readonly readonly: boolean;\n readonly size?: 'main' | 'sub';\n readonly value: FormFieldValue | undefined;\n}\n\nexport const ORG_UNIT_SEARCH_FAILED_TEXT = '搜尋組織失敗,請稍後再試。';\n\n/**\n * 組織選擇 picker. The stored value is one organization unit id. Searching and\n * name lookup use the picker queries open to any signed-in member\n * (`searchOrgUnitOptions` / `resolveOrgUnitOptions`), not the designer-only\n * organization directory.\n *\n * A stored id shows as itself until its name comes back, then as the name\n * (「(已刪除)」 after it for a retired unit). An id the server cannot name\n * — unknown, or the lookup failed — shows as 「未知組織」 and is not asked for\n * again unless a later search returns the unit.\n */\nexport function OrgUnitFieldControl({\n name,\n onChange,\n placeholder,\n readonly,\n size,\n value,\n}: OrgUnitFieldControlProps): ReactElement {\n const [knownOptions, setKnownOptions] = useState<\n readonly OrgUnitFieldOption[]\n >([]);\n const [searchResults, setSearchResults] = useState<\n readonly OrgUnitFieldOption[]\n >([]);\n const [loading, setLoading] = useState(false);\n const [searchFailed, setSearchFailed] = useState(false);\n const latestSearchRef = useRef(0);\n const unresolvedId = readUnresolvedOrgUnitId(value, knownOptions);\n\n useEffect((): (() => void) => {\n let cancelled = false;\n\n if (unresolvedId === null) {\n return (): void => undefined;\n }\n\n void resolveOrgUnitOptions([unresolvedId])\n .then((records): readonly OrgUnitFieldOption[] => {\n const resolved = records.map(readOrgUnitFieldOption);\n\n // The id came back without a unit: it stays 「未知組織」 and is not\n // asked for again.\n return resolved.some(\n (option) => option.id.toLowerCase() === unresolvedId.toLowerCase(),\n )\n ? resolved\n : [...resolved, readUnknownOrgUnitFieldOption(unresolvedId)];\n })\n .catch((): readonly OrgUnitFieldOption[] => [\n readUnknownOrgUnitFieldOption(unresolvedId),\n ])\n .then((options): void => {\n if (!cancelled) {\n setKnownOptions((current) =>\n mergeOrgUnitFieldOptions(current, options),\n );\n }\n });\n\n return (): void => {\n cancelled = true;\n };\n }, [unresolvedId]);\n\n async function handleSearch(searchText: string): Promise<void> {\n const requestId = latestSearchRef.current + 1;\n\n latestSearchRef.current = requestId;\n setLoading(true);\n\n try {\n const options = (await searchOrgUnitOptions(searchText)).map(\n readOrgUnitFieldOption,\n );\n\n if (latestSearchRef.current === requestId) {\n setSearchResults(options);\n setSearchFailed(false);\n setKnownOptions((current) =>\n mergeOrgUnitFieldOptions(current, options),\n );\n }\n } catch {\n // The picked unit is untouched: only the list of hits is emptied.\n if (latestSearchRef.current === requestId) {\n setSearchResults([]);\n setSearchFailed(true);\n }\n } finally {\n if (latestSearchRef.current === requestId) {\n setLoading(false);\n }\n }\n }\n\n const selected = readSelectedOrgUnitFieldOption(value, knownOptions);\n // The picked unit stays in the option list so it can be un-ticked from the\n // dropdown, not only through its tag.\n const options = [\n ...mergeOrgUnitFieldOptions(selected ? [selected] : [], searchResults),\n ];\n\n return (\n <div>\n <AutoComplete\n asyncData\n clearSearchText\n disabled={readonly}\n disabledOptionsFilter\n emptyText=\"沒有符合的組織\"\n globalPortal\n inputProps={{\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n }}\n loading={loading}\n loadingText=\"搜尋組織中...\"\n menuMaxHeight={FORM_CONTROL_MENU_MAX_HEIGHT}\n mode=\"single\"\n onChange={(nextOption): void =>\n onChange(readOrgUnitFieldValue(nextOption ?? null))\n }\n onSearch={handleSearch}\n onVisibilityChange={(open: boolean): void => {\n if (open) {\n setSearchResults([]);\n void handleSearch('');\n }\n }}\n options={options}\n placeholder={placeholder ?? '搜尋組織名稱'}\n readOnly={readonly}\n searchDebounceTime={300}\n size={size}\n value={selected}\n />\n {searchFailed ? (\n <Typography color=\"text-error\" variant=\"caption\">\n {ORG_UNIT_SEARCH_FAILED_TEXT}\n </Typography>\n ) : null}\n </div>\n );\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n AutoComplete,\n Button,\n CheckboxGroup,\n DatePicker,\n DateTimePicker,\n Input,\n RadioGroup,\n Select,\n Table,\n Textarea,\n Toggle,\n Typography,\n} from '@mezzanine-ui/react';\nimport type { TableActions, TableColumn } from '@mezzanine-ui/core/table';\nimport { PlusIcon, TrashIcon } from '@mezzanine-ui/icons';\nimport {\n FormDataSourceOptionFieldDefinition,\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldValue,\n FormTableRowValue,\n FormUiSchema,\n TableColumnDefinition,\n TableFieldDefinition,\n isFormDataSourceFieldDefinition,\n isFormStaticOptionFieldDefinition,\n isFormTableCellValue,\n isTableFieldDefinition,\n readFormFieldSelectionMode,\n} from '@rytass/bpm-core-shared/form';\nimport {\n buildFormRendererValues,\n clampOptionalNumber,\n createFormTableRow,\n formatDatePickerValue,\n formatDateTimePickerValue,\n FormRendererValues,\n isFormRendererFieldReadonly,\n isFormRendererFieldRequired,\n parseOptionalNumberInput,\n readDatePickerValue,\n readFieldOptionAsSelectOption,\n readFormTableCellPath,\n readFormTableRowBounds,\n readFormTableRows,\n readSelectedFormDataSourceOptions,\n readSelectOption,\n readVisibleFormRendererFields,\n} from '@rytass/bpm-core-client/form';\nimport type { ResolvedFieldAccess } from '@rytass/bpm-core-shared';\nimport { BPMFormField } from '../../../components/bpm-form-field';\nimport {\n FormDataSourceFieldState,\n FormRendererDataSourceContext,\n readFormDataSourceFieldStatusMessage,\n useFormDataSourceField,\n} from './form-data-source-field';\nimport {\n FileUploadFieldControl,\n type FileUploadFieldState,\n type FormRendererAttachment,\n} from './file-upload-field';\nimport { readStringArrayValue } from './form-value-readers';\nimport { resolveFormFieldLink } from './form-field-link';\nimport { FORM_CONTROL_MENU_MAX_HEIGHT } from './form-control-menu';\nimport { MemberFieldControl } from './member-field';\nimport { OrgUnitFieldControl } from './org-unit-field';\n\nexport interface FormRendererProps {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n /**\n * Attachments the form's `file_upload` values may refer to (for example the\n * instance's `listAttachments`). A field lists each id it holds by the name\n * and size found here.\n */\n readonly attachments?: readonly FormRendererAttachment[];\n readonly emptyText?: string;\n readonly errors?: Readonly<Record<string, string>>;\n /**\n * Per top-level field, what this viewer may see and edit (ADR 20 §3.3,\n * `resolveFieldAccess`). A field with `visible: false` is not rendered; one\n * with `editable: true` stays editable inside a `readonly` form, and one\n * with `editable: false` is read-only in an editable form. The form's own\n * rules (`hidden`, `visibleWhen`, `readonlyWhen`) still apply. A field the\n * map leaves out follows `readonly`; leaving the prop out changes nothing.\n */\n readonly fieldAccess?: Readonly<Record<string, ResolvedFieldAccess>>;\n /**\n * Short text shown after a field's label (Mezzanine `labelOptionalMarker`),\n * per top-level field — e.g. 「本關補填」 on the fields an approver fills.\n */\n readonly fieldLabelMarkers?: Readonly<Record<string, string>>;\n readonly maxWidth?: CSSProperties['maxWidth'];\n readonly onChange?: (values: FormRendererValues) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'selectionExpired' | 'status'\n >,\n ) => void;\n /**\n * Called when a `file_upload` field starts or settles an upload. A host\n * that submits the form refuses to while any field reports a pending or\n * failed upload (`readFileUploadSubmissionBlockMessage`): those files are\n * listed but not in the value.\n */\n readonly onFileUploadStateChange?: (\n fieldKey: string,\n state: FileUploadFieldState,\n ) => void;\n /**\n * Opens an attachment a `file_upload` field lists. Without it, only files\n * picked in this form can be downloaded from the field.\n */\n readonly onDownloadAttachment?: (attachmentId: string) => void;\n /**\n * Uploads a file picked in a `file_upload` field and returns the attachment\n * id the value then holds. Without it a `file_upload` field cannot upload.\n */\n readonly onUploadAttachment?: (\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly?: boolean;\n readonly schema: FormDefinitionSchema;\n readonly singleColumn?: boolean;\n readonly uiSchema: FormUiSchema;\n readonly value?: FormRendererValues;\n}\n\n/**\n * Controls inside a table cell take the table's own `sub` size, so a row is\n * sized by its cells rather than by whichever control kept the default.\n * `DatePicker` and `DateTimePicker` accept no size, so a date column stays at\n * the default height — a Mezzanine limitation, not a choice.\n */\ntype ControlSize = 'main' | 'sub';\n\nconst FORM_RENDERER_GRID_STYLE: CSSProperties = {\n alignItems: 'start',\n display: 'grid',\n gap: 12,\n gridTemplateColumns: 'repeat(12, minmax(0, 1fr))',\n};\n\nconst FORM_RENDERER_EMPTY_STYLE: CSSProperties = {\n gridColumn: '1 / -1',\n};\n\nconst FORM_RENDERER_FIELD_STYLE: CSSProperties = {\n alignItems: 'start',\n display: 'grid',\n gap: 8,\n minWidth: 0,\n};\n\nconst TEXTAREA_STYLE: CSSProperties = {\n minWidth: '100%',\n width: '100%',\n};\n\nfunction applyFullWidthTextareaHost(element: HTMLDivElement | null): void {\n if (!element) {\n return;\n }\n\n element.style.width = '100%';\n}\n\n/** What a `file_upload` control needs from the form it renders in. */\ninterface FormRendererFileUpload {\n readonly attachments: readonly FormRendererAttachment[] | undefined;\n readonly onDownloadAttachment: ((attachmentId: string) => void) | undefined;\n readonly onStateChange:\n | ((fieldKey: string, state: FileUploadFieldState) => void)\n | undefined;\n readonly onUploadAttachment:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined;\n}\n\nexport function FormRenderer({\n attachments,\n dataSourceContext,\n dataSourceInitialValues,\n emptyText = '尚未建立欄位。',\n errors = {},\n fieldAccess,\n fieldLabelMarkers,\n maxWidth,\n onChange,\n onDataSourceStateChange,\n onDownloadAttachment,\n onFileUploadStateChange,\n onUploadAttachment,\n optionSnapshots,\n readonly = false,\n schema,\n singleColumn = false,\n uiSchema,\n value,\n}: FormRendererProps): ReactElement {\n const fileUpload = useMemo(\n (): FormRendererFileUpload => ({\n attachments,\n onDownloadAttachment,\n onStateChange: onFileUploadStateChange,\n onUploadAttachment,\n }),\n [\n attachments,\n onDownloadAttachment,\n onFileUploadStateChange,\n onUploadAttachment,\n ],\n );\n const [internalValues, setInternalValues] = useState<FormRendererValues>(() =>\n buildFormRendererValues(schema.fields, value ?? {}),\n );\n const values = useMemo(\n (): FormRendererValues =>\n buildFormRendererValues(schema.fields, value ?? internalValues),\n [internalValues, schema.fields, value],\n );\n const valuesRef = useRef<FormRendererValues>(values);\n const visibleFields = useMemo(\n (): readonly FormFieldDefinition[] =>\n readVisibleFormRendererFields(schema, uiSchema, values).filter(\n (field) =>\n readOwnFieldAccess(fieldAccess, field.fieldKey)?.visible !== false,\n ),\n [fieldAccess, schema, uiSchema, values],\n );\n\n useEffect((): void => {\n valuesRef.current = values;\n }, [values]);\n\n useEffect((): void => {\n setInternalValues((currentValues) =>\n buildFormRendererValues(schema.fields, value ?? currentValues),\n );\n }, [schema.fields, value]);\n\n function updateValue(\n fieldKey: string,\n nextValue: FormFieldValue | undefined,\n ): void {\n const nextValues = {\n ...valuesRef.current,\n [fieldKey]: nextValue,\n };\n valuesRef.current = nextValues;\n\n if (!value) {\n setInternalValues(nextValues);\n }\n\n onChange?.(nextValues);\n }\n\n if (!visibleFields.length) {\n return (\n <div style={FORM_RENDERER_GRID_STYLE}>\n <Typography\n color=\"text-neutral\"\n style={FORM_RENDERER_EMPTY_STYLE}\n variant=\"body\"\n >\n {schema.fields.length > 0 ? '目前條件下沒有可填寫欄位。' : emptyText}\n </Typography>\n </div>\n );\n }\n\n return (\n <div\n style={{\n ...FORM_RENDERER_GRID_STYLE,\n // `maxWidth` is applied per field rather than to the grid, so a table\n // field can use the full content width while every other field keeps\n // the single-column reading width (docs/17 P4).\n ...(singleColumn ? { width: '100%' } : {}),\n }}\n >\n {visibleFields.map((field) => (\n <FormRendererField\n dataSourceContext={dataSourceContext}\n dataSourceInitialValues={dataSourceInitialValues}\n field={field}\n error={errors[field.fieldKey] ?? null}\n errors={errors}\n fields={schema.fields}\n key={field.fieldKey}\n labelMarker={\n fieldLabelMarkers !== undefined &&\n Object.prototype.hasOwnProperty.call(\n fieldLabelMarkers,\n field.fieldKey,\n )\n ? fieldLabelMarkers[field.fieldKey]\n : undefined\n }\n onChange={updateValue}\n fileUpload={fileUpload}\n onDataSourceStateChange={onDataSourceStateChange}\n optionSnapshots={optionSnapshots}\n readonly={readFieldAccessReadonly(\n readOwnFieldAccess(fieldAccess, field.fieldKey),\n readonly,\n )}\n schema={schema}\n style={{\n ...FORM_RENDERER_FIELD_STYLE,\n gridColumn: `span ${\n singleColumn ? 12 : readFieldColumnSpan(field, uiSchema)\n }`,\n ...(singleColumn && !isTableFieldDefinition(field)\n ? { maxWidth }\n : {}),\n }}\n value={values[field.fieldKey]}\n values={values}\n uiSchema={uiSchema}\n />\n ))}\n </div>\n );\n}\n\n/** Own-property read: a field key like `constructor` must not hit the prototype. */\nfunction readOwnFieldAccess(\n fieldAccess: Readonly<Record<string, ResolvedFieldAccess>> | undefined,\n fieldKey: string,\n): ResolvedFieldAccess | undefined {\n return fieldAccess !== undefined &&\n Object.prototype.hasOwnProperty.call(fieldAccess, fieldKey)\n ? fieldAccess[fieldKey]\n : undefined;\n}\n\nfunction readFieldAccessReadonly(\n access: ResolvedFieldAccess | undefined,\n formReadonly: boolean,\n): boolean {\n return access === undefined ? formReadonly : !access.editable;\n}\n\nfunction FormRendererField({\n dataSourceContext,\n dataSourceInitialValues,\n error,\n errors,\n field,\n fields,\n fileUpload,\n labelMarker,\n onChange,\n onDataSourceStateChange,\n optionSnapshots,\n readonly,\n schema,\n style,\n value,\n values,\n uiSchema,\n}: {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n readonly error: string | null;\n /**\n * The whole error map, not just this field's line: a table addresses its\n * cells by instance path, so it needs the keys of its own rows too\n * (ADR 16 §3.3).\n */\n readonly errors: Readonly<Record<string, string>>;\n readonly field: FormFieldDefinition;\n readonly fields: readonly FormFieldDefinition[];\n readonly labelMarker?: string;\n readonly onChange: (\n fieldKey: string,\n value: FormFieldValue | undefined,\n ) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'selectionExpired' | 'status'\n >,\n ) => void;\n readonly fileUpload: FormRendererFileUpload;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n readonly schema: FormDefinitionSchema;\n readonly style: CSSProperties;\n readonly uiSchema: FormUiSchema;\n readonly value: FormFieldValue | undefined;\n readonly values: FormRendererValues;\n}): ReactElement {\n const required = isFormRendererFieldRequired(field, fields, values);\n const fieldReadonly =\n readonly || isFormRendererFieldReadonly(field, fields, values);\n const dataSourceState = useFormDataSourceField({\n context: dataSourceContext,\n field,\n formData: values,\n initialFormData: dataSourceInitialValues,\n initialValue: dataSourceInitialValues?.[field.fieldKey],\n optionSnapshots,\n readonly: fieldReadonly,\n schema,\n uiSchema,\n });\n const dataSourceStatusMessage = readFormDataSourceFieldStatusMessage(\n dataSourceState,\n );\n const dataSourceStateSignature = JSON.stringify({\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n selectionExpired: dataSourceState.selectionExpired === true,\n status: dataSourceState.status,\n });\n\n useEffect((): void => {\n if (!isFormDataSourceFieldDefinition(field)) {\n return;\n }\n\n onDataSourceStateChange?.(field.fieldKey, {\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n selectionExpired: dataSourceState.selectionExpired === true,\n status: dataSourceState.status,\n });\n }, [\n dataSourceStateSignature,\n field.fieldKey,\n onDataSourceStateChange,\n ]);\n\n return (\n <div data-form-field-key={field.fieldKey} style={style}>\n <BPMFormField\n label={field.label}\n {...(labelMarker ? { labelOptionalMarker: labelMarker } : {})}\n name={field.fieldKey}\n required={required}\n >\n {isTableFieldDefinition(field) ? (\n <FormTableField\n dataSourceContext={dataSourceContext}\n dataSourceInitialValues={dataSourceInitialValues}\n errors={errors}\n field={field}\n onChange={onChange}\n onDataSourceStateChange={onDataSourceStateChange}\n optionSnapshots={optionSnapshots}\n readonly={fieldReadonly}\n schema={schema}\n uiSchema={uiSchema}\n value={value}\n values={values}\n />\n ) : (\n renderControl(\n field,\n value,\n fieldReadonly,\n onChange,\n fileUpload,\n dataSourceContext,\n dataSourceState,\n )\n )}\n </BPMFormField>\n {error ? (\n <Typography color=\"text-error\" variant=\"caption\">\n {error}\n </Typography>\n ) : null}\n {dataSourceStatusMessage ? (\n <div style={DATA_SOURCE_FEEDBACK_STYLE}>\n <Typography\n color={\n dataSourceState.status === 'INVALID' ||\n dataSourceState.status === 'UNAVAILABLE'\n ? 'text-error'\n : 'text-neutral'\n }\n variant=\"caption\"\n >\n {dataSourceStatusMessage}\n </Typography>\n {dataSourceState.status === 'UNAVAILABLE' && dataSourceState.canRetry ? (\n <Button\n onClick={dataSourceState.retry}\n size=\"sub\"\n type=\"button\"\n variant=\"base-ghost\"\n >\n 重試\n </Button>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\nconst EXTERNAL_LINK_CUE = '(另開新分頁)';\nconst EXTERNAL_LINK_TITLE = '另開新分頁';\n\nconst LINK_STYLE: CSSProperties = {\n color: 'inherit',\n textDecoration: 'underline',\n wordBreak: 'break-all',\n};\n\nconst DATA_SOURCE_FEEDBACK_STYLE: CSSProperties = {\n alignItems: 'center',\n display: 'flex',\n gap: 8,\n};\n\nconst TABLE_FIELD_STYLE: CSSProperties = {\n display: 'grid',\n gap: 8,\n minWidth: 0,\n width: '100%',\n};\n\nconst TABLE_FIELD_ACTIONS_STYLE: CSSProperties = {\n display: 'flex',\n justifyContent: 'flex-start',\n};\n\n// Mezzanine `Table` fills whatever box it is given and would keep shrinking\n// its columns. Keeping the table out of the single-column reading width is\n// what stops a control from overflowing its own cell; this per-column floor is\n// the defence for tables wide enough that even the full width would squeeze\n// them, and it is what makes the Table's own scroller scroll when that happens.\nconst TABLE_COLUMN_MIN_WIDTH = 160;\n\nconst TABLE_CELL_STYLE: CSSProperties = {\n display: 'grid',\n gap: 4,\n minWidth: 0,\n};\n\ntype FormTableRowRecord = Readonly<\n Record<string, unknown> & {\n index: number;\n key: string;\n }\n>;\n\n/**\n * Rows carry no identity in `formData` — array order is display order\n * (ADR 16 §3.2) — so the React key and, from P3, the per-cell DataSource state\n * key come from an ephemeral id minted here and never written to the value.\n */\nfunction FormTableField({\n dataSourceContext,\n dataSourceInitialValues,\n errors,\n field,\n onChange,\n onDataSourceStateChange,\n optionSnapshots,\n readonly,\n schema,\n uiSchema,\n value,\n values,\n}: {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n readonly errors: Readonly<Record<string, string>>;\n readonly field: TableFieldDefinition;\n readonly onChange: (\n fieldKey: string,\n value: FormFieldValue | undefined,\n ) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'selectionExpired' | 'status'\n >,\n ) => void;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n readonly value: FormFieldValue | undefined;\n readonly values: FormRendererValues;\n}): ReactElement {\n const rows = readFormTableRows(value);\n const bounds = readFormTableRowBounds(field);\n const rowIdSequenceRef = useRef(0);\n const [rowIds, setRowIds] = useState<readonly string[]>(() =>\n rows.map((): string => createRowId(rowIdSequenceRef)),\n );\n\n // The value can also change from outside (a parent reset, a returned case\n // loading its saved rows). Re-mint ids only then, so ordinary edits keep the\n // per-row identity stable.\n useEffect((): void => {\n if (rowIds.length === rows.length) {\n return;\n }\n\n setRowIds(rows.map((): string => createRowId(rowIdSequenceRef)));\n }, [rowIds.length, rows.length]);\n\n function commitRows(nextRows: readonly FormTableRowValue[]): void {\n onChange(field.fieldKey, nextRows);\n }\n\n function handleAddRow(): void {\n setRowIds([...rowIds, createRowId(rowIdSequenceRef)]);\n commitRows([...rows, createFormTableRow(field.columns)]);\n }\n\n function handleRemoveRow(rowIndex: number): void {\n setRowIds(rowIds.filter((_, index) => index !== rowIndex));\n commitRows(rows.filter((_, index) => index !== rowIndex));\n }\n\n function handleCellChange(\n rowIndex: number,\n columnKey: string,\n cellValue: FormFieldValue | undefined,\n ): void {\n commitRows(\n rows.map((row, index) =>\n index === rowIndex\n ? readNextTableRow(row, columnKey, cellValue)\n : row,\n ),\n );\n }\n\n const dataSource: readonly FormTableRowRecord[] = rows.map((row, index) => ({\n ...row,\n index,\n key: rowIds[index] ?? `row-${index}`,\n }));\n const columns: TableColumn<FormTableRowRecord>[] = field.columns.map(\n (column) => ({\n key: column.fieldKey,\n minWidth: TABLE_COLUMN_MIN_WIDTH,\n render: (row): ReactElement => (\n <FormTableCell\n column={column}\n dataSourceContext={dataSourceContext}\n error={\n errors[\n readFormTableCellPath(field.fieldKey, row.index, column.fieldKey)\n ] ?? null\n }\n fieldPath={`${field.fieldKey}.${column.fieldKey}`}\n formData={values}\n initialFormData={dataSourceInitialValues}\n initialRowValues={readInitialTableRow(\n dataSourceInitialValues?.[field.fieldKey],\n row.index,\n )}\n // Keyed by the ephemeral row id, so a cell's DataSource state follows\n // its row rather than its position (ADR 16 §3.9).\n key={`${rowIds[row.index] ?? row.index}-${column.fieldKey}`}\n onChange={(cellValue): void =>\n handleCellChange(row.index, column.fieldKey, cellValue)\n }\n onDataSourceStateChange={onDataSourceStateChange}\n optionSnapshots={optionSnapshots}\n path={readFormTableCellPath(\n field.fieldKey,\n row.index,\n column.fieldKey,\n )}\n readonly={readonly}\n rowValues={rows[row.index] ?? {}}\n schema={schema}\n uiSchema={uiSchema}\n />\n ),\n title: column.label || column.fieldKey,\n }),\n );\n const actions: TableActions<FormTableRowRecord> | undefined = readonly\n ? undefined\n : {\n render: (row): ReturnType<TableActions<FormTableRowRecord>['render']> => [\n {\n disabled: (): boolean => rows.length <= bounds.minRows,\n icon: TrashIcon,\n iconType: 'icon-only',\n name: '刪除此列',\n onClick: (): void => handleRemoveRow(row.index),\n variant: 'destructive-ghost',\n },\n ],\n fixed: 'end',\n width: 56,\n };\n\n return (\n <div style={TABLE_FIELD_STYLE}>\n {/*\n Mezzanine `Table` scrolls horizontally inside its own scroller once its\n columns' `minWidth` floors outgrow the box. It must own that scroller:\n the row actions are pinned with `fixed: 'end'`, and a pinned column\n only sticks to the Table's scroller, never to a wrapper around it.\n */}\n <Table\n {...(actions ? { actions } : {})}\n columns={columns}\n dataSource={[...dataSource]}\n // Cells carry form controls rather than text, so the row height\n // comes from the roomy container token, not the compact default.\n rowHeightPreset=\"roomy\"\n showHeader\n size=\"sub\"\n />\n {readonly ? null : (\n <div style={TABLE_FIELD_ACTIONS_STYLE}>\n <Button\n disabled={rows.length >= bounds.maxRows}\n icon={PlusIcon}\n iconType=\"leading\"\n onClick={handleAddRow}\n size=\"sub\"\n type=\"button\"\n variant=\"base-secondary\"\n >\n {field.addRowLabel || '新增一列'}\n </Button>\n </div>\n )}\n </div>\n );\n}\n\nfunction FormTableCell({\n column,\n dataSourceContext,\n error,\n fieldPath,\n formData,\n initialFormData,\n initialRowValues,\n onChange,\n onDataSourceStateChange,\n optionSnapshots,\n path,\n readonly,\n rowValues,\n schema,\n uiSchema,\n}: {\n readonly column: TableColumnDefinition;\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly error: string | null;\n readonly fieldPath: string;\n readonly formData: FormRendererValues;\n readonly initialFormData?: FormRendererValues;\n readonly initialRowValues?: FormTableRowValue;\n readonly onChange: (value: FormFieldValue | undefined) => void;\n readonly onDataSourceStateChange?: (\n fieldKey: string,\n state: Pick<\n FormDataSourceFieldState,\n 'hasValue' | 'invalidValues' | 'selectionExpired' | 'status'\n >,\n ) => void;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly path: string;\n readonly readonly: boolean;\n readonly rowValues: FormTableRowValue;\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n}): ReactElement {\n const value = readTableCellValue(rowValues, column.fieldKey);\n const dataSourceState = useFormDataSourceField({\n context: dataSourceContext,\n field: column,\n fieldPath,\n formData,\n initialFormData,\n initialValue: initialRowValues\n ? readTableCellValue(initialRowValues, column.fieldKey)\n : undefined,\n optionSnapshots,\n readonly,\n row: {\n initialValues: initialRowValues,\n values: rowValues,\n },\n schema,\n snapshotKey: path,\n uiSchema,\n });\n const statusMessage = readFormDataSourceFieldStatusMessage(dataSourceState);\n const stateSignature = JSON.stringify({\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n selectionExpired: dataSourceState.selectionExpired === true,\n status: dataSourceState.status,\n });\n\n useEffect((): (() => void) | void => {\n if (!isFormDataSourceFieldDefinition(column)) {\n return;\n }\n\n onDataSourceStateChange?.(path, {\n hasValue: dataSourceState.hasValue,\n invalidValues: dataSourceState.invalidValues,\n selectionExpired: dataSourceState.selectionExpired === true,\n status: dataSourceState.status,\n });\n\n // A deleted row must stop blocking the submit it can no longer be fixed in.\n return (): void => {\n onDataSourceStateChange?.(path, {\n hasValue: false,\n invalidValues: [],\n status: 'IDLE',\n });\n };\n }, [onDataSourceStateChange, path, stateSignature]);\n\n return (\n <div data-form-field-key={path} style={TABLE_CELL_STYLE}>\n {renderControl(\n column,\n value,\n readonly,\n (_columnKey, nextValue): void => onChange(nextValue),\n undefined,\n dataSourceContext,\n dataSourceState,\n 'sub',\n )}\n {error ? (\n <Typography color=\"text-error\" variant=\"caption\">\n {error}\n </Typography>\n ) : null}\n {statusMessage ? (\n <div style={DATA_SOURCE_FEEDBACK_STYLE}>\n <Typography\n color={\n dataSourceState.status === 'INVALID' ||\n dataSourceState.status === 'UNAVAILABLE'\n ? 'text-error'\n : 'text-neutral'\n }\n variant=\"caption\"\n >\n {statusMessage}\n </Typography>\n {dataSourceState.status === 'UNAVAILABLE' &&\n dataSourceState.canRetry ? (\n <Button\n onClick={dataSourceState.retry}\n size=\"sub\"\n type=\"button\"\n variant=\"base-ghost\"\n >\n 重試\n </Button>\n ) : null}\n </div>\n ) : null}\n </div>\n );\n}\n\n/**\n * The row as it was loaded, used to tell a returned case's stored values from\n * edits. Absent when the table itself is new.\n */\nfunction readInitialTableRow(\n value: FormFieldValue | undefined,\n rowIndex: number,\n): FormTableRowValue | undefined {\n return readFormTableRows(value)[rowIndex];\n}\n\nfunction createRowId(sequenceRef: { current: number }): string {\n sequenceRef.current += 1;\n\n return `table-row-${sequenceRef.current}`;\n}\n\n/**\n * Clearing a cell removes the key instead of storing `undefined`: the row is\n * serialised to JSON, where `undefined` would vanish anyway, and an absent key\n * is what an untouched cell looks like.\n */\nfunction readNextTableRow(\n row: FormTableRowValue,\n columnKey: string,\n value: FormFieldValue | undefined,\n): FormTableRowValue {\n if (typeof value === 'undefined' || !isFormTableCellValue(value)) {\n return Object.fromEntries(\n Object.entries(row).filter(([key]) => key !== columnKey),\n );\n }\n\n return { ...row, [columnKey]: value };\n}\n\nfunction readTableCellValue(\n row: FormTableRowValue | undefined,\n columnKey: string,\n): FormFieldValue | undefined {\n if (!row || !Object.prototype.hasOwnProperty.call(row, columnKey)) {\n return undefined;\n }\n\n return row[columnKey];\n}\n\nfunction renderControl(\n field: FormFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n fileUpload: FormRendererFileUpload | undefined,\n dataSourceContext: FormRendererDataSourceContext | undefined,\n dataSourceState: FormDataSourceFieldState,\n size: ControlSize = 'main',\n): ReactElement {\n if (isFormDataSourceFieldDefinition(field)) {\n return renderDynamicOptionControl(\n field,\n value,\n readonly,\n onChange,\n dataSourceContext,\n dataSourceState,\n size,\n );\n }\n\n return renderStaticControl(\n field,\n value,\n readonly,\n onChange,\n fileUpload,\n size,\n );\n}\n\n/**\n * Every control that does not depend on a DataSource. Split out of\n * {@link renderControl} so a table cell can reuse the same controls without\n * carrying a field-level DataSource state it has no use for (ADR 16 §3.9).\n */\nfunction renderStaticControl(\n field: FormFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n fileUpload: FormRendererFileUpload | undefined,\n size: ControlSize = 'main',\n): ReactElement {\n const link =\n readonly &&\n (field.type === 'text' || field.type === 'textarea') &&\n field.displayAs === 'link'\n ? resolveFormFieldLink(value)\n : null;\n\n // Only a safe href becomes an anchor; anything else falls through to the\n // ordinary read-only control, i.e. plain text.\n if (link) {\n return (\n // Typography types its props as a paragraph's, so it cannot take `href`;\n // it supplies the brand colour and the anchor inherits it.\n <Typography color=\"text-brand\" component=\"span\" variant=\"body\">\n <a\n href={link.href}\n style={LINK_STYLE}\n {...(link.external\n ? {\n // The cue lives in attributes, not in a child node, so\n // selecting or copying the link yields just the URL.\n 'aria-label': `${link.href}${EXTERNAL_LINK_CUE}`,\n rel: 'noopener noreferrer',\n target: '_blank',\n title: EXTERNAL_LINK_TITLE,\n }\n : {})}\n >\n {link.href}\n </a>\n </Typography>\n );\n }\n\n if (field.type === 'textarea') {\n return (\n <Textarea\n onChange={(event: ChangeEvent<HTMLTextAreaElement>): void =>\n onChange(field.fieldKey, event.target.value)\n }\n placeholder={field.placeholder ?? '請輸入多行文字'}\n readOnly={readonly}\n ref={applyFullWidthTextareaHost}\n rows={3}\n style={TEXTAREA_STYLE}\n value={readStringValue(value)}\n />\n );\n }\n\n if (field.type === 'boolean') {\n return (\n <Toggle\n checked={value === true}\n disabled={readonly}\n label=\"啟用\"\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n onChange(field.fieldKey, event.target.checked)\n }\n size={size}\n />\n );\n }\n\n if (isFormStaticOptionFieldDefinition(field) && field.type === 'checkbox') {\n const options = field.options.map(readFieldOptionAsSelectOption);\n\n return (\n <Select\n menuMaxHeight={FORM_CONTROL_MENU_MAX_HEIGHT}\n mode=\"multiple\"\n onChange={(options): void =>\n onChange(\n field.fieldKey,\n options?.length\n ? options.map((option) => option.id)\n : undefined,\n )\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇一或多個選項'}\n readOnly={readonly}\n size={size}\n value={options.filter((option) =>\n readStringArrayValue(value).includes(option.id),\n )}\n />\n );\n }\n\n if (isFormStaticOptionFieldDefinition(field)) {\n const options = field.options.map(readFieldOptionAsSelectOption);\n\n return (\n <Select\n menuMaxHeight={FORM_CONTROL_MENU_MAX_HEIGHT}\n onChange={(option): void =>\n onChange(field.fieldKey, option?.id ?? undefined)\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\n size={size}\n value={readSelectOption(options, readStringValue(value))}\n />\n );\n }\n\n if (field.type === 'member') {\n return (\n <MemberFieldControl\n multiple={field.multiple === true}\n name={field.fieldKey}\n onChange={(nextValue): void => onChange(field.fieldKey, nextValue)}\n placeholder={field.placeholder}\n readonly={readonly}\n size={size}\n value={value}\n />\n );\n }\n\n if (field.type === 'org_unit') {\n return (\n <OrgUnitFieldControl\n name={field.fieldKey}\n onChange={(nextValue): void => onChange(field.fieldKey, nextValue)}\n placeholder={field.placeholder ?? readInputPlaceholder(field.type)}\n readonly={readonly}\n size={size}\n value={value}\n />\n );\n }\n\n if (field.type === 'file_upload') {\n return (\n <FileUploadFieldControl\n attachments={fileUpload?.attachments}\n field={field}\n onChange={(nextValue): void => onChange(field.fieldKey, nextValue)}\n onDownloadAttachment={fileUpload?.onDownloadAttachment}\n onStateChange={(state): void =>\n fileUpload?.onStateChange?.(field.fieldKey, state)\n }\n onUploadAttachment={fileUpload?.onUploadAttachment}\n readonly={readonly}\n value={value}\n />\n );\n }\n\n if (field.type === 'date') {\n return (\n <DatePicker\n format=\"YYYY-MM-DD\"\n fullWidth\n onChange={(nextValue): void =>\n onChange(field.fieldKey, formatDatePickerValue(nextValue))\n }\n placeholder={field.placeholder ?? readInputPlaceholder(field.type)}\n readOnly={readonly}\n value={readDatePickerValue(value)}\n />\n );\n }\n\n if (field.type === 'datetime') {\n return (\n <DateTimePicker\n formatDate=\"YYYY-MM-DD\"\n formatTime=\"HH:mm\"\n fullWidth\n hideSecond\n onChange={(nextValue): void =>\n onChange(field.fieldKey, formatDateTimePickerValue(nextValue))\n }\n placeholderLeft=\"選擇日期\"\n placeholderRight=\"選擇時間\"\n readOnly={readonly}\n value={readDatePickerValue(value)}\n />\n );\n }\n\n if (field.type === 'number' || field.type === 'money') {\n return (\n <Input\n fullWidth\n max={field.maximum}\n min={field.minimum}\n size={size}\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n onChange(\n field.fieldKey,\n clampOptionalNumber(parseOptionalNumberInput(event.target.value), {\n max: field.maximum,\n min: field.minimum,\n }),\n )\n }\n placeholder={field.placeholder ?? readInputPlaceholder(field.type)}\n {...(readonly ? { readonly: true as const } : {})}\n showSpinner\n value={readNumberInputValue(value)}\n variant=\"measure\"\n />\n );\n }\n\n return (\n <Input\n fullWidth\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n onChange(field.fieldKey, event.target.value)\n }\n placeholder={field.placeholder ?? readInputPlaceholder(field.type)}\n {...(readonly ? { readonly: true as const } : {})}\n size={size}\n value={readStringValue(value)}\n variant=\"base\"\n />\n );\n}\n\nfunction renderDynamicOptionControl(\n field: FormDataSourceOptionFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n dataSourceContext: FormRendererDataSourceContext | undefined,\n dataSourceState: FormDataSourceFieldState,\n size: ControlSize = 'main',\n): ReactElement {\n const options = dataSourceState.options.map(readFieldOptionAsSelectOption);\n const selectedOptions = readSelectedFormDataSourceOptions(\n value,\n dataSourceState.options,\n ).map(readFieldOptionAsSelectOption);\n const loading = dataSourceState.status === 'LOADING';\n // An async AutoComplete clears its list while loading; keep the loaded\n // options visible while the next page comes in.\n const autoCompleteLoading = loading && !dataSourceState.loadingNextPage;\n const waitingForDependencies =\n dataSourceState.status === 'WAITING_FOR_DEPENDENCIES';\n const interactiveSearch = Boolean(dataSourceContext) && !readonly;\n const optionControlDisabled = readonly || waitingForDependencies;\n // AutoComplete alone has no pre-query gate — it only learns a dependency is\n // missing from the answer to a search the filler already typed. Disabling it\n // at that point strands that text in a locked input with no way to clear it,\n // so it stays editable and reports the wait through the status line instead.\n const autoCompleteDisabled = readonly;\n\n if (field.type === 'autocomplete') {\n if (readFormFieldSelectionMode(field) === 'multiple') {\n return (\n <AutoComplete\n asyncData={interactiveSearch}\n clearSearchText\n disabled={autoCompleteDisabled}\n disabledOptionsFilter={interactiveSearch}\n globalPortal\n loading={autoCompleteLoading}\n menuMaxHeight={FORM_CONTROL_MENU_MAX_HEIGHT}\n mode=\"multiple\"\n onChange={(nextOptions): void =>\n onChange(\n field.fieldKey,\n nextOptions.length\n ? nextOptions.map((option) => option.id)\n : undefined,\n )\n }\n onReachBottom={dataSourceState.onReachBottom}\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n onVisibilityChange={(open: boolean): void => {\n if (open && interactiveSearch) {\n dataSourceState.onOpen();\n }\n }}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\n size={size}\n value={selectedOptions}\n />\n );\n }\n\n return (\n <AutoComplete\n asyncData={interactiveSearch}\n clearSearchText\n disabled={autoCompleteDisabled}\n disabledOptionsFilter={interactiveSearch}\n globalPortal\n loading={autoCompleteLoading}\n menuMaxHeight={FORM_CONTROL_MENU_MAX_HEIGHT}\n mode=\"single\"\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={dataSourceState.onReachBottom}\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n onVisibilityChange={(open: boolean): void => {\n if (open && interactiveSearch) {\n dataSourceState.onOpen();\n }\n }}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\n size={size}\n value={selectedOptions[0] ?? null}\n />\n );\n }\n\n if (field.type === 'radio') {\n return (\n <RadioGroup\n disabled={optionControlDisabled}\n name={field.fieldKey}\n onChange={(event): void =>\n onChange(field.fieldKey, event.target.value || undefined)\n }\n options={options}\n value={readStringValue(value)}\n />\n );\n }\n\n if (field.type === 'checkbox') {\n return (\n <CheckboxGroup\n disabled={optionControlDisabled}\n name={field.fieldKey}\n onChange={(event): void =>\n onChange(\n field.fieldKey,\n event.target.values.length ? event.target.values : undefined,\n )\n }\n options={options.map((option) => ({\n label: option.name,\n value: option.id,\n }))}\n value={[...readStringArrayValue(value)]}\n />\n );\n }\n\n if (readFormFieldSelectionMode(field) === 'multiple') {\n return (\n <Select\n disabled={optionControlDisabled}\n loading={loading}\n menuMaxHeight={FORM_CONTROL_MENU_MAX_HEIGHT}\n mode=\"multiple\"\n onChange={(nextOptions): void =>\n onChange(\n field.fieldKey,\n nextOptions.length\n ? nextOptions.map((option) => option.id)\n : undefined,\n )\n }\n onReachBottom={dataSourceState.onReachBottom}\n options={options}\n placeholder={field.placeholder ?? '請選擇一或多個選項'}\n readOnly={readonly}\n size={size}\n value={selectedOptions}\n />\n );\n }\n\n return (\n <Select\n disabled={optionControlDisabled}\n loading={loading}\n menuMaxHeight={FORM_CONTROL_MENU_MAX_HEIGHT}\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={dataSourceState.onReachBottom}\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\n size={size}\n value={selectedOptions[0] ?? null}\n />\n );\n}\n\nfunction readFieldColumnSpan(\n field: FormFieldDefinition,\n uiSchema: FormUiSchema,\n): number {\n const width =\n uiSchema.layout.find((item) => item.fieldKey === field.fieldKey)?.width ??\n 'FULL';\n const spans: Readonly<Record<typeof width, number>> = {\n FULL: 12,\n HALF: 6,\n THIRD: 4,\n };\n\n return spans[width];\n}\n\nfunction readStringValue(value: FormFieldValue | undefined): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction readNumberInputValue(value: FormFieldValue | undefined): string {\n return typeof value === 'number' ? String(value) : '';\n}\n\nfunction readInputPlaceholder(type: FormFieldDefinition['type']): string {\n const placeholders: Readonly<Record<FormFieldDefinition['type'], string>> = {\n boolean: '',\n autocomplete: '請輸入或選擇',\n checkbox: '請選擇一或多個選項',\n date: '請選擇日期',\n datetime: '請選擇日期與時間',\n file_upload: '請上傳附件',\n member: '搜尋姓名或信箱',\n money: '請輸入金額',\n number: '請輸入數字',\n org_unit: '搜尋組織名稱',\n radio: '請選擇一個選項',\n select: '請選擇一個選項',\n table: '',\n text: '請輸入文字',\n textarea: '請輸入多行文字',\n };\n\n return placeholders[type];\n}\n\nexport { FormRenderer as FormRendererView };\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-pickers-BjbkBpSJ.cjs","names":["useState","searchMembers","styles","AutoComplete","useMemo","Select","formatMemberLabel","require_format_member_label","react","_mezzanine_ui_react","_rytass_bpm_core_client","react_jsx_runtime","readOrgUnitOption","orgUnit","readPositionOption","position","admin_pickers_module_default","PICKER_MENU_MAX_HEIGHT","MemberPicker","loading","setLoading","options","setOptions","value","handleSearch","searchText","readMemberOption","onChange","readMemberOptionFromValue","option","readUniqueOption","open","mergeMemberOptions","OrgUnitPicker","orgUnits","readOrgUnitOptionFromValue","PositionPicker","positions","readPositionOptionFromValue","member","isRecord","email","id","name","normalized","matched","selected"],"sources":["../../src/components/admin-picker-options.ts","../../src/components/admin-pickers.module.scss","../../src/components/admin-pickers.tsx"],"sourcesContent":["import type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\n\nexport type OrgUnitOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport type PositionOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport function readOrgUnitOption(orgUnit: OrgUnitRecord): OrgUnitOption {\n return { id: orgUnit.id, name: `${orgUnit.name} · ${orgUnit.code}` };\n}\n\nexport function readPositionOption(position: PositionRecord): PositionOption {\n return { id: position.id, name: `${position.name} · ${position.code}` };\n}\n",".pickerHost {\n width: 100%;\n min-width: 0;\n\n :global(.mzn-auto-complete),\n :global(.mzn-input),\n :global(.mzn-input-container),\n :global(.mzn-select),\n :global(.mzn-select-trigger),\n :global(.mzn-text-field) {\n width: 100%;\n min-width: 0 !important;\n }\n}\n","'use client';\n\nimport { useMemo, useState, type ReactElement } from 'react';\nimport { AutoComplete, Select } from '@mezzanine-ui/react';\nimport { searchMembers } from '@rytass/bpm-core-client';\nimport type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\nimport { formatMemberLabel } from '../lib/format-member-label';\nimport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n} from './admin-picker-options';\nimport styles from './admin-pickers.module.scss';\n\nexport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n};\n\nexport type MemberOption = Readonly<{\n email: string;\n id: string;\n name: string;\n}>;\n\n// Without a cap the Mezzanine dropdown grows with its options and never\n// mounts its scrollbar, so a long org-unit or position list runs off the\n// viewport with no way to reach the items below the fold. The pickers also\n// set `flip`: near the bottom of a modal the capped menu still overflows\n// unless it may open upwards.\nconst PICKER_MENU_MAX_HEIGHT = 320;\n\ninterface MemberPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: MemberOption | null) => void;\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: MemberOption | null;\n}\n\ninterface OrgUnitPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: OrgUnitOption | null) => void;\n readonly orgUnits: readonly OrgUnitRecord[];\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: OrgUnitOption | null;\n}\n\ninterface PositionPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: PositionOption | null) => void;\n readonly placeholder?: string;\n readonly positions: readonly PositionRecord[];\n readonly size?: 'main' | 'sub';\n readonly value: PositionOption | null;\n}\n\n/**\n * AutoComplete-backed picker that searches the BPM member directory via\n * `searchMembers()`. Emits a {@link MemberOption} `{ id, name, email }`\n * on selection (or `null` when cleared).\n */\nexport function MemberPicker({\n disabled = false,\n name,\n onChange,\n placeholder = '搜尋姓名或信箱',\n size,\n value,\n}: MemberPickerProps): ReactElement {\n const [loading, setLoading] = useState(false);\n const [options, setOptions] = useState<readonly MemberOption[]>(\n value ? [value] : [],\n );\n\n async function handleSearch(searchText: string): Promise<void> {\n setLoading(true);\n try {\n setOptions((await searchMembers(searchText)).map(readMemberOption));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className={styles.pickerHost}>\n <AutoComplete\n asyncData\n disabled={disabled}\n disabledOptionsFilter\n emptyText=\"沒有符合的會員\"\n inputProps={{\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n }}\n loading={loading}\n loadingText=\"搜尋會員中...\"\n mode=\"single\"\n onChange={(option): void => onChange(readMemberOptionFromValue(option))}\n onSearch={handleSearch}\n onSearchTextChange={(searchText): void =>\n onChange(readUniqueOption(searchText, options))\n }\n onVisibilityChange={(open): void => {\n if (open) {\n void handleSearch('');\n }\n }}\n options={[...mergeMemberOptions(value ? [value] : [], options)]}\n placeholder={placeholder}\n searchDebounceTime={300}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of org units. Caller is\n * responsible for fetching the org units (typically via\n * `readOrganizationDashboard` from `@rytass/bpm-core-client/organization`).\n */\nexport function OrgUnitPicker({\n disabled = false,\n onChange,\n orgUnits,\n placeholder = '選擇組織',\n size,\n value,\n}: OrgUnitPickerProps): ReactElement {\n const options = useMemo(\n (): readonly OrgUnitOption[] => orgUnits.map(readOrgUnitOption),\n [orgUnits],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readOrgUnitOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of positions.\n */\nexport function PositionPicker({\n disabled = false,\n onChange,\n placeholder = '選擇職位',\n positions,\n size,\n value,\n}: PositionPickerProps): ReactElement {\n const options = useMemo(\n (): readonly PositionOption[] => positions.map(readPositionOption),\n [positions],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readPositionOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\nexport function readMemberOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberOption {\n return {\n email: member.email,\n id: member.memberId,\n name: formatMemberLabel(member.name, member.email, 'dot'),\n };\n}\n\nfunction readMemberOptionFromValue(value: unknown): MemberOption | null {\n if (!isRecord(value)) return null;\n const email = value.email;\n const id = value.id;\n const name = value.name;\n return typeof email === 'string' &&\n typeof id === 'string' &&\n typeof name === 'string'\n ? { email, id, name }\n : null;\n}\n\nfunction readOrgUnitOptionFromValue(value: unknown): OrgUnitOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readPositionOptionFromValue(value: unknown): PositionOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readUniqueOption(\n searchText: string,\n options: readonly MemberOption[],\n): MemberOption | null {\n const normalized = searchText.trim().toLocaleLowerCase();\n if (!normalized) return null;\n const matched = options.filter(\n (option) =>\n option.id.toLocaleLowerCase() === normalized ||\n option.email.toLocaleLowerCase() === normalized ||\n option.name.toLocaleLowerCase() === normalized,\n );\n return matched.length === 1 ? matched[0] : null;\n}\n\nfunction mergeMemberOptions(\n selected: readonly MemberOption[],\n options: readonly MemberOption[],\n): readonly MemberOption[] {\n const byId = new Map(\n [...selected, ...options].map(\n (option): readonly [string, MemberOption] => [option.id, option],\n ),\n );\n return [...byId.values()];\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin-pickers-BjbkBpSJ.cjs","names":["useState","searchMembers","styles","AutoComplete","useMemo","Select","formatMemberLabel","require_format_member_label","react","_mezzanine_ui_react","_rytass_bpm_core_client","react_jsx_runtime","readOrgUnitOption","orgUnit","readPositionOption","position","admin_pickers_module_default","PICKER_MENU_MAX_HEIGHT","MemberPicker","loading","setLoading","options","setOptions","value","handleSearch","searchText","readMemberOption","onChange","readMemberOptionFromValue","option","readUniqueOption","open","mergeMemberOptions","OrgUnitPicker","orgUnits","readOrgUnitOptionFromValue","PositionPicker","positions","readPositionOptionFromValue","member","isRecord","email","id","name","normalized","matched","selected"],"sources":["../../src/components/admin-picker-options.ts","../../src/components/admin-pickers.tsx"],"sourcesContent":["import type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\n\nexport type OrgUnitOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport type PositionOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport function readOrgUnitOption(orgUnit: OrgUnitRecord): OrgUnitOption {\n return { id: orgUnit.id, name: `${orgUnit.name} · ${orgUnit.code}` };\n}\n\nexport function readPositionOption(position: PositionRecord): PositionOption {\n return { id: position.id, name: `${position.name} · ${position.code}` };\n}\n","'use client';\n\nimport { useMemo, useState, type ReactElement } from 'react';\nimport { AutoComplete, Select } from '@mezzanine-ui/react';\nimport { searchMembers } from '@rytass/bpm-core-client';\nimport type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\nimport { formatMemberLabel } from '../lib/format-member-label';\nimport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n} from './admin-picker-options';\nimport styles from './admin-pickers.module.scss';\n\nexport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n};\n\nexport type MemberOption = Readonly<{\n email: string;\n id: string;\n name: string;\n}>;\n\n// Without a cap the Mezzanine dropdown grows with its options and never\n// mounts its scrollbar, so a long org-unit or position list runs off the\n// viewport with no way to reach the items below the fold. The pickers also\n// set `flip`: near the bottom of a modal the capped menu still overflows\n// unless it may open upwards.\nconst PICKER_MENU_MAX_HEIGHT = 320;\n\ninterface MemberPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: MemberOption | null) => void;\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: MemberOption | null;\n}\n\ninterface OrgUnitPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: OrgUnitOption | null) => void;\n readonly orgUnits: readonly OrgUnitRecord[];\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: OrgUnitOption | null;\n}\n\ninterface PositionPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: PositionOption | null) => void;\n readonly placeholder?: string;\n readonly positions: readonly PositionRecord[];\n readonly size?: 'main' | 'sub';\n readonly value: PositionOption | null;\n}\n\n/**\n * AutoComplete-backed picker that searches the BPM member directory via\n * `searchMembers()`. Emits a {@link MemberOption} `{ id, name, email }`\n * on selection (or `null` when cleared).\n */\nexport function MemberPicker({\n disabled = false,\n name,\n onChange,\n placeholder = '搜尋姓名或信箱',\n size,\n value,\n}: MemberPickerProps): ReactElement {\n const [loading, setLoading] = useState(false);\n const [options, setOptions] = useState<readonly MemberOption[]>(\n value ? [value] : [],\n );\n\n async function handleSearch(searchText: string): Promise<void> {\n setLoading(true);\n try {\n setOptions((await searchMembers(searchText)).map(readMemberOption));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className={styles.pickerHost}>\n <AutoComplete\n asyncData\n disabled={disabled}\n disabledOptionsFilter\n emptyText=\"沒有符合的會員\"\n inputProps={{\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n }}\n loading={loading}\n loadingText=\"搜尋會員中...\"\n mode=\"single\"\n onChange={(option): void => onChange(readMemberOptionFromValue(option))}\n onSearch={handleSearch}\n onSearchTextChange={(searchText): void =>\n onChange(readUniqueOption(searchText, options))\n }\n onVisibilityChange={(open): void => {\n if (open) {\n void handleSearch('');\n }\n }}\n options={[...mergeMemberOptions(value ? [value] : [], options)]}\n placeholder={placeholder}\n searchDebounceTime={300}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of org units. Caller is\n * responsible for fetching the org units (typically via\n * `readOrganizationDashboard` from `@rytass/bpm-core-client/organization`).\n */\nexport function OrgUnitPicker({\n disabled = false,\n onChange,\n orgUnits,\n placeholder = '選擇組織',\n size,\n value,\n}: OrgUnitPickerProps): ReactElement {\n const options = useMemo(\n (): readonly OrgUnitOption[] => orgUnits.map(readOrgUnitOption),\n [orgUnits],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readOrgUnitOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of positions.\n */\nexport function PositionPicker({\n disabled = false,\n onChange,\n placeholder = '選擇職位',\n positions,\n size,\n value,\n}: PositionPickerProps): ReactElement {\n const options = useMemo(\n (): readonly PositionOption[] => positions.map(readPositionOption),\n [positions],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readPositionOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\nexport function readMemberOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberOption {\n return {\n email: member.email,\n id: member.memberId,\n name: formatMemberLabel(member.name, member.email, 'dot'),\n };\n}\n\nfunction readMemberOptionFromValue(value: unknown): MemberOption | null {\n if (!isRecord(value)) return null;\n const email = value.email;\n const id = value.id;\n const name = value.name;\n return typeof email === 'string' &&\n typeof id === 'string' &&\n typeof name === 'string'\n ? { email, id, name }\n : null;\n}\n\nfunction readOrgUnitOptionFromValue(value: unknown): OrgUnitOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readPositionOptionFromValue(value: unknown): PositionOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readUniqueOption(\n searchText: string,\n options: readonly MemberOption[],\n): MemberOption | null {\n const normalized = searchText.trim().toLocaleLowerCase();\n if (!normalized) return null;\n const matched = options.filter(\n (option) =>\n option.id.toLocaleLowerCase() === normalized ||\n option.email.toLocaleLowerCase() === normalized ||\n option.name.toLocaleLowerCase() === normalized,\n );\n return matched.length === 1 ? matched[0] : null;\n}\n\nfunction mergeMemberOptions(\n selected: readonly MemberOption[],\n options: readonly MemberOption[],\n): readonly MemberOption[] {\n const byId = new Map(\n [...selected, ...options].map(\n (option): readonly [string, MemberOption] => [option.id, option],\n ),\n );\n return [...byId.values()];\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"A,+LAeA,SAAgBY,EAAkB,EAAuC,CACvE,MAAO,CAAE,GAAIC,EAAQ,GAAI,KAAM,GAAGA,EAAQ,KAAK,KAAKA,EAAQ,MAAO,CACrE,CAEA,SAAgBC,EAAmB,EAA0C,CAC3E,MAAO,CAAE,GAAIC,EAAS,GAAI,KAAM,GAAGA,EAAS,KAAK,KAAKA,EAAS,MAAO,CACxE,C,0CCeME,EAAyB,IAoC/B,SAAgBC,EAAa,CAC3B,WAAW,GACX,OACA,WACA,cAAc,UACd,OACA,SACkC,CAClC,GAAM,CAACC,EAASC,IAAA,EAAcpB,EAAAA,SAAAA,CAAS,EAAK,EACtC,CAACqB,EAASC,IAAA,EAActB,EAAAA,SAAAA,CAC5BuB,EAAQ,CAACA,CAAK,EAAI,CAAC,CACrB,EAEA,eAAeC,EAAa,EAAmC,CAC7DJ,EAAW,EAAI,EACf,GAAI,CACFE,GAAY,MAAA,EAAMrB,EAAAA,cAAAA,CAAcwB,CAAU,EAAA,CAAG,IAAIC,CAAgB,CAAC,CACpE,QAAU,CACRN,EAAW,EAAK,CAClB,CACF,CAEA,OACE,EAAAT,EAAA,IAAA,CAAC,MAAD,CAAK,UAAWT,EAAO,WACrB,UAAA,EAAAS,EAAA,IAAA,CAACR,EAAAA,aAAD,CACE,UAAA,GACU,WACV,sBAAA,GACA,UAAU,UACV,WAAY,CACV,eAAgB,OAChB,YAAa,MACb,OACA,WAAY,EACd,EACS,UACT,YAAY,WACZ,KAAK,SACL,SAAW,GAAiBwB,EAASC,EAA0BC,CAAM,CAAC,EACtE,SAAUL,EACV,mBAAqB,GACnBG,EAASG,EAAiBL,EAAYJ,CAAO,CAAC,EAEhD,mBAAqB,GAAe,CAC9BU,GACFP,EAAkB,EAAE,CAExB,EACA,QAAS,CAAC,GAAGQ,EAAmBT,EAAQ,CAACA,CAAK,EAAI,CAAC,EAAGF,CAAO,CAAC,EACjD,cACb,mBAAoB,IACd,OACC,OACR,CAAA,CACE,CAAA,CAET,CAOA,SAAgBY,EAAc,CAC5B,WAAW,GACX,WACA,WACA,cAAc,OACd,OACA,SACmC,CACnC,IAAMZ,GAAA,EAAUjB,EAAAA,QAAAA,KACkB8B,EAAS,IAAItB,CAAiB,EAC9D,CAACsB,CAAQ,CACX,EACA,OACE,EAAAvB,EAAA,IAAA,CAAC,MAAD,CAAK,UAAWT,EAAO,WACrB,UAAA,EAAAS,EAAA,IAAA,CAACN,EAAAA,OAAD,CACE,UAAA,GACU,WACV,KAAA,GACA,UAAA,GACA,cAAeY,EACf,SAAW,GAAiBU,EAASQ,EAA2BN,CAAM,CAAC,EACvE,QAAS,CAAC,GAAGR,CAAO,EACP,cACP,OACC,OACR,CAAA,CACE,CAAA,CAET,CAKA,SAAgBe,EAAe,CAC7B,WAAW,GACX,WACA,cAAc,OACd,YACA,OACA,SACoC,CACpC,IAAMf,GAAA,EAAUjB,EAAAA,QAAAA,KACmBiC,EAAU,IAAIvB,CAAkB,EACjE,CAACuB,CAAS,CACZ,EACA,OACE,EAAA1B,EAAA,IAAA,CAAC,MAAD,CAAK,UAAWT,EAAO,WACrB,UAAA,EAAAS,EAAA,IAAA,CAACN,EAAAA,OAAD,CACE,UAAA,GACU,WACV,KAAA,GACA,UAAA,GACA,cAAeY,EACf,SAAW,GAAiBU,EAASW,EAA4BT,CAAM,CAAC,EACxE,QAAS,CAAC,GAAGR,CAAO,EACP,cACP,OACC,OACR,CAAA,CACE,CAAA,CAET,CAEA,SAAgBK,EAAiB,EAIhB,CACf,MAAO,CACL,MAAOa,EAAO,MACd,GAAIA,EAAO,SACX,KAAMjC,EAAAA,EAAkBiC,EAAO,KAAMA,EAAO,MAAO,KAAK,CAC1D,CACF,CAEA,SAASX,EAA0B,EAAqC,CACtE,GAAI,CAACY,EAASjB,CAAK,EAAG,OAAO,KAC7B,IAAMkB,EAAQlB,EAAM,MACdmB,EAAKnB,EAAM,GACXoB,EAAOpB,EAAM,KACnB,OAAO,OAAOkB,GAAU,UACtB,OAAOC,GAAO,UACd,OAAOC,GAAS,SACd,CAAE,QAAO,KAAI,MAAK,EAClB,IACN,CAEA,SAASR,EAA2B,EAAsC,CACxE,GAAI,CAACK,EAASjB,CAAK,EAAG,OAAO,KAC7B,IAAMmB,EAAKnB,EAAM,GACXoB,EAAOpB,EAAM,KACnB,OAAO,OAAOmB,GAAO,UAAY,OAAOC,GAAS,SAC7C,CAAE,KAAI,MAAK,EACX,IACN,CAEA,SAASL,EAA4B,EAAuC,CAC1E,GAAI,CAACE,EAASjB,CAAK,EAAG,OAAO,KAC7B,IAAMmB,EAAKnB,EAAM,GACXoB,EAAOpB,EAAM,KACnB,OAAO,OAAOmB,GAAO,UAAY,OAAOC,GAAS,SAC7C,CAAE,KAAI,MAAK,EACX,IACN,CAEA,SAASb,EACP,EACA,EACqB,CACrB,IAAMc,EAAanB,EAAW,KAAK,CAAC,CAAC,kBAAkB,EACvD,GAAI,CAACmB,EAAY,OAAO,KACxB,IAAMC,EAAUxB,EAAQ,OACrB,GACCQ,EAAO,GAAG,kBAAkB,IAAMe,GAClCf,EAAO,MAAM,kBAAkB,IAAMe,GACrCf,EAAO,KAAK,kBAAkB,IAAMe,CACxC,EACA,OAAOC,EAAQ,SAAW,EAAIA,EAAQ,GAAK,IAC7C,CAEA,SAASb,EACP,EACA,EACyB,CAMzB,MAAO,CAAC,GAAG,IALM,IACf,CAAC,GAAGc,EAAU,GAAGzB,CAAO,CAAC,CAAC,IACvB,GAA4C,CAACQ,EAAO,GAAIA,CAAM,CACjE,CAES,CAAA,CAAK,OAAO,CAAC,CAC1B,CAEA,SAASW,EAAS,EAA4D,CAC5E,OAAO,OAAOjB,GAAU,YAAYA,CACtC,C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-pickers-DfGxsjpp.js","names":["styles","formatMemberLabel","readOrgUnitOption","orgUnit","readPositionOption","position","admin_pickers_module_default","PICKER_MENU_MAX_HEIGHT","MemberPicker","loading","setLoading","useState","options","setOptions","value","handleSearch","searchMembers","searchText","readMemberOption","AutoComplete","onChange","readMemberOptionFromValue","option","readUniqueOption","open","mergeMemberOptions","OrgUnitPicker","useMemo","orgUnits","Select","readOrgUnitOptionFromValue","PositionPicker","positions","readPositionOptionFromValue","member","isRecord","email","id","name","normalized","matched","selected"],"sources":["../../src/components/admin-picker-options.ts","../../src/components/admin-pickers.module.scss","../../src/components/admin-pickers.tsx"],"sourcesContent":["import type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\n\nexport type OrgUnitOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport type PositionOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport function readOrgUnitOption(orgUnit: OrgUnitRecord): OrgUnitOption {\n return { id: orgUnit.id, name: `${orgUnit.name} · ${orgUnit.code}` };\n}\n\nexport function readPositionOption(position: PositionRecord): PositionOption {\n return { id: position.id, name: `${position.name} · ${position.code}` };\n}\n",".pickerHost {\n width: 100%;\n min-width: 0;\n\n :global(.mzn-auto-complete),\n :global(.mzn-input),\n :global(.mzn-input-container),\n :global(.mzn-select),\n :global(.mzn-select-trigger),\n :global(.mzn-text-field) {\n width: 100%;\n min-width: 0 !important;\n }\n}\n","'use client';\n\nimport { useMemo, useState, type ReactElement } from 'react';\nimport { AutoComplete, Select } from '@mezzanine-ui/react';\nimport { searchMembers } from '@rytass/bpm-core-client';\nimport type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\nimport { formatMemberLabel } from '../lib/format-member-label';\nimport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n} from './admin-picker-options';\nimport styles from './admin-pickers.module.scss';\n\nexport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n};\n\nexport type MemberOption = Readonly<{\n email: string;\n id: string;\n name: string;\n}>;\n\n// Without a cap the Mezzanine dropdown grows with its options and never\n// mounts its scrollbar, so a long org-unit or position list runs off the\n// viewport with no way to reach the items below the fold. The pickers also\n// set `flip`: near the bottom of a modal the capped menu still overflows\n// unless it may open upwards.\nconst PICKER_MENU_MAX_HEIGHT = 320;\n\ninterface MemberPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: MemberOption | null) => void;\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: MemberOption | null;\n}\n\ninterface OrgUnitPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: OrgUnitOption | null) => void;\n readonly orgUnits: readonly OrgUnitRecord[];\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: OrgUnitOption | null;\n}\n\ninterface PositionPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: PositionOption | null) => void;\n readonly placeholder?: string;\n readonly positions: readonly PositionRecord[];\n readonly size?: 'main' | 'sub';\n readonly value: PositionOption | null;\n}\n\n/**\n * AutoComplete-backed picker that searches the BPM member directory via\n * `searchMembers()`. Emits a {@link MemberOption} `{ id, name, email }`\n * on selection (or `null` when cleared).\n */\nexport function MemberPicker({\n disabled = false,\n name,\n onChange,\n placeholder = '搜尋姓名或信箱',\n size,\n value,\n}: MemberPickerProps): ReactElement {\n const [loading, setLoading] = useState(false);\n const [options, setOptions] = useState<readonly MemberOption[]>(\n value ? [value] : [],\n );\n\n async function handleSearch(searchText: string): Promise<void> {\n setLoading(true);\n try {\n setOptions((await searchMembers(searchText)).map(readMemberOption));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className={styles.pickerHost}>\n <AutoComplete\n asyncData\n disabled={disabled}\n disabledOptionsFilter\n emptyText=\"沒有符合的會員\"\n inputProps={{\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n }}\n loading={loading}\n loadingText=\"搜尋會員中...\"\n mode=\"single\"\n onChange={(option): void => onChange(readMemberOptionFromValue(option))}\n onSearch={handleSearch}\n onSearchTextChange={(searchText): void =>\n onChange(readUniqueOption(searchText, options))\n }\n onVisibilityChange={(open): void => {\n if (open) {\n void handleSearch('');\n }\n }}\n options={[...mergeMemberOptions(value ? [value] : [], options)]}\n placeholder={placeholder}\n searchDebounceTime={300}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of org units. Caller is\n * responsible for fetching the org units (typically via\n * `readOrganizationDashboard` from `@rytass/bpm-core-client/organization`).\n */\nexport function OrgUnitPicker({\n disabled = false,\n onChange,\n orgUnits,\n placeholder = '選擇組織',\n size,\n value,\n}: OrgUnitPickerProps): ReactElement {\n const options = useMemo(\n (): readonly OrgUnitOption[] => orgUnits.map(readOrgUnitOption),\n [orgUnits],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readOrgUnitOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of positions.\n */\nexport function PositionPicker({\n disabled = false,\n onChange,\n placeholder = '選擇職位',\n positions,\n size,\n value,\n}: PositionPickerProps): ReactElement {\n const options = useMemo(\n (): readonly PositionOption[] => positions.map(readPositionOption),\n [positions],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readPositionOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\nexport function readMemberOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberOption {\n return {\n email: member.email,\n id: member.memberId,\n name: formatMemberLabel(member.name, member.email, 'dot'),\n };\n}\n\nfunction readMemberOptionFromValue(value: unknown): MemberOption | null {\n if (!isRecord(value)) return null;\n const email = value.email;\n const id = value.id;\n const name = value.name;\n return typeof email === 'string' &&\n typeof id === 'string' &&\n typeof name === 'string'\n ? { email, id, name }\n : null;\n}\n\nfunction readOrgUnitOptionFromValue(value: unknown): OrgUnitOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readPositionOptionFromValue(value: unknown): PositionOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readUniqueOption(\n searchText: string,\n options: readonly MemberOption[],\n): MemberOption | null {\n const normalized = searchText.trim().toLocaleLowerCase();\n if (!normalized) return null;\n const matched = options.filter(\n (option) =>\n option.id.toLocaleLowerCase() === normalized ||\n option.email.toLocaleLowerCase() === normalized ||\n option.name.toLocaleLowerCase() === normalized,\n );\n return matched.length === 1 ? matched[0] : null;\n}\n\nfunction mergeMemberOptions(\n selected: readonly MemberOption[],\n options: readonly MemberOption[],\n): readonly MemberOption[] {\n const byId = new Map(\n [...selected, ...options].map(\n (option): readonly [string, MemberOption] => [option.id, option],\n ),\n );\n return [...byId.values()];\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"admin-pickers-DfGxsjpp.js","names":["styles","formatMemberLabel","readOrgUnitOption","orgUnit","readPositionOption","position","admin_pickers_module_default","PICKER_MENU_MAX_HEIGHT","MemberPicker","loading","setLoading","useState","options","setOptions","value","handleSearch","searchMembers","searchText","readMemberOption","AutoComplete","onChange","readMemberOptionFromValue","option","readUniqueOption","open","mergeMemberOptions","OrgUnitPicker","useMemo","orgUnits","Select","readOrgUnitOptionFromValue","PositionPicker","positions","readPositionOptionFromValue","member","isRecord","email","id","name","normalized","matched","selected"],"sources":["../../src/components/admin-picker-options.ts","../../src/components/admin-pickers.tsx"],"sourcesContent":["import type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\n\nexport type OrgUnitOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport type PositionOption = Readonly<{\n id: string;\n name: string;\n}>;\n\nexport function readOrgUnitOption(orgUnit: OrgUnitRecord): OrgUnitOption {\n return { id: orgUnit.id, name: `${orgUnit.name} · ${orgUnit.code}` };\n}\n\nexport function readPositionOption(position: PositionRecord): PositionOption {\n return { id: position.id, name: `${position.name} · ${position.code}` };\n}\n","'use client';\n\nimport { useMemo, useState, type ReactElement } from 'react';\nimport { AutoComplete, Select } from '@mezzanine-ui/react';\nimport { searchMembers } from '@rytass/bpm-core-client';\nimport type {\n OrgUnitRecord,\n PositionRecord,\n} from '@rytass/bpm-core-client/organization';\nimport { formatMemberLabel } from '../lib/format-member-label';\nimport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n} from './admin-picker-options';\nimport styles from './admin-pickers.module.scss';\n\nexport {\n type OrgUnitOption,\n type PositionOption,\n readOrgUnitOption,\n readPositionOption,\n};\n\nexport type MemberOption = Readonly<{\n email: string;\n id: string;\n name: string;\n}>;\n\n// Without a cap the Mezzanine dropdown grows with its options and never\n// mounts its scrollbar, so a long org-unit or position list runs off the\n// viewport with no way to reach the items below the fold. The pickers also\n// set `flip`: near the bottom of a modal the capped menu still overflows\n// unless it may open upwards.\nconst PICKER_MENU_MAX_HEIGHT = 320;\n\ninterface MemberPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: MemberOption | null) => void;\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: MemberOption | null;\n}\n\ninterface OrgUnitPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: OrgUnitOption | null) => void;\n readonly orgUnits: readonly OrgUnitRecord[];\n readonly placeholder?: string;\n readonly size?: 'main' | 'sub';\n readonly value: OrgUnitOption | null;\n}\n\ninterface PositionPickerProps {\n readonly disabled?: boolean;\n readonly name: string;\n readonly onChange: (option: PositionOption | null) => void;\n readonly placeholder?: string;\n readonly positions: readonly PositionRecord[];\n readonly size?: 'main' | 'sub';\n readonly value: PositionOption | null;\n}\n\n/**\n * AutoComplete-backed picker that searches the BPM member directory via\n * `searchMembers()`. Emits a {@link MemberOption} `{ id, name, email }`\n * on selection (or `null` when cleared).\n */\nexport function MemberPicker({\n disabled = false,\n name,\n onChange,\n placeholder = '搜尋姓名或信箱',\n size,\n value,\n}: MemberPickerProps): ReactElement {\n const [loading, setLoading] = useState(false);\n const [options, setOptions] = useState<readonly MemberOption[]>(\n value ? [value] : [],\n );\n\n async function handleSearch(searchText: string): Promise<void> {\n setLoading(true);\n try {\n setOptions((await searchMembers(searchText)).map(readMemberOption));\n } finally {\n setLoading(false);\n }\n }\n\n return (\n <div className={styles.pickerHost}>\n <AutoComplete\n asyncData\n disabled={disabled}\n disabledOptionsFilter\n emptyText=\"沒有符合的會員\"\n inputProps={{\n autoCapitalize: 'none',\n autoCorrect: 'off',\n name,\n spellCheck: false,\n }}\n loading={loading}\n loadingText=\"搜尋會員中...\"\n mode=\"single\"\n onChange={(option): void => onChange(readMemberOptionFromValue(option))}\n onSearch={handleSearch}\n onSearchTextChange={(searchText): void =>\n onChange(readUniqueOption(searchText, options))\n }\n onVisibilityChange={(open): void => {\n if (open) {\n void handleSearch('');\n }\n }}\n options={[...mergeMemberOptions(value ? [value] : [], options)]}\n placeholder={placeholder}\n searchDebounceTime={300}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of org units. Caller is\n * responsible for fetching the org units (typically via\n * `readOrganizationDashboard` from `@rytass/bpm-core-client/organization`).\n */\nexport function OrgUnitPicker({\n disabled = false,\n onChange,\n orgUnits,\n placeholder = '選擇組織',\n size,\n value,\n}: OrgUnitPickerProps): ReactElement {\n const options = useMemo(\n (): readonly OrgUnitOption[] => orgUnits.map(readOrgUnitOption),\n [orgUnits],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readOrgUnitOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\n/**\n * Static `<Select>` over a pre-fetched list of positions.\n */\nexport function PositionPicker({\n disabled = false,\n onChange,\n placeholder = '選擇職位',\n positions,\n size,\n value,\n}: PositionPickerProps): ReactElement {\n const options = useMemo(\n (): readonly PositionOption[] => positions.map(readPositionOption),\n [positions],\n );\n return (\n <div className={styles.pickerHost}>\n <Select\n clearable\n disabled={disabled}\n flip\n fullWidth\n menuMaxHeight={PICKER_MENU_MAX_HEIGHT}\n onChange={(option): void => onChange(readPositionOptionFromValue(option))}\n options={[...options]}\n placeholder={placeholder}\n size={size}\n value={value}\n />\n </div>\n );\n}\n\nexport function readMemberOption(member: {\n readonly email: string;\n readonly memberId: string;\n readonly name: string;\n}): MemberOption {\n return {\n email: member.email,\n id: member.memberId,\n name: formatMemberLabel(member.name, member.email, 'dot'),\n };\n}\n\nfunction readMemberOptionFromValue(value: unknown): MemberOption | null {\n if (!isRecord(value)) return null;\n const email = value.email;\n const id = value.id;\n const name = value.name;\n return typeof email === 'string' &&\n typeof id === 'string' &&\n typeof name === 'string'\n ? { email, id, name }\n : null;\n}\n\nfunction readOrgUnitOptionFromValue(value: unknown): OrgUnitOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readPositionOptionFromValue(value: unknown): PositionOption | null {\n if (!isRecord(value)) return null;\n const id = value.id;\n const name = value.name;\n return typeof id === 'string' && typeof name === 'string'\n ? { id, name }\n : null;\n}\n\nfunction readUniqueOption(\n searchText: string,\n options: readonly MemberOption[],\n): MemberOption | null {\n const normalized = searchText.trim().toLocaleLowerCase();\n if (!normalized) return null;\n const matched = options.filter(\n (option) =>\n option.id.toLocaleLowerCase() === normalized ||\n option.email.toLocaleLowerCase() === normalized ||\n option.name.toLocaleLowerCase() === normalized,\n );\n return matched.length === 1 ? matched[0] : null;\n}\n\nfunction mergeMemberOptions(\n selected: readonly MemberOption[],\n options: readonly MemberOption[],\n): readonly MemberOption[] {\n const byId = new Map(\n [...selected, ...options].map(\n (option): readonly [string, MemberOption] => [option.id, option],\n ),\n );\n return [...byId.values()];\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null;\n}\n"],"mappings":"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"}
|