@rytass/bpm-core-react 0.11.0 → 0.12.0

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/chunks/FormBuilderView-CLFlfDGe.cjs +3 -0
  3. package/dist/chunks/FormBuilderView-CLFlfDGe.cjs.map +1 -0
  4. package/dist/chunks/FormBuilderView-D4xIuPYB.js +1960 -0
  5. package/dist/chunks/FormBuilderView-D4xIuPYB.js.map +1 -0
  6. package/dist/chunks/FormRendererView-BxGT16z0.cjs +2 -0
  7. package/dist/chunks/FormRendererView-BxGT16z0.cjs.map +1 -0
  8. package/dist/chunks/FormRendererView-DUceSOXe.js +798 -0
  9. package/dist/chunks/FormRendererView-DUceSOXe.js.map +1 -0
  10. package/dist/chunks/{compose-CUOe5Ia9.js → compose-Bb-REfoB.js} +4 -4
  11. package/dist/chunks/{compose-CUOe5Ia9.js.map → compose-Bb-REfoB.js.map} +1 -1
  12. package/dist/chunks/{compose-CyRkXdeu.cjs → compose-DzRYZcqe.cjs} +2 -2
  13. package/dist/chunks/{compose-CyRkXdeu.cjs.map → compose-DzRYZcqe.cjs.map} +1 -1
  14. package/dist/chunks/{designer-Cb3WAC46.js → designer-C6oIO9Kq.js} +2 -2
  15. package/dist/chunks/{designer-Cb3WAC46.js.map → designer-C6oIO9Kq.js.map} +1 -1
  16. package/dist/chunks/{designer-zLliyrHu.cjs → designer-gFUdImeZ.cjs} +2 -2
  17. package/dist/chunks/{designer-zLliyrHu.cjs.map → designer-gFUdImeZ.cjs.map} +1 -1
  18. package/dist/chunks/{detail-Cmw-aQcu.js → detail-BgKgMvYx.js} +391 -390
  19. package/dist/chunks/{detail-Cmw-aQcu.js.map → detail-BgKgMvYx.js.map} +1 -1
  20. package/dist/chunks/{detail-Bp2Nv0cs.cjs → detail-Dnz5aaI-.cjs} +2 -2
  21. package/dist/chunks/{detail-Bp2Nv0cs.cjs.map → detail-Dnz5aaI-.cjs.map} +1 -1
  22. package/dist/pages/instances/detail/index.cjs +1 -1
  23. package/dist/pages/instances/detail/index.js +1 -1
  24. package/dist/pages/templates/compose/index.cjs +1 -1
  25. package/dist/pages/templates/compose/index.js +1 -1
  26. package/dist/pages/templates/designer/index.cjs +1 -1
  27. package/dist/pages/templates/designer/index.js +1 -1
  28. package/dist/views/forms/builder/index.cjs +1 -1
  29. package/dist/views/forms/builder/index.js +1 -1
  30. package/dist/views/forms/renderer/form-data-source-field.d.ts +16 -0
  31. package/dist/views/forms/renderer/index.cjs +1 -1
  32. package/dist/views/forms/renderer/index.js +1 -1
  33. package/dist/views/instances/detail/index.cjs +1 -1
  34. package/dist/views/instances/detail/index.js +1 -1
  35. package/dist/views/instances/new/index.cjs +1 -1
  36. package/dist/views/instances/new/index.cjs.map +1 -1
  37. package/dist/views/instances/new/index.js +48 -48
  38. package/dist/views/instances/new/index.js.map +1 -1
  39. package/dist/views/templates/compose/index.cjs +1 -1
  40. package/dist/views/templates/compose/index.js +1 -1
  41. package/dist/views/templates/designer/index.cjs +1 -1
  42. package/dist/views/templates/designer/index.js +1 -1
  43. package/package.json +3 -3
  44. package/dist/chunks/FormBuilderView-CAqjOYAP.cjs +0 -3
  45. package/dist/chunks/FormBuilderView-CAqjOYAP.cjs.map +0 -1
  46. package/dist/chunks/FormBuilderView-TzSezeqC.js +0 -1479
  47. package/dist/chunks/FormBuilderView-TzSezeqC.js.map +0 -1
  48. package/dist/chunks/FormRendererView-BurlEDUu.js +0 -580
  49. package/dist/chunks/FormRendererView-BurlEDUu.js.map +0 -1
  50. package/dist/chunks/FormRendererView-Dde1s3I0.cjs +0 -2
  51. package/dist/chunks/FormRendererView-Dde1s3I0.cjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"FormRendererView-BurlEDUu.js","names":[],"sources":["../../src/views/forms/renderer/form-data-source-field.ts","../../src/views/forms/renderer/FormRendererView.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldOption,\n FormFieldValue,\n FormUiSchema,\n isFormDataSourceFieldDefinition,\n} from '@rytass/bpm-core-shared/form';\nimport {\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 readonly invalidValues: readonly string[];\n readonly onReachBottom: () => void;\n readonly onSearch: (searchText: string) => void;\n readonly options: readonly FormFieldOption[];\n readonly retry: () => void;\n readonly status: FormDataSourceFieldStatus;\n}\n\nexport interface UseFormDataSourceFieldInput {\n readonly context?: FormRendererDataSourceContext;\n readonly field: FormFieldDefinition;\n readonly formData: FormRendererValues;\n readonly initialFormData?: FormRendererValues;\n readonly initialValue?: FormFieldValue | undefined;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n}\n\ninterface FormDataSourceResolution {\n readonly signature: string;\n readonly unresolvedValues: readonly string[];\n}\n\nconst EMPTY_FIELD_STATE: FormDataSourceFieldState = {\n canRetry: false,\n error: null,\n hasValue: false,\n hasNextPage: false,\n invalidValues: [],\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 * 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\nexport function useFormDataSourceField(\n input: UseFormDataSourceFieldInput,\n): FormDataSourceFieldState {\n const dynamicField = isFormDataSourceFieldDefinition(input.field)\n ? input.field\n : null;\n const snapshot = dynamicField\n ? input.optionSnapshots?.[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 currentValue = dynamicField\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 dataSourceRefreshSignature = dynamicField\n ? JSON.stringify({\n bindings: dynamicField.dataSource.bindings.map((binding) =>\n binding.from.kind === 'FIELD'\n ? {\n fieldKey: binding.from.fieldKey,\n value: input.formData[binding.from.fieldKey],\n }\n : { value: binding.from.value },\n ),\n value:\n typeof input.initialFormData !== 'undefined' ||\n typeof input.initialValue !== 'undefined'\n ? input.formData[dynamicField.fieldKey]\n : undefined,\n })\n : '';\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 false;\n }\n\n return (\n readFormDataSourceValueSignature(\n input.initialFormData?.[binding.from.fieldKey],\n ) !==\n readFormDataSourceValueSignature(input.formData[binding.from.fieldKey])\n );\n })\n : false;\n const isStale = Boolean(\n (snapshot || hasValue) && (valueChanged || bindingsChanged),\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(dynamicField, input.formData).length >\n 0;\n const pendingQueryStatus = readPendingQueryStatus(isStale, waitsForDependencies);\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 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 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 Object.entries(input.formData).reduce<\n Readonly<Record<string, FormFieldValue>>\n >((values, [key, value]) => {\n if (typeof value === 'undefined') {\n return values;\n }\n\n return { ...values, [key]: value };\n }, {}),\n [formDataSignature],\n );\n const mergedOptions = useMemo(\n (): readonly FormFieldOption[] =>\n mergeFormDataSourceOptions(snapshotOptions, remoteOptions),\n [remoteOptions, snapshotOptions],\n );\n const currentResolution =\n resolution && resolution.signature === dataSourceRefreshSignature\n ? resolution\n : null;\n const resolvePending = needsResolve && !currentResolution;\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 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 setError(null);\n\n const request =\n effectiveContext.kind === 'preview'\n ? previewFormFieldOptions({\n cursor,\n fieldKey: dynamicField.fieldKey,\n formData: queryFormData,\n schema: input.schema,\n searchText: nextSearchTextToLoad,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n })\n : readFormFieldOptions({\n cursor,\n fieldKey: dynamicField.fieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\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 setRemoteOptions((currentOptions) => {\n const selectedOptions = readSelectedFormDataSourceOptions(\n dynamicField ? input.formData[dynamicField.fieldKey] : undefined,\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 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 dynamicFieldSignature,\n effectiveContext,\n hasValue,\n pendingQueryStatus,\n queryFormData,\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 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: dynamicField.fieldKey,\n formData: queryFormData,\n schema: input.schema,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n values: selectedValues,\n })\n : resolveFormFieldOptions({\n fieldKey: dynamicField.fieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\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 });\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 dataSourceRefreshSignature,\n dynamicFieldSignature,\n effectiveContext,\n queryFormData,\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 optionsAbort.current?.abort();\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(quiescentStatus);\n\n return;\n }\n\n loadOptionsRef.current('', null);\n }, [\n autoQueries,\n dynamicFieldSignature,\n effectiveContext,\n dataSourceRefreshSignature,\n initialFormDataSignature,\n input.readonly,\n pendingQueryStatus,\n quiescentStatus,\n snapshotSignature,\n ]);\n\n useEffect((): void => {\n if (!needsResolve) {\n return;\n }\n\n runResolveRef.current();\n }, [dataSourceRefreshSignature, effectiveContext, needsResolve]);\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 onReachBottom = useCallback((): void => {\n if (nextCursor) {\n loadOptions(searchText, nextCursor);\n }\n }, [loadOptions, nextCursor, searchText]);\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 invalidValues,\n onReachBottom,\n onSearch,\n options: mergedOptions,\n retry,\n status: readFormDataSourceExposedStatus({\n invalidValues,\n resolvePending,\n status,\n }),\n };\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 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<FormDataSourceFieldState, 'hasValue' | 'status'>[],\n): string | null {\n const blocked = states.filter(isFormDataSourceFieldSubmissionBlocked);\n\n if (blocked.length === 0) {\n return null;\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","'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 Textarea,\n Toggle,\n Typography,\n Upload,\n} from '@mezzanine-ui/react';\nimport type { UploadFile } from '@mezzanine-ui/react/Upload';\nimport {\n FormDataSourceOptionFieldDefinition,\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldValue,\n FormUiSchema,\n isFormDataSourceFieldDefinition,\n isFormStaticOptionFieldDefinition,\n readFormFieldSelectionMode,\n} from '@rytass/bpm-core-shared/form';\nimport {\n buildFormRendererValues,\n clampOptionalNumber,\n formatDatePickerValue,\n formatDateTimePickerValue,\n FormRendererValues,\n isFormRendererFieldReadonly,\n isFormRendererFieldRequired,\n parseOptionalNumberInput,\n readDatePickerValue,\n readFieldOptionAsSelectOption,\n readSelectedFormDataSourceOptions,\n readSelectOption,\n readVisibleFormRendererFields,\n} from '@rytass/bpm-core-client/form';\nimport { BPMFormField } from '../../../components/bpm-form-field';\nimport {\n FormDataSourceFieldState,\n FormRendererDataSourceContext,\n readFormDataSourceFieldStatusMessage,\n useFormDataSourceField,\n} from './form-data-source-field';\n\nexport interface FormRendererProps {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n readonly emptyText?: string;\n readonly errors?: 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' | 'status'\n >,\n ) => void;\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\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\nexport function FormRenderer({\n dataSourceContext,\n dataSourceInitialValues,\n emptyText = '尚未建立欄位。',\n errors = {},\n maxWidth,\n onChange,\n onDataSourceStateChange,\n onUploadAttachment,\n optionSnapshots,\n readonly = false,\n schema,\n singleColumn = false,\n uiSchema,\n value,\n}: FormRendererProps): ReactElement {\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),\n [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 ...(singleColumn ? { maxWidth, 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 fields={schema.fields}\n key={field.fieldKey}\n onChange={updateValue}\n onDataSourceStateChange={onDataSourceStateChange}\n onUploadAttachment={onUploadAttachment}\n optionSnapshots={optionSnapshots}\n readonly={readonly}\n schema={schema}\n style={{\n ...FORM_RENDERER_FIELD_STYLE,\n gridColumn: `span ${\n singleColumn ? 12 : readFieldColumnSpan(field, uiSchema)\n }`,\n }}\n value={values[field.fieldKey]}\n values={values}\n uiSchema={uiSchema}\n />\n ))}\n </div>\n );\n}\n\nfunction FormRendererField({\n dataSourceContext,\n dataSourceInitialValues,\n error,\n field,\n fields,\n onChange,\n onDataSourceStateChange,\n onUploadAttachment,\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 readonly field: FormFieldDefinition;\n readonly fields: readonly FormFieldDefinition[];\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' | 'status'\n >,\n ) => void;\n readonly onUploadAttachment?:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined;\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 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 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 name={field.fieldKey}\n required={required}\n >\n {renderControl(\n field,\n value,\n fieldReadonly,\n onChange,\n onUploadAttachment,\n dataSourceContext,\n dataSourceState,\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 DATA_SOURCE_FEEDBACK_STYLE: CSSProperties = {\n alignItems: 'center',\n display: 'flex',\n gap: 8,\n};\n\nfunction renderControl(\n field: FormFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n onUploadAttachment:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined,\n dataSourceContext: FormRendererDataSourceContext | undefined,\n dataSourceState: FormDataSourceFieldState,\n): ReactElement {\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 />\n );\n }\n\n if (isFormDataSourceFieldDefinition(field)) {\n return renderDynamicOptionControl(\n field,\n value,\n readonly,\n onChange,\n dataSourceContext,\n dataSourceState,\n );\n }\n\n if (isFormStaticOptionFieldDefinition(field) && field.type === 'checkbox') {\n const options = field.options.map(readFieldOptionAsSelectOption);\n\n return (\n <Select\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 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 onChange={(option): void =>\n onChange(field.fieldKey, option?.id ?? undefined)\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\n value={readSelectOption(options, readStringValue(value))}\n />\n );\n }\n\n if (field.type === 'file_upload') {\n return (\n <Upload\n accept={field.acceptedMimeTypes?.join(',')}\n disabled={readonly}\n maxFiles={field.maxFiles}\n mode=\"button-list\"\n {...((field.maxFiles ?? 1) > 1 ? { multiple: true } : {})}\n onChange={(files): void =>\n onChange(\n field.fieldKey,\n files.length ? files.map((file) => file.id) : undefined,\n )\n }\n onUpload={async (files): Promise<UploadFile[]> =>\n Promise.all(\n files.map(async (file): Promise<UploadFile> => {\n const uploadedFile = onUploadAttachment\n ? await onUploadAttachment(field, file)\n : { id: `${file.name}-${file.lastModified}` };\n\n return {\n file,\n id: uploadedFile.id,\n progress: 100,\n status: 'done',\n };\n }),\n )\n }\n size=\"sub\"\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 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 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): 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 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={loading}\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={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\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={loading}\n mode=\"single\"\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\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 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={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇一或多個選項'}\n readOnly={readonly}\n value={selectedOptions}\n />\n );\n }\n\n return (\n <Select\n disabled={optionControlDisabled}\n loading={loading}\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\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 readStringArrayValue(\n value: FormFieldValue | undefined,\n): readonly string[] {\n return Array.isArray(value) ? 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 money: '請輸入金額',\n number: '請輸入數字',\n radio: '請選擇一個選項',\n select: '請選擇一個選項',\n text: '請輸入文字',\n textarea: '請輸入多行文字',\n };\n\n return placeholders[type];\n}\n\nexport { FormRenderer as FormRendererView };\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- "use client";const e=require("./bpm-form-field-Bc6k4ZEO.cjs");let t=require("react"),n=require("@mezzanine-ui/react"),r=require("react/jsx-runtime"),i=require("@rytass/bpm-core-client/form"),a=require("@rytass/bpm-core-shared/form");var o={canRetry:!1,error:null,hasValue:!1,hasNextPage:!1,invalidValues:[],onReachBottom:()=>void 0,onSearch:()=>void 0,options:[],retry:()=>void 0,status:`IDLE`},s=[],c=10,l=[`FORM_DATA_SOURCE_SEARCH_NOT_SUPPORTED`,`FORM_DATA_SOURCE_SEARCH_TOO_SHORT`];function u(e){let n=(0,a.isFormDataSourceFieldDefinition)(e.field)?e.field:null,r=n?e.optionSnapshots?.[n.fieldKey]:void 0,s=n?JSON.stringify({dataSource:n.dataSource,fieldKey:n.fieldKey,type:n.type}):``,u=JSON.stringify(r??null),d=(0,t.useMemo)(()=>r?.options??[],[u]),f=JSON.stringify(e.formData),p=JSON.stringify(e.initialFormData??{}),b=JSON.stringify(e.schema),x=JSON.stringify(e.uiSchema),S=e.context?.kind,C=e.context?.kind===`runtime`?e.context.instanceId:void 0,w=e.context?.kind===`runtime`?e.context.templateId:void 0,T=n?e.formData[n.fieldKey]:void 0,E=(0,i.readFormDataSourceValueSignature)(T),D=(0,t.useMemo)(()=>(0,i.readFormDataSourceSelectedValues)(T),[E]),O=D.length>0,k=n?JSON.stringify({bindings:n.dataSource.bindings.map(t=>t.from.kind===`FIELD`?{fieldKey:t.from.fieldKey,value:e.formData[t.from.fieldKey]}:{value:t.from.value}),value:e.initialFormData!==void 0||e.initialValue!==void 0?e.formData[n.fieldKey]:void 0}):``,A=(0,t.useMemo)(()=>{if(S===`preview`)return{kind:`preview`};if(S===`runtime`&&!e.readonly)return{instanceId:C,kind:`runtime`,templateId:w}},[C,S,w,e.readonly]),j=n?(e.initialFormData!==void 0||e.initialValue!==void 0)&&(0,i.readFormDataSourceValueSignature)(e.initialValue)!==E:!1,M=n?e.initialFormData!==void 0&&n.dataSource.bindings.some(t=>t.from.kind===`FIELD`&&(0,i.readFormDataSourceValueSignature)(e.initialFormData?.[t.from.fieldKey])!==(0,i.readFormDataSourceValueSignature)(e.formData[t.from.fieldKey])):!1,N=!!((r||O)&&(j||M)),P=!!(n&&A&&N&&O),F=h({hasSnapshot:!!r,hasValue:O,isStale:N,readonly:e.readonly}),I=!!(n&&A&&n.type!==`autocomplete`),L=g(N,I&&n!==null&&(0,i.readMissingFormDataSourceDependencies)(n,e.formData).length>0),[ee,R]=(0,t.useState)([]),[te,ne]=(0,t.useState)(``),[z,B]=(0,t.useState)(null),[V,re]=(0,t.useState)(null),[ie,H]=(0,t.useState)(()=>n?I?L:F:`IDLE`),[ae,U]=(0,t.useState)(null),W=(0,t.useRef)(0),G=(0,t.useRef)(0),K=(0,t.useRef)(0),q=(0,t.useRef)(null),J=(0,t.useRef)(null),Y=(0,t.useRef)({cursor:null,searchText:``}),X=(0,t.useMemo)(()=>Object.entries(e.formData).reduce((e,[t,n])=>n===void 0?e:{...e,[t]:n},{}),[f]),oe=(0,t.useMemo)(()=>(0,i.mergeFormDataSourceOptions)(d,ee),[ee,d]),se=V&&V.signature===k?V:null,ce=P&&!se,le=m({options:oe,resolution:se,resolvePending:ce,value:T}),Z=(0,t.useCallback)((t,r)=>{if(!n||!A)return;let a=G.current+1;G.current=a,Y.current={cursor:r,searchText:t},q.current?.abort();let o=new AbortController;q.current=o,H(L),U(null),(A.kind===`preview`?(0,i.previewFormFieldOptions)({cursor:r,fieldKey:n.fieldKey,formData:X,schema:e.schema,searchText:t,signal:o.signal,uiSchema:e.uiSchema}):(0,i.readFormFieldOptions)({cursor:r,fieldKey:n.fieldKey,formData:X,instanceId:A.instanceId??null,searchText:t,signal:o.signal,templateId:A.templateId??null})).then(s=>{y(o,G,a)||(R(t=>{let a=(0,i.readSelectedFormDataSourceOptions)(n?e.formData[n.fieldKey]:void 0,t);return(0,i.mergeFormDataSourceOptions)(a,r?t:[],s.options)}),B(s.nextCursor),U(null),W.current=r?W.current+s.options.length:s.options.length,s.nextCursor&&s.options.length>0&&W.current<c&&Q.current(t,s.nextCursor),H(s.waitingForFieldKeys.length?`WAITING_FOR_DEPENDENCIES`:`VALID`))}).catch(e=>{if(y(o,G,a))return;let t=(0,i.readFormDataSourceErrorMessage)(e)??`選項來源暫時無法使用。`,n=(0,i.readFormDataSourceErrorCode)(e);U(t),H(_(!!(n&&l.includes(n)),O))})},[s,A,O,L,X,b,x]),Q=(0,t.useRef)(Z);Q.current=Z;let ue=(0,t.useCallback)(()=>{if(!n||!A||D.length===0)return;let t=k,r=K.current+1;K.current=r,J.current?.abort();let a=new AbortController;J.current=a,U(null),(A.kind===`preview`?(0,i.previewResolveFormFieldOptions)({fieldKey:n.fieldKey,formData:X,schema:e.schema,signal:a.signal,uiSchema:e.uiSchema,values:D}):(0,i.resolveFormFieldOptions)({fieldKey:n.fieldKey,formData:X,instanceId:A.instanceId??null,signal:a.signal,templateId:A.templateId??null,values:D})).then(e=>{if(!y(a,K,r)){if(e.waitingForFieldKeys.length){H(`WAITING_FOR_DEPENDENCIES`);return}R(t=>(0,i.mergeFormDataSourceOptions)(t,e.options)),re({signature:t,unresolvedValues:e.unresolvedValues}),H(`VALID`)}}).catch(e=>{y(a,K,r)||(H(`UNAVAILABLE`),U((0,i.readFormDataSourceErrorMessage)(e)??`選項來源暫時無法使用。`))})},[k,s,A,X,b,D,x]),$=(0,t.useRef)(ue);$.current=ue,(0,t.useEffect)(()=>{if(G.current+=1,q.current?.abort(),R(e=>(0,i.readSelectedFormDataSourceOptions)(T,e)),B(e=>e===null?e:null),ne(e=>e&&``),U(e=>e&&null),!n){H(`IDLE`);return}if(!I){H(F);return}Q.current(``,null)},[I,s,A,k,p,e.readonly,L,F,u]),(0,t.useEffect)(()=>{P&&$.current()},[k,A,P]),(0,t.useEffect)(()=>()=>{q.current?.abort(),J.current?.abort()},[]);let de=(0,t.useCallback)(e=>{ne(e),R(e=>(0,i.readSelectedFormDataSourceOptions)(T,e)),B(null),Z(e,null)},[Z]),fe=(0,t.useCallback)(()=>{z&&Z(te,z)},[Z,z,te]),pe=(0,t.useCallback)(()=>{A&&(Z(Y.current.searchText,Y.current.cursor),P&&$.current())},[A,Z,P]);return n?{canRetry:!!A,error:ae,hasValue:O,hasNextPage:!!z,invalidValues:le,onReachBottom:fe,onSearch:de,options:oe,retry:pe,status:v({invalidValues:le,resolvePending:ce,status:ie})}:o}function d(e){switch(e.status){case`WAITING_FOR_DEPENDENCIES`:return`請先填寫相依欄位。`;case`STALE`:return`選項需要重新載入或驗證。`;case`INVALID`:return e.invalidValues.length?`無法辨識選項:${e.invalidValues.join(`、`)}`:`目前選項已失效,請重新選擇。`;case`UNAVAILABLE`:return e.error??`選項來源暫時無法使用。`;case`VALID`:return e.error??(e.options.length?null:`目前沒有可用選項。`);default:return e.error}}function f(e){return e.hasValue&&e.status!==`VALID`}function p(e){let t=e.filter(f);return t.length===0?null:t.every(e=>e.status===`LOADING`||e.status===`STALE`)?`選項驗證中,請稍候再送出。`:`請先完成動態選項驗證。`}function m(e){return e.resolution?e.resolution.unresolvedValues:e.resolvePending?s:(0,i.readMissingFormDataSourceOptionValues)(e.value,e.options)}function h(e){return e.isStale?`STALE`:e.hasSnapshot?`VALID`:e.readonly&&e.hasValue?`UNAVAILABLE`:`IDLE`}function g(e,t){return t?`WAITING_FOR_DEPENDENCIES`:e?`STALE`:`LOADING`}function _(e,t){return e?t?`VALID`:`IDLE`:`UNAVAILABLE`}function v(e){return e.status===`LOADING`||e.status===`UNAVAILABLE`||e.status===`WAITING_FOR_DEPENDENCIES`?e.status:e.invalidValues.length?`INVALID`:e.resolvePending?`STALE`:e.status}function y(e,t,n){return e.signal.aborted||t.current!==n}var b={alignItems:`start`,display:`grid`,gap:12,gridTemplateColumns:`repeat(12, minmax(0, 1fr))`},x={gridColumn:`1 / -1`},S={alignItems:`start`,display:`grid`,gap:8,minWidth:0},C={minWidth:`100%`,width:`100%`};function w(e){e&&(e.style.width=`100%`)}function T({dataSourceContext:e,dataSourceInitialValues:a,emptyText:o=`尚未建立欄位。`,errors:s={},maxWidth:c,onChange:l,onDataSourceStateChange:u,onUploadAttachment:d,optionSnapshots:f,readonly:p=!1,schema:m,singleColumn:h=!1,uiSchema:g,value:_}){let[v,y]=(0,t.useState)(()=>(0,i.buildFormRendererValues)(m.fields,_??{})),C=(0,t.useMemo)(()=>(0,i.buildFormRendererValues)(m.fields,_??v),[v,m.fields,_]),w=(0,t.useRef)(C),T=(0,t.useMemo)(()=>(0,i.readVisibleFormRendererFields)(m,g,C),[m,g,C]);(0,t.useEffect)(()=>{w.current=C},[C]),(0,t.useEffect)(()=>{y(e=>(0,i.buildFormRendererValues)(m.fields,_??e))},[m.fields,_]);function D(e,t){let n={...w.current,[e]:t};w.current=n,_||y(n),l?.(n)}return T.length?(0,r.jsx)(`div`,{style:{...b,...h?{maxWidth:c,width:`100%`}:{}},children:T.map(t=>(0,r.jsx)(E,{dataSourceContext:e,dataSourceInitialValues:a,field:t,error:s[t.fieldKey]??null,fields:m.fields,onChange:D,onDataSourceStateChange:u,onUploadAttachment:d,optionSnapshots:f,readonly:p,schema:m,style:{...S,gridColumn:`span ${h?12:A(t,g)}`},value:C[t.fieldKey],values:C,uiSchema:g},t.fieldKey))}):(0,r.jsx)(`div`,{style:b,children:(0,r.jsx)(n.Typography,{color:`text-neutral`,style:x,variant:`body`,children:m.fields.length>0?`目前條件下沒有可填寫欄位。`:o})})}function E({dataSourceContext:o,dataSourceInitialValues:s,error:c,field:l,fields:f,onChange:p,onDataSourceStateChange:m,onUploadAttachment:h,optionSnapshots:g,readonly:_,schema:v,style:y,value:b,values:x,uiSchema:S}){let C=(0,i.isFormRendererFieldRequired)(l,f,x),w=_||(0,i.isFormRendererFieldReadonly)(l,f,x),T=u({context:o,field:l,formData:x,initialFormData:s,initialValue:s?.[l.fieldKey],optionSnapshots:g,readonly:w,schema:v,uiSchema:S}),E=d(T),k=JSON.stringify({hasValue:T.hasValue,invalidValues:T.invalidValues,status:T.status});return(0,t.useEffect)(()=>{(0,a.isFormDataSourceFieldDefinition)(l)&&m?.(l.fieldKey,{hasValue:T.hasValue,invalidValues:T.invalidValues,status:T.status})},[k,l.fieldKey,m]),(0,r.jsxs)(`div`,{"data-form-field-key":l.fieldKey,style:y,children:[(0,r.jsx)(e.t,{label:l.label,name:l.fieldKey,required:C,children:O(l,b,w,p,h,o,T)}),c?(0,r.jsx)(n.Typography,{color:`text-error`,variant:`caption`,children:c}):null,E?(0,r.jsxs)(`div`,{style:D,children:[(0,r.jsx)(n.Typography,{color:T.status===`INVALID`||T.status===`UNAVAILABLE`?`text-error`:`text-neutral`,variant:`caption`,children:E}),T.status===`UNAVAILABLE`&&T.canRetry?(0,r.jsx)(n.Button,{onClick:T.retry,size:`sub`,type:`button`,variant:`base-ghost`,children:`重試`}):null]}):null]})}var D={alignItems:`center`,display:`flex`,gap:8};function O(e,t,o,s,c,l,u){if(e.type===`textarea`)return(0,r.jsx)(n.Textarea,{onChange:t=>s(e.fieldKey,t.target.value),placeholder:e.placeholder??`請輸入多行文字`,readOnly:o,ref:w,rows:3,style:C,value:j(t)});if(e.type===`boolean`)return(0,r.jsx)(n.Toggle,{checked:t===!0,disabled:o,label:`啟用`,onChange:t=>s(e.fieldKey,t.target.checked)});if((0,a.isFormDataSourceFieldDefinition)(e))return k(e,t,o,s,l,u);if((0,a.isFormStaticOptionFieldDefinition)(e)&&e.type===`checkbox`){let a=e.options.map(i.readFieldOptionAsSelectOption);return(0,r.jsx)(n.Select,{mode:`multiple`,onChange:t=>s(e.fieldKey,t?.length?t.map(e=>e.id):void 0),options:a,placeholder:e.placeholder??`請選擇一或多個選項`,readOnly:o,value:a.filter(e=>M(t).includes(e.id))})}if((0,a.isFormStaticOptionFieldDefinition)(e)){let a=e.options.map(i.readFieldOptionAsSelectOption);return(0,r.jsx)(n.Select,{onChange:t=>s(e.fieldKey,t?.id??void 0),options:a,placeholder:e.placeholder??`請選擇`,readOnly:o,value:(0,i.readSelectOption)(a,j(t))})}return e.type===`file_upload`?(0,r.jsx)(n.Upload,{accept:e.acceptedMimeTypes?.join(`,`),disabled:o,maxFiles:e.maxFiles,mode:`button-list`,...(e.maxFiles??1)>1?{multiple:!0}:{},onChange:t=>s(e.fieldKey,t.length?t.map(e=>e.id):void 0),onUpload:async t=>Promise.all(t.map(async t=>({file:t,id:(c?await c(e,t):{id:`${t.name}-${t.lastModified}`}).id,progress:100,status:`done`}))),size:`sub`}):e.type===`date`?(0,r.jsx)(n.DatePicker,{format:`YYYY-MM-DD`,fullWidth:!0,onChange:t=>s(e.fieldKey,(0,i.formatDatePickerValue)(t)),placeholder:e.placeholder??P(e.type),readOnly:o,value:(0,i.readDatePickerValue)(t)}):e.type===`datetime`?(0,r.jsx)(n.DateTimePicker,{formatDate:`YYYY-MM-DD`,formatTime:`HH:mm`,fullWidth:!0,hideSecond:!0,onChange:t=>s(e.fieldKey,(0,i.formatDateTimePickerValue)(t)),placeholderLeft:`選擇日期`,placeholderRight:`選擇時間`,readOnly:o,value:(0,i.readDatePickerValue)(t)}):e.type===`number`||e.type===`money`?(0,r.jsx)(n.Input,{fullWidth:!0,max:e.maximum,min:e.minimum,onChange:t=>s(e.fieldKey,(0,i.clampOptionalNumber)((0,i.parseOptionalNumberInput)(t.target.value),{max:e.maximum,min:e.minimum})),placeholder:e.placeholder??P(e.type),...o?{readonly:!0}:{},showSpinner:!0,value:N(t),variant:`measure`}):(0,r.jsx)(n.Input,{fullWidth:!0,onChange:t=>s(e.fieldKey,t.target.value),placeholder:e.placeholder??P(e.type),...o?{readonly:!0}:{},value:j(t),variant:`base`})}function k(e,t,o,s,c,l){let u=l.options.map(i.readFieldOptionAsSelectOption),d=(0,i.readSelectedFormDataSourceOptions)(t,l.options).map(i.readFieldOptionAsSelectOption),f=l.status===`LOADING`,p=l.status===`WAITING_FOR_DEPENDENCIES`,m=!!c&&!o,h=o||p,g=o;return e.type===`autocomplete`?(0,a.readFormFieldSelectionMode)(e)===`multiple`?(0,r.jsx)(n.AutoComplete,{asyncData:m,clearSearchText:!0,disabled:g,disabledOptionsFilter:m,globalPortal:!0,loading:f,mode:`multiple`,onChange:t=>s(e.fieldKey,t.length?t.map(e=>e.id):void 0),onReachBottom:l.hasNextPage?l.onReachBottom:void 0,onSearch:m?l.onSearch:void 0,options:u,placeholder:e.placeholder??`請輸入或選擇`,readOnly:o,searchDebounceTime:300,value:d}):(0,r.jsx)(n.AutoComplete,{asyncData:m,clearSearchText:!0,disabled:g,disabledOptionsFilter:m,globalPortal:!0,loading:f,mode:`single`,onChange:t=>s(e.fieldKey,t?.id??void 0),onReachBottom:l.hasNextPage?l.onReachBottom:void 0,onSearch:m?l.onSearch:void 0,options:u,placeholder:e.placeholder??`請輸入或選擇`,readOnly:o,searchDebounceTime:300,value:d[0]??null}):e.type===`radio`?(0,r.jsx)(n.RadioGroup,{disabled:h,name:e.fieldKey,onChange:t=>s(e.fieldKey,t.target.value||void 0),options:u,value:j(t)}):e.type===`checkbox`?(0,r.jsx)(n.CheckboxGroup,{disabled:h,name:e.fieldKey,onChange:t=>s(e.fieldKey,t.target.values.length?t.target.values:void 0),options:u.map(e=>({label:e.name,value:e.id})),value:[...M(t)]}):(0,a.readFormFieldSelectionMode)(e)===`multiple`?(0,r.jsx)(n.Select,{disabled:h,loading:f,mode:`multiple`,onChange:t=>s(e.fieldKey,t.length?t.map(e=>e.id):void 0),onReachBottom:l.hasNextPage?l.onReachBottom:void 0,options:u,placeholder:e.placeholder??`請選擇一或多個選項`,readOnly:o,value:d}):(0,r.jsx)(n.Select,{disabled:h,loading:f,onChange:t=>s(e.fieldKey,t?.id??void 0),onReachBottom:l.hasNextPage?l.onReachBottom:void 0,options:u,placeholder:e.placeholder??`請選擇`,readOnly:o,value:d[0]??null})}function A(e,t){return{FULL:12,HALF:6,THIRD:4}[t.layout.find(t=>t.fieldKey===e.fieldKey)?.width??`FULL`]}function j(e){return typeof e==`string`?e:``}function M(e){return Array.isArray(e)?e:[]}function N(e){return typeof e==`number`?String(e):``}function P(e){return{boolean:``,autocomplete:`請輸入或選擇`,checkbox:`請選擇一或多個選項`,date:`請選擇日期`,datetime:`請選擇日期與時間`,file_upload:`請上傳附件`,money:`請輸入金額`,number:`請輸入數字`,radio:`請選擇一個選項`,select:`請選擇一個選項`,text:`請輸入文字`,textarea:`請輸入多行文字`}[e]}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return T}});
2
- //# sourceMappingURL=FormRendererView-Dde1s3I0.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"FormRendererView-Dde1s3I0.cjs","names":[],"sources":["../../src/views/forms/renderer/form-data-source-field.ts","../../src/views/forms/renderer/FormRendererView.tsx"],"sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldOption,\n FormFieldValue,\n FormUiSchema,\n isFormDataSourceFieldDefinition,\n} from '@rytass/bpm-core-shared/form';\nimport {\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 readonly invalidValues: readonly string[];\n readonly onReachBottom: () => void;\n readonly onSearch: (searchText: string) => void;\n readonly options: readonly FormFieldOption[];\n readonly retry: () => void;\n readonly status: FormDataSourceFieldStatus;\n}\n\nexport interface UseFormDataSourceFieldInput {\n readonly context?: FormRendererDataSourceContext;\n readonly field: FormFieldDefinition;\n readonly formData: FormRendererValues;\n readonly initialFormData?: FormRendererValues;\n readonly initialValue?: FormFieldValue | undefined;\n readonly optionSnapshots?: FormDataSourceValueSnapshots;\n readonly readonly: boolean;\n readonly schema: FormDefinitionSchema;\n readonly uiSchema: FormUiSchema;\n}\n\ninterface FormDataSourceResolution {\n readonly signature: string;\n readonly unresolvedValues: readonly string[];\n}\n\nconst EMPTY_FIELD_STATE: FormDataSourceFieldState = {\n canRetry: false,\n error: null,\n hasValue: false,\n hasNextPage: false,\n invalidValues: [],\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 * 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\nexport function useFormDataSourceField(\n input: UseFormDataSourceFieldInput,\n): FormDataSourceFieldState {\n const dynamicField = isFormDataSourceFieldDefinition(input.field)\n ? input.field\n : null;\n const snapshot = dynamicField\n ? input.optionSnapshots?.[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 currentValue = dynamicField\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 dataSourceRefreshSignature = dynamicField\n ? JSON.stringify({\n bindings: dynamicField.dataSource.bindings.map((binding) =>\n binding.from.kind === 'FIELD'\n ? {\n fieldKey: binding.from.fieldKey,\n value: input.formData[binding.from.fieldKey],\n }\n : { value: binding.from.value },\n ),\n value:\n typeof input.initialFormData !== 'undefined' ||\n typeof input.initialValue !== 'undefined'\n ? input.formData[dynamicField.fieldKey]\n : undefined,\n })\n : '';\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 false;\n }\n\n return (\n readFormDataSourceValueSignature(\n input.initialFormData?.[binding.from.fieldKey],\n ) !==\n readFormDataSourceValueSignature(input.formData[binding.from.fieldKey])\n );\n })\n : false;\n const isStale = Boolean(\n (snapshot || hasValue) && (valueChanged || bindingsChanged),\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(dynamicField, input.formData).length >\n 0;\n const pendingQueryStatus = readPendingQueryStatus(isStale, waitsForDependencies);\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 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 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 Object.entries(input.formData).reduce<\n Readonly<Record<string, FormFieldValue>>\n >((values, [key, value]) => {\n if (typeof value === 'undefined') {\n return values;\n }\n\n return { ...values, [key]: value };\n }, {}),\n [formDataSignature],\n );\n const mergedOptions = useMemo(\n (): readonly FormFieldOption[] =>\n mergeFormDataSourceOptions(snapshotOptions, remoteOptions),\n [remoteOptions, snapshotOptions],\n );\n const currentResolution =\n resolution && resolution.signature === dataSourceRefreshSignature\n ? resolution\n : null;\n const resolvePending = needsResolve && !currentResolution;\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 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 setError(null);\n\n const request =\n effectiveContext.kind === 'preview'\n ? previewFormFieldOptions({\n cursor,\n fieldKey: dynamicField.fieldKey,\n formData: queryFormData,\n schema: input.schema,\n searchText: nextSearchTextToLoad,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n })\n : readFormFieldOptions({\n cursor,\n fieldKey: dynamicField.fieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\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 setRemoteOptions((currentOptions) => {\n const selectedOptions = readSelectedFormDataSourceOptions(\n dynamicField ? input.formData[dynamicField.fieldKey] : undefined,\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 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 dynamicFieldSignature,\n effectiveContext,\n hasValue,\n pendingQueryStatus,\n queryFormData,\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 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: dynamicField.fieldKey,\n formData: queryFormData,\n schema: input.schema,\n signal: controller.signal,\n uiSchema: input.uiSchema,\n values: selectedValues,\n })\n : resolveFormFieldOptions({\n fieldKey: dynamicField.fieldKey,\n formData: queryFormData,\n instanceId: effectiveContext.instanceId ?? null,\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 });\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 dataSourceRefreshSignature,\n dynamicFieldSignature,\n effectiveContext,\n queryFormData,\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 optionsAbort.current?.abort();\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(quiescentStatus);\n\n return;\n }\n\n loadOptionsRef.current('', null);\n }, [\n autoQueries,\n dynamicFieldSignature,\n effectiveContext,\n dataSourceRefreshSignature,\n initialFormDataSignature,\n input.readonly,\n pendingQueryStatus,\n quiescentStatus,\n snapshotSignature,\n ]);\n\n useEffect((): void => {\n if (!needsResolve) {\n return;\n }\n\n runResolveRef.current();\n }, [dataSourceRefreshSignature, effectiveContext, needsResolve]);\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 onReachBottom = useCallback((): void => {\n if (nextCursor) {\n loadOptions(searchText, nextCursor);\n }\n }, [loadOptions, nextCursor, searchText]);\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 invalidValues,\n onReachBottom,\n onSearch,\n options: mergedOptions,\n retry,\n status: readFormDataSourceExposedStatus({\n invalidValues,\n resolvePending,\n status,\n }),\n };\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 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<FormDataSourceFieldState, 'hasValue' | 'status'>[],\n): string | null {\n const blocked = states.filter(isFormDataSourceFieldSubmissionBlocked);\n\n if (blocked.length === 0) {\n return null;\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","'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 Textarea,\n Toggle,\n Typography,\n Upload,\n} from '@mezzanine-ui/react';\nimport type { UploadFile } from '@mezzanine-ui/react/Upload';\nimport {\n FormDataSourceOptionFieldDefinition,\n FormDataSourceValueSnapshots,\n FormDefinitionSchema,\n FormFieldDefinition,\n FormFieldValue,\n FormUiSchema,\n isFormDataSourceFieldDefinition,\n isFormStaticOptionFieldDefinition,\n readFormFieldSelectionMode,\n} from '@rytass/bpm-core-shared/form';\nimport {\n buildFormRendererValues,\n clampOptionalNumber,\n formatDatePickerValue,\n formatDateTimePickerValue,\n FormRendererValues,\n isFormRendererFieldReadonly,\n isFormRendererFieldRequired,\n parseOptionalNumberInput,\n readDatePickerValue,\n readFieldOptionAsSelectOption,\n readSelectedFormDataSourceOptions,\n readSelectOption,\n readVisibleFormRendererFields,\n} from '@rytass/bpm-core-client/form';\nimport { BPMFormField } from '../../../components/bpm-form-field';\nimport {\n FormDataSourceFieldState,\n FormRendererDataSourceContext,\n readFormDataSourceFieldStatusMessage,\n useFormDataSourceField,\n} from './form-data-source-field';\n\nexport interface FormRendererProps {\n readonly dataSourceContext?: FormRendererDataSourceContext;\n readonly dataSourceInitialValues?: FormRendererValues;\n readonly emptyText?: string;\n readonly errors?: 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' | 'status'\n >,\n ) => void;\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\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\nexport function FormRenderer({\n dataSourceContext,\n dataSourceInitialValues,\n emptyText = '尚未建立欄位。',\n errors = {},\n maxWidth,\n onChange,\n onDataSourceStateChange,\n onUploadAttachment,\n optionSnapshots,\n readonly = false,\n schema,\n singleColumn = false,\n uiSchema,\n value,\n}: FormRendererProps): ReactElement {\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),\n [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 ...(singleColumn ? { maxWidth, 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 fields={schema.fields}\n key={field.fieldKey}\n onChange={updateValue}\n onDataSourceStateChange={onDataSourceStateChange}\n onUploadAttachment={onUploadAttachment}\n optionSnapshots={optionSnapshots}\n readonly={readonly}\n schema={schema}\n style={{\n ...FORM_RENDERER_FIELD_STYLE,\n gridColumn: `span ${\n singleColumn ? 12 : readFieldColumnSpan(field, uiSchema)\n }`,\n }}\n value={values[field.fieldKey]}\n values={values}\n uiSchema={uiSchema}\n />\n ))}\n </div>\n );\n}\n\nfunction FormRendererField({\n dataSourceContext,\n dataSourceInitialValues,\n error,\n field,\n fields,\n onChange,\n onDataSourceStateChange,\n onUploadAttachment,\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 readonly field: FormFieldDefinition;\n readonly fields: readonly FormFieldDefinition[];\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' | 'status'\n >,\n ) => void;\n readonly onUploadAttachment?:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined;\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 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 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 name={field.fieldKey}\n required={required}\n >\n {renderControl(\n field,\n value,\n fieldReadonly,\n onChange,\n onUploadAttachment,\n dataSourceContext,\n dataSourceState,\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 DATA_SOURCE_FEEDBACK_STYLE: CSSProperties = {\n alignItems: 'center',\n display: 'flex',\n gap: 8,\n};\n\nfunction renderControl(\n field: FormFieldDefinition,\n value: FormFieldValue | undefined,\n readonly: boolean,\n onChange: (fieldKey: string, value: FormFieldValue | undefined) => void,\n onUploadAttachment:\n | ((\n field: FormFieldDefinition,\n file: File,\n ) => Promise<{ readonly id: string }>)\n | undefined,\n dataSourceContext: FormRendererDataSourceContext | undefined,\n dataSourceState: FormDataSourceFieldState,\n): ReactElement {\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 />\n );\n }\n\n if (isFormDataSourceFieldDefinition(field)) {\n return renderDynamicOptionControl(\n field,\n value,\n readonly,\n onChange,\n dataSourceContext,\n dataSourceState,\n );\n }\n\n if (isFormStaticOptionFieldDefinition(field) && field.type === 'checkbox') {\n const options = field.options.map(readFieldOptionAsSelectOption);\n\n return (\n <Select\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 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 onChange={(option): void =>\n onChange(field.fieldKey, option?.id ?? undefined)\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\n value={readSelectOption(options, readStringValue(value))}\n />\n );\n }\n\n if (field.type === 'file_upload') {\n return (\n <Upload\n accept={field.acceptedMimeTypes?.join(',')}\n disabled={readonly}\n maxFiles={field.maxFiles}\n mode=\"button-list\"\n {...((field.maxFiles ?? 1) > 1 ? { multiple: true } : {})}\n onChange={(files): void =>\n onChange(\n field.fieldKey,\n files.length ? files.map((file) => file.id) : undefined,\n )\n }\n onUpload={async (files): Promise<UploadFile[]> =>\n Promise.all(\n files.map(async (file): Promise<UploadFile> => {\n const uploadedFile = onUploadAttachment\n ? await onUploadAttachment(field, file)\n : { id: `${file.name}-${file.lastModified}` };\n\n return {\n file,\n id: uploadedFile.id,\n progress: 100,\n status: 'done',\n };\n }),\n )\n }\n size=\"sub\"\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 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 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): 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 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={loading}\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={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\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={loading}\n mode=\"single\"\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n onSearch={interactiveSearch ? dataSourceState.onSearch : undefined}\n options={options}\n placeholder={field.placeholder ?? '請輸入或選擇'}\n readOnly={readonly}\n searchDebounceTime={300}\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 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={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇一或多個選項'}\n readOnly={readonly}\n value={selectedOptions}\n />\n );\n }\n\n return (\n <Select\n disabled={optionControlDisabled}\n loading={loading}\n onChange={(nextOption): void =>\n onChange(field.fieldKey, nextOption?.id ?? undefined)\n }\n onReachBottom={\n dataSourceState.hasNextPage\n ? dataSourceState.onReachBottom\n : undefined\n }\n options={options}\n placeholder={field.placeholder ?? '請選擇'}\n readOnly={readonly}\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 readStringArrayValue(\n value: FormFieldValue | undefined,\n): readonly string[] {\n return Array.isArray(value) ? 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 money: '請輸入金額',\n number: '請輸入數字',\n radio: '請選擇一個選項',\n select: '請選擇一個選項',\n text: '請輸入文字',\n textarea: '請輸入多行文字',\n };\n\n return placeholders[type];\n}\n\nexport { FormRenderer as FormRendererView };\n"],"mappings":"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"}