@faasjs/ant-design 8.0.0-beta.16 → 8.0.0-beta.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,15 +32,24 @@ let dayjs = require("dayjs");
32
32
  dayjs = __toESM(dayjs);
33
33
  //#region src/Loading.tsx
34
34
  /**
35
- * Loading component based on Spin
35
+ * Render an Ant Design loading spinner with an optional content fallback.
36
+ *
37
+ * @param {LoadingProps} props - Loading indicator props and optional wrapped children.
36
38
  *
37
39
  * @example
38
40
  * ```tsx
39
- * <Loading /> // display loading
41
+ * import { Loading } from '@faasjs/ant-design'
40
42
  *
41
- * <Loading loading={ !remoteData }>
42
- * <div>{remoteData}</div>
43
- * </Loading>
43
+ * export function Page({ remoteData }: { remoteData?: string }) {
44
+ * return (
45
+ * <>
46
+ * <Loading />
47
+ * <Loading loading={!remoteData}>
48
+ * <div>{remoteData}</div>
49
+ * </Loading>
50
+ * </>
51
+ * )
52
+ * }
44
53
  * ```
45
54
  */
46
55
  function Loading(props) {
@@ -59,18 +68,84 @@ function Loading(props) {
59
68
  //#endregion
60
69
  //#region src/FaasDataWrapper.tsx
61
70
  /**
62
- * FaasDataWrapper component with Loading
71
+ * Render the `@faasjs/react` data wrapper with an Ant Design loading fallback.
72
+ *
73
+ * When `loading` is not provided, the component renders {@link Loading} with `loadingProps` while
74
+ * the wrapped FaasJS request is pending.
75
+ *
76
+ * @template T - Action path or response data type used for inference.
77
+ * @param {FaasDataWrapperProps<T>} props - Wrapper props including loading fallbacks and request configuration.
63
78
  *
64
79
  * @example
65
80
  * ```tsx
66
- * function MyComponent (props: FaasDataInjection) {
67
- * return <div>{ props.data }</div>
81
+ * import { Alert, Button } from 'antd'
82
+ * import { FaasDataWrapper } from '@faasjs/ant-design'
83
+ *
84
+ * type User = {
85
+ * name: string
86
+ * }
87
+ *
88
+ * function UserView(props: {
89
+ * data?: User
90
+ * error?: Error
91
+ * reload?: () => void
92
+ * }) {
93
+ * if (props.error) {
94
+ * return (
95
+ * <Alert
96
+ * type="error"
97
+ * message={props.error.message}
98
+ * action={
99
+ * <Button size="small" onClick={() => props.reload?.()}>
100
+ * Retry
101
+ * </Button>
102
+ * }
103
+ * />
104
+ * )
105
+ * }
106
+ *
107
+ * return <div>Hello, {props.data?.name}</div>
108
+ * }
109
+ *
110
+ * // Render-prop mode
111
+ * export function UserProfile(props: { id: number }) {
112
+ * return (
113
+ * <FaasDataWrapper<User>
114
+ * action="user/get"
115
+ * params={{ id: props.id }}
116
+ * loading={<div>Loading user...</div>}
117
+ * render={({ data, error, reload }) => {
118
+ * if (error) {
119
+ * return (
120
+ * <Alert
121
+ * type="error"
122
+ * message={error.message}
123
+ * action={
124
+ * <Button size="small" onClick={() => reload()}>
125
+ * Retry
126
+ * </Button>
127
+ * }
128
+ * />
129
+ * )
130
+ * }
131
+ *
132
+ * return <div>Hello, {data.name}</div>
133
+ * }}
134
+ * />
135
+ * )
68
136
  * }
69
137
  *
70
- * function MyPage () {
71
- * return <FaasDataWrapper action="test" params={{ a: 1 }}>
72
- * <MyComponent />
73
- * </FaasDataWrapper>
138
+ * // Children injection mode
139
+ * export function UserProfileWithChildren(props: { id: number }) {
140
+ * return (
141
+ * <FaasDataWrapper<User>
142
+ * action="user/get"
143
+ * params={{ id: props.id }}
144
+ * loading={<div>Loading user...</div>}
145
+ * >
146
+ * <UserView />
147
+ * </FaasDataWrapper>
148
+ * )
74
149
  * }
75
150
  * ```
76
151
  */
@@ -81,11 +156,27 @@ function FaasDataWrapper(props) {
81
156
  });
82
157
  }
83
158
  /**
84
- * HOC to wrap a component with FaasDataWrapper and Loading
159
+ * Wrap a component with {@link FaasDataWrapper} and its Ant Design loading fallback.
160
+ *
161
+ * @template PathOrData - Action path or response data type used for inference.
162
+ * @template TComponentProps - Component props including injected Faas data fields.
163
+ * @param {React.FC<TComponentProps & Record<string, any>>} Component - Component that consumes injected Faas data props.
164
+ * @param {FaasDataWrapperProps<PathOrData>} faasProps - Request configuration forwarded to {@link FaasDataWrapper}.
165
+ * @returns Higher-order component that injects Faas data props.
85
166
  *
86
167
  * @example
87
168
  * ```tsx
88
- * const MyComponent = withFaasData(({ data }) => <div>{data.name}</div>, { action: 'test', params: { a: 1 } })
169
+ * import { withFaasData } from '@faasjs/ant-design'
170
+ *
171
+ * const UserCard = withFaasData(
172
+ * ({ data, error, reload }) =>
173
+ * error ? (
174
+ * <a onClick={() => reload()}>Retry</a>
175
+ * ) : (
176
+ * <div>{data.name}</div>
177
+ * ),
178
+ * { action: 'user/get', params: { id: 1 } },
179
+ * )
89
180
  * ```
90
181
  */
91
182
  function withFaasData(Component, faasProps) {
@@ -131,17 +222,29 @@ const baseTheme = {
131
222
  },
132
223
  Link: { style: {} }
133
224
  };
225
+ /**
226
+ * React context storing the resolved FaasJS Ant Design theme.
227
+ */
134
228
  const ConfigContext = (0, react.createContext)({ theme: baseTheme });
135
229
  /**
136
- * Config for `@faasjs/ant-design` components.
230
+ * Provide theme overrides and optional FaasJS client initialization for descendants.
231
+ *
232
+ * Theme overrides are merged with the built-in defaults. When `theme.lang` is omitted, the
233
+ * provider infers a default language from `navigator.language`.
234
+ *
235
+ * @param {ConfigProviderProps} props - Theme overrides and optional FaasJS client configuration.
137
236
  *
138
237
  * @example
139
238
  * ```tsx
140
- * import { ConfigProvider } from '@faasjs/ant-design'
239
+ * import { Blank, ConfigProvider } from '@faasjs/ant-design'
141
240
  *
142
- * <ConfigProvider theme={{ common: { blank: 'Empty' } }}>
143
- * <Blank />
144
- * </ConfigProvider>
241
+ * export function Page() {
242
+ * return (
243
+ * <ConfigProvider theme={{ common: { blank: 'Empty' } }}>
244
+ * <Blank />
245
+ * </ConfigProvider>
246
+ * )
247
+ * }
145
248
  * ```
146
249
  */
147
250
  function ConfigProvider(props) {
@@ -162,24 +265,60 @@ function ConfigProvider(props) {
162
265
  children: props.children
163
266
  });
164
267
  }
268
+ /**
269
+ * Read the current `@faasjs/ant-design` config context.
270
+ *
271
+ * @returns Current config context value containing the resolved theme.
272
+ *
273
+ * @example
274
+ * ```tsx
275
+ * import { Blank, ConfigProvider, useConfigContext } from '@faasjs/ant-design'
276
+ *
277
+ * function EmptyState() {
278
+ * const { theme } = useConfigContext()
279
+ *
280
+ * return <span>{theme.common.blank}</span>
281
+ * }
282
+ *
283
+ * export function Page() {
284
+ * return (
285
+ * <ConfigProvider theme={{ common: { blank: 'N/A' } }}>
286
+ * <EmptyState />
287
+ * </ConfigProvider>
288
+ * )
289
+ * }
290
+ * ```
291
+ */
165
292
  function useConfigContext() {
166
293
  return (0, react.useContext)(ConfigContext);
167
294
  }
168
295
  //#endregion
169
296
  //#region src/Drawer.tsx
170
297
  /**
171
- * Hook style drawer
298
+ * Create a hook-managed Ant Design drawer instance.
299
+ *
300
+ * The returned setter merges partial updates into the current drawer props instead of replacing the
301
+ * entire state object.
302
+ *
303
+ * @param {DrawerProps} [init] - Initial drawer props.
304
+ * @returns Hook-managed drawer element, current props, and a state-merging setter.
172
305
  *
306
+ * @example
173
307
  * ```tsx
308
+ * import { useDrawer } from '@faasjs/ant-design'
309
+ * import { Button } from 'antd'
310
+ *
174
311
  * function Example() {
175
312
  * const { drawer, setDrawerProps } = useDrawer()
176
313
  *
177
- * return <>
178
- * <Button onClick={ () => setDrawerProps(prev => ({ open: !prev.open})) }>
179
- * Toggle
180
- * </Button>
181
- * {drawer}
182
- * </>
314
+ * return (
315
+ * <>
316
+ * <Button onClick={() => setDrawerProps({ open: true, title: 'Details', children: <div>Content</div> })}>
317
+ * Open
318
+ * </Button>
319
+ * {drawer}
320
+ * </>
321
+ * )
183
322
  * }
184
323
  * ```
185
324
  */
@@ -224,6 +363,24 @@ function ErrorChildren(props) {
224
363
  }
225
364
  /**
226
365
  * Styled error boundary.
366
+ *
367
+ * When `errorChildren` is not provided, the fallback UI renders an Ant Design `Alert` containing
368
+ * the captured error message and description.
369
+ *
370
+ * @param {ErrorBoundaryProps} props - Error boundary props forwarded to the underlying React implementation.
371
+ *
372
+ * @example
373
+ * ```tsx
374
+ * import { ErrorBoundary } from '@faasjs/ant-design'
375
+ *
376
+ * export function Page() {
377
+ * return (
378
+ * <ErrorBoundary>
379
+ * <DangerousWidget />
380
+ * </ErrorBoundary>
381
+ * )
382
+ * }
383
+ * ```
227
384
  */
228
385
  function ErrorBoundary(props) {
229
386
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_faasjs_react.ErrorBoundary, {
@@ -234,16 +391,30 @@ function ErrorBoundary(props) {
234
391
  //#endregion
235
392
  //#region src/Modal.tsx
236
393
  /**
237
- * Hook style modal
394
+ * Create a hook-managed Ant Design modal instance.
395
+ *
396
+ * The returned setter merges partial updates into the current modal props instead of replacing the
397
+ * entire state object.
238
398
  *
399
+ * @param {ModalProps} [init] - Initial modal props.
400
+ * @returns Hook-managed modal element, current props, and a state-merging setter.
401
+ *
402
+ * @example
239
403
  * ```tsx
404
+ * import { useModal } from '@faasjs/ant-design'
405
+ * import { Button } from 'antd'
406
+ *
240
407
  * function Example() {
241
408
  * const { modal, setModalProps } = useModal()
242
409
  *
243
- * return <>
244
- * <Button onClick={() => setModalProps({ open: true })}>Open Modal</Button>
245
- * {modal}
246
- * </>
410
+ * return (
411
+ * <>
412
+ * <Button onClick={() => setModalProps({ open: true, title: 'Delete', children: 'Are you sure?' })}>
413
+ * Open Modal
414
+ * </Button>
415
+ * {modal}
416
+ * </>
417
+ * )
247
418
  * }
248
419
  * ```
249
420
  */
@@ -273,6 +444,9 @@ function useModal(init) {
273
444
  }
274
445
  //#endregion
275
446
  //#region src/useApp.ts
447
+ /**
448
+ * Shared context storing message, notification, modal, and drawer helpers.
449
+ */
276
450
  const AppContext = (0, _faasjs_react.createSplittingContext)([
277
451
  "message",
278
452
  "notification",
@@ -282,12 +456,38 @@ const AppContext = (0, _faasjs_react.createSplittingContext)([
282
456
  "setDrawerProps"
283
457
  ]);
284
458
  /**
285
- * Get app context.
459
+ * Read app-level services exposed by the root `App` component.
286
460
  *
287
- * ```ts
288
- * import { useApp } from '@faasjs/ant-design'
461
+ * @template NewT - Narrowed app context shape to read from `AppContext`.
462
+ * @returns Read-only app context value.
289
463
  *
290
- * const { message, notification, setModalProps, setDrawerProps } = useApp()
464
+ * @example
465
+ * ```tsx
466
+ * import { App, useApp } from '@faasjs/ant-design'
467
+ * import { Button } from 'antd'
468
+ *
469
+ * function Page() {
470
+ * const { message, setModalProps } = useApp()
471
+ *
472
+ * return (
473
+ * <Button
474
+ * onClick={() => {
475
+ * message.success('Saved')
476
+ * setModalProps({ open: true, title: 'Done', children: 'Profile updated.' })
477
+ * }}
478
+ * >
479
+ * Save
480
+ * </Button>
481
+ * )
482
+ * }
483
+ *
484
+ * export function Root() {
485
+ * return (
486
+ * <App>
487
+ * <Page />
488
+ * </App>
489
+ * )
490
+ * }
291
491
  * ```
292
492
  */
293
493
  function useApp() {
@@ -306,25 +506,25 @@ function RoutesApp(props) {
306
506
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: props.children });
307
507
  }
308
508
  /**
309
- * App component with Ant Design & FaasJS
509
+ * Render the root provider shell for a FaasJS Ant Design application.
510
+ *
511
+ * `App` initializes Ant Design message and notification APIs, exposes hook-managed modal and
512
+ * drawer state through {@link AppContext}, wraps descendants with {@link ErrorBoundary}, and
513
+ * optionally mounts React Router's `BrowserRouter`.
310
514
  *
311
- * - Based on Ant Design's [ConfigProvider](https://ant.design/components/config-provider/).
312
- * - Integrated Ant Design's [Message](https://ant.design/components/message/) and [Notification](https://ant.design/components/notification/).
313
- * - Based on FaasJS's [ConfigProvider](https://faasjs.com/doc/ant-design/#configprovider).
314
- * - Integrated FaasJS's [Modal](https://faasjs.com/doc/ant-design/#usemodal), [Drawer](https://faasjs.com/doc/ant-design/#usedrawer) and [ErrorBoundary](https://faasjs.com/doc/ant-design/#errorboundary).
315
- * - Integrated React Router's [BrowserRouter](https://api.reactrouter.com/v7/interfaces/react_router.BrowserRouterProps.html).
515
+ * @param {AppProps} props - App shell props including providers, routing, and error handling options.
316
516
  *
317
517
  * @example
318
518
  * ```tsx
319
519
  * import { App } from '@faasjs/ant-design'
320
520
  *
321
- * export default function () {
521
+ * export default function Page() {
322
522
  * return (
323
523
  * <App
324
- * configProviderProps={{}} // https://ant.design/components/config-provider/#API
325
- * browserRouterProps={{}} // https://api.reactrouter.com/v7/interfaces/react_router.BrowserRouterProps.html
326
- * errorBoundaryProps={{}} // https://faasjs.com/doc/ant-design/#errorboundary
327
- * faasConfigProviderProps={{}} // https://faasjs.com/doc/ant-design/#configprovider
524
+ * configProviderProps={{}}
525
+ * browserRouterProps={{}}
526
+ * errorBoundaryProps={{}}
527
+ * faasConfigProviderProps={{}}
328
528
  * >
329
529
  * <div>content</div>
330
530
  * </App>
@@ -335,8 +535,8 @@ function RoutesApp(props) {
335
535
  function App(props) {
336
536
  const [messageApi, messageContextHolder] = antd.message.useMessage();
337
537
  const [notificationApi, notificationContextHolder] = antd.notification.useNotification();
338
- const { modal, modalProps, setModalProps } = useModal();
339
- const { drawer, drawerProps, setDrawerProps } = useDrawer();
538
+ const { modal, modalProps, setModalProps } = useModal({ destroyOnHidden: true });
539
+ const { drawer, drawerProps, setDrawerProps } = useDrawer({ destroyOnHidden: true });
340
540
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_faasjs_react.OptionalWrapper, {
341
541
  condition: !!props.configProviderProps,
342
542
  Wrapper: antd.ConfigProvider,
@@ -382,15 +582,20 @@ function App(props) {
382
582
  //#endregion
383
583
  //#region src/Blank.tsx
384
584
  /**
385
- * Blank component.
585
+ * Render a disabled placeholder when a value is empty.
386
586
  *
387
- * If value is undefined or null, return text, otherwise return value.
587
+ * Empty values include `undefined`, `null`, empty strings, and empty arrays.
588
+ *
589
+ * @param {BlankProps} [options] - Placeholder text and value to render.
590
+ * @returns Rendered value or the configured placeholder text.
388
591
  *
389
592
  * @example
390
593
  * ```tsx
391
594
  * import { Blank } from '@faasjs/ant-design'
392
595
  *
393
- * <Blank value={undefined} text="Empty" />
596
+ * export function FieldPreview() {
597
+ * return <Blank value={undefined} text="Empty" />
598
+ * }
394
599
  * ```
395
600
  */
396
601
  function Blank(options) {
@@ -403,17 +608,14 @@ function Blank(options) {
403
608
  //#endregion
404
609
  //#region src/data.ts
405
610
  /**
406
- * Converts an identifier string to a title case string.
407
- *
408
- * This function takes an identifier string with words separated by underscores,
409
- * capitalizes the first letter of each word, and joins them together without spaces.
611
+ * Convert a snake_case, kebab-case, or spaced identifier into a title-style label.
410
612
  *
411
- * @param id - The identifier string to convert.
412
- * @returns The converted title case string.
613
+ * @param {string | number} id - Identifier to convert.
614
+ * @returns Generated label string.
413
615
  *
414
616
  * @example
415
- * ```typescript
416
- * idToTitle('example_id'); // returns 'ExampleId'
617
+ * ```ts
618
+ * idToTitle('example_id') // 'Example Id'
417
619
  * ```
418
620
  */
419
621
  function idToTitle(id) {
@@ -422,7 +624,24 @@ function idToTitle(id) {
422
624
  return splitted.charAt(0).toUpperCase() + splitted.slice(1);
423
625
  }
424
626
  /**
425
- * convert string[] or number[] to { label, value }[]
627
+ * Normalize primitive options into explicit `{ label, value }` objects.
628
+ *
629
+ * String and number options are converted with {@link idToTitle}, while pre-shaped option objects
630
+ * are returned as-is.
631
+ *
632
+ * @param {BaseOption[]} options - Raw option list to normalize.
633
+ * @returns Normalized option list.
634
+ *
635
+ * @example
636
+ * ```ts
637
+ * import { transferOptions } from '@faasjs/ant-design'
638
+ *
639
+ * transferOptions(['draft', { label: 'Published', value: 'published' }])
640
+ * // [
641
+ * // { label: 'Draft', value: 'draft' },
642
+ * // { label: 'Published', value: 'published' },
643
+ * // ]
644
+ * ```
426
645
  */
427
646
  function transferOptions(options) {
428
647
  if (!options) return [];
@@ -431,6 +650,25 @@ function transferOptions(options) {
431
650
  value: item
432
651
  });
433
652
  }
653
+ /**
654
+ * Normalize raw values into the runtime shape expected by FaasJS Ant Design components.
655
+ *
656
+ * Primitive strings such as `'null'` and `'undefined'` become `null`, comma-delimited array
657
+ * strings are split into arrays, and date or time values are converted to `dayjs` objects.
658
+ *
659
+ * @param {FaasItemType | null | undefined} type - Target field type.
660
+ * @param {any} value - Raw value to normalize.
661
+ * @returns Normalized value for rendering or form initialization.
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * import { transferValue } from '@faasjs/ant-design'
666
+ *
667
+ * transferValue('number', '42') // 42
668
+ * transferValue('boolean', 'true') // true
669
+ * transferValue('string[]', 'a,b') // ['a', 'b']
670
+ * ```
671
+ */
434
672
  function transferValue(type, value) {
435
673
  if (!type) type = "string";
436
674
  if (!type.endsWith("[]") && (typeof value === "undefined" || value === null || value === "" || value === "null" || value === "undefined")) return null;
@@ -456,15 +694,27 @@ function transferValue(type, value) {
456
694
  return value;
457
695
  }
458
696
  /**
459
- * Clone a UnionFaasItemElement with the given props.
697
+ * Clone a {@link UnionFaasItemElement} with FaasJS injection props.
698
+ *
699
+ * React elements are cloned directly, while component references are first wrapped with
700
+ * `createElement`.
460
701
  *
461
- * This function takes a UnionFaasItemElement and props, and returns a cloned element.
462
- * If the provided element is a valid React element, it clones it with the new props.
463
- * Otherwise, it creates a new element from the provided element and props.
702
+ * @param {UnionFaasItemElement} element - Element or component to clone.
703
+ * @param {any} props - Injection props such as `scene`, `value`, `values`, and `index`.
704
+ * @returns Cloned React element ready for rendering.
464
705
  *
465
- * @param element - The UnionFaasItemElement to be cloned.
466
- * @param props - The props to be applied to the cloned element.
467
- * @returns The cloned element with the applied props.
706
+ * @example
707
+ * ```tsx
708
+ * import { cloneUnionFaasItemElement, type UnionFaasItemElement } from '@faasjs/ant-design'
709
+ *
710
+ * const Cell: UnionFaasItemElement<string> = ({ value }) => <span>{value}</span>
711
+ *
712
+ * const element = cloneUnionFaasItemElement(Cell, {
713
+ * scene: 'table',
714
+ * value: 'Hello',
715
+ * index: 0,
716
+ * })
717
+ * ```
468
718
  */
469
719
  function cloneUnionFaasItemElement(element, props) {
470
720
  return (0, react.cloneElement)((0, react.isValidElement)(element) ? element : (0, react.createElement)(element), props);
@@ -547,31 +797,41 @@ function DescriptionItemContent(props) {
547
797
  }
548
798
  DescriptionItemContent.displayName = "DescriptionItemContent";
549
799
  /**
550
- * Description component
800
+ * Render an Ant Design description list from FaasJS item metadata.
551
801
  *
552
- * - Based on [Ant Design Descriptions](https://ant.design/components/descriptions/).
802
+ * The component can render a local `dataSource` directly or resolve one through `faasData`, and
803
+ * it applies the same item type normalization helpers used by the form and table components.
804
+ *
805
+ * @template T - Data record shape rendered by the component.
806
+ * @param {DescriptionProps<T>} props - Description props including items, data source, and optional Faas data config.
807
+ * @throws {Error} When an entry in `extendTypes` omits both `children` and `render`.
553
808
  *
554
809
  * @example
555
810
  * ```tsx
556
811
  * import { Description } from '@faasjs/ant-design'
557
812
  *
558
- * <Description
559
- * title="Title"
560
- * items={[
561
- * {
562
- * id: 'id',
563
- * title: 'Title',
564
- * type: 'string',
565
- * },
566
- * ]}
567
- * dataSource={{ id: 'value' }}
568
- * />
813
+ * export function Detail() {
814
+ * return (
815
+ * <Description
816
+ * title="Title"
817
+ * items={[
818
+ * {
819
+ * id: 'id',
820
+ * title: 'Title',
821
+ * type: 'string',
822
+ * },
823
+ * ]}
824
+ * dataSource={{ id: 'value' }}
825
+ * />
826
+ * )
827
+ * }
569
828
  * ```
570
829
  */
571
- function Description({ faasData, dataSource, renderTitle, extendTypes, ...props }) {
830
+ function Description(props) {
831
+ const { faasData, dataSource, renderTitle, extendTypes, ...descriptionProps } = props;
572
832
  if (faasData && !dataSource) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FaasDataWrapper, {
573
833
  render: ({ data }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Description, {
574
- ...props,
834
+ ...descriptionProps,
575
835
  dataSource: data,
576
836
  ...renderTitle ? { renderTitle } : {},
577
837
  ...extendTypes ? { extendTypes } : {}
@@ -579,9 +839,9 @@ function Description({ faasData, dataSource, renderTitle, extendTypes, ...props
579
839
  ...faasData
580
840
  });
581
841
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Descriptions, {
582
- ...props,
583
- title: typeof renderTitle === "function" ? renderTitle(dataSource) : props.title,
584
- items: props.items.filter((item) => item && !(item.descriptionChildren === null || item.children === null || item.descriptionRender === null || item.render === null) && (!item.if || item.if(dataSource))).map((item) => ({
842
+ ...descriptionProps,
843
+ title: typeof renderTitle === "function" ? renderTitle(dataSource) : descriptionProps.title,
844
+ items: descriptionProps.items.filter((item) => item && !(item.descriptionChildren === null || item.children === null || item.descriptionRender === null || item.render === null) && (!item.if || item.if(dataSource))).map((item) => ({
585
845
  ...item,
586
846
  key: item.id,
587
847
  label: item.title ?? idToTitle(item.id),
@@ -633,20 +893,30 @@ function processProps(propsCopy, config) {
633
893
  return propsCopy;
634
894
  }
635
895
  /**
636
- * FormItem
896
+ * Render a FaasJS-aware Ant Design form field or nested field group.
897
+ *
898
+ * The component derives default labels from `id`, applies required validation messages from the
899
+ * active theme, supports surface-specific union renderers, and can render nested `object` or
900
+ * `object[]` field structures.
637
901
  *
638
- * - Based on [Ant Design Form.Item](https://ant.design/components/form#formitem).
639
- * - Can be used without [Form](https://faasjs.com/doc/ant-design/#form).
902
+ * @template T - Value type rendered or edited by the form item.
903
+ * @param {FormItemProps<T>} props - Form item props including field metadata, rules, and custom renderers.
640
904
  *
641
905
  * @example
642
906
  * ```tsx
643
- * // use inline type
644
- * <FormItem type='string' id='name' />
907
+ * import { FormItem } from '@faasjs/ant-design'
908
+ * import { Input } from 'antd'
645
909
  *
646
- * // use custom type
647
- * <FormItem id='password'>
648
- * <Input.Password />
649
- * </>
910
+ * export function AccountFields() {
911
+ * return (
912
+ * <>
913
+ * <FormItem id="name" type="string" />
914
+ * <FormItem id="password">
915
+ * <Input.Password />
916
+ * </FormItem>
917
+ * </>
918
+ * )
919
+ * }
650
920
  * ```
651
921
  */
652
922
  function FormItem(props) {
@@ -895,108 +1165,189 @@ function isFormItemProps(item) {
895
1165
  return item.id !== void 0;
896
1166
  }
897
1167
  /**
898
- * Form component with Ant Design & FaasJS
1168
+ * Render a data-aware Ant Design form with optional FaasJS submission helpers.
1169
+ *
1170
+ * The component normalizes `initialValues` with {@link transferValue}, renders item definitions
1171
+ * through {@link FormItem}, and can either delegate submission to a custom `onFinish` handler or
1172
+ * the built-in FaasJS request flow configured by `faas`.
1173
+ *
1174
+ * @template Values - Form values shape.
1175
+ * @param {FormProps<Values>} props - Form props including items, submit behavior, and FaasJS integration.
1176
+ *
1177
+ * @example
1178
+ * ```tsx
1179
+ * import { Form } from '@faasjs/ant-design'
1180
+ *
1181
+ * export function ProfileForm() {
1182
+ * return (
1183
+ * <Form
1184
+ * items={[
1185
+ * { id: 'name', required: true },
1186
+ * { id: 'email', required: true },
1187
+ * ]}
1188
+ * onFinish={async (values) => {
1189
+ * console.log(values)
1190
+ * }}
1191
+ * />
1192
+ * )
1193
+ * }
1194
+ * ```
1195
+ *
1196
+ * @example
1197
+ * ```tsx
1198
+ * import { Form } from '@faasjs/ant-design'
899
1199
  *
900
- * - Based on [Ant Design Form](https://ant.design/components/form/).
1200
+ * export function CreateUserForm() {
1201
+ * return (
1202
+ * <Form
1203
+ * initialValues={{ role: 'user' }}
1204
+ * items={[
1205
+ * { id: 'name', required: true },
1206
+ * { id: 'role', options: ['user', 'admin'] },
1207
+ * ]}
1208
+ * faas={{
1209
+ * action: 'user/create',
1210
+ * params: (values) => ({
1211
+ * role: values.role || 'user',
1212
+ * }),
1213
+ * }}
1214
+ * />
1215
+ * )
1216
+ * }
1217
+ * ```
901
1218
  */
902
1219
  function Form(props) {
903
1220
  const [loading, setLoading] = (0, react.useState)(false);
904
- const [computedProps, setComputedProps] = (0, react.useState)();
905
- const [submit, setSubmit] = (0, react.useState)();
1221
+ const [antdProps, setAntdProps] = (0, react.useState)();
1222
+ const [submit, setSubmit] = (0, react.useState)(props.submit === false ? false : {});
1223
+ const [items, setItems] = (0, react.useState)([]);
906
1224
  const config = useConfigContext();
907
1225
  const [extendTypes, setExtendTypes] = (0, react.useState)();
908
1226
  const [form] = antd.Form.useForm(props.form);
909
1227
  const [initialValues, setInitialValues] = (0, react.useState)(props.initialValues || Object.create(null));
1228
+ const [onFinish, setOnFinish] = (0, react.useState)();
910
1229
  (0, _faasjs_react.useEqualEffect)(() => {
911
- const { submit, ...propsCopy } = {
912
- ...props,
913
- form
914
- };
915
- let nextInitialValues = propsCopy.initialValues;
916
- if (typeof submit !== "undefined") setSubmit(submit);
917
- if (propsCopy.initialValues && propsCopy.items?.length) {
918
- for (const key in propsCopy.initialValues) propsCopy.initialValues[key] = transferValue(propsCopy.items.find((item) => isFormItemProps(item) && item.id === key)?.type, propsCopy.initialValues[key]);
919
- nextInitialValues = propsCopy.initialValues;
920
- setInitialValues(propsCopy.initialValues);
921
- delete propsCopy.initialValues;
922
- }
923
- if (propsCopy.items?.length) {
924
- for (const item of propsCopy.items) if (isFormItemProps(item) && item.if) item.hidden = !item.if(nextInitialValues || Object.create(null));
1230
+ if (props.onFinish) {
1231
+ setOnFinish(() => async (values) => {
1232
+ if (!props.onFinish) return;
1233
+ setLoading(true);
1234
+ try {
1235
+ return await props.onFinish(values);
1236
+ } finally {
1237
+ setLoading(false);
1238
+ }
1239
+ });
1240
+ return;
925
1241
  }
926
- const submitTo = typeof submit === "object" ? submit.to : void 0;
927
- if (propsCopy.onFinish) {
928
- const originOnFinish = propsCopy.onFinish;
929
- propsCopy.onFinish = async (values) => {
1242
+ if (props.faas?.action) {
1243
+ setOnFinish(() => async (values) => {
1244
+ if (!props.faas?.action) return;
930
1245
  setLoading(true);
1246
+ let submitValues = values;
931
1247
  try {
932
- if (submitTo?.action) await originOnFinish(values, async (nextValues) => (0, _faasjs_react.faas)(submitTo.action, submitTo.params ? {
933
- ...nextValues,
934
- ...submitTo.params
935
- } : nextValues));
936
- else await originOnFinish(values);
1248
+ if (props.faas?.transformValues) submitValues = await props.faas.transformValues(values);
1249
+ const extraParams = typeof props.faas?.params === "function" ? props.faas.params(submitValues) : props.faas.params;
1250
+ if (extraParams) submitValues = {
1251
+ ...submitValues,
1252
+ ...extraParams
1253
+ };
1254
+ const result = await (0, _faasjs_react.faas)(props.faas.action, submitValues);
1255
+ props.faas.onSuccess?.(result, submitValues);
1256
+ return result;
937
1257
  } catch (error) {
938
- console.error(error);
1258
+ props.faas.onError?.(error, submitValues);
1259
+ throw error;
1260
+ } finally {
1261
+ props.faas.onFinally?.();
1262
+ setLoading(false);
939
1263
  }
940
- setLoading(false);
941
- };
942
- } else if (submitTo?.action) propsCopy.onFinish = async (values) => {
943
- setLoading(true);
944
- return (0, _faasjs_react.faas)(submitTo.action, submitTo.params ? {
945
- ...values,
946
- ...submitTo.params
947
- } : values).then((result) => {
948
- submitTo.then?.(result);
949
- return result;
950
- }).catch((error) => {
951
- submitTo.catch?.(error);
952
- return Promise.reject(error);
953
- }).finally(() => {
954
- submitTo.finally?.();
955
- setLoading(false);
956
1264
  });
957
- };
958
- if (propsCopy.extendTypes) {
959
- setExtendTypes(propsCopy.extendTypes);
960
- delete propsCopy.extendTypes;
1265
+ return;
961
1266
  }
962
- setComputedProps(propsCopy);
963
- }, [form, props]);
1267
+ setOnFinish(void 0);
1268
+ }, [props.onFinish, props.faas]);
1269
+ (0, _faasjs_react.useEqualEffect)(() => {
1270
+ setExtendTypes(props.extendTypes);
1271
+ }, [props.extendTypes]);
1272
+ (0, _faasjs_react.useEqualEffect)(() => {
1273
+ setSubmit(props.submit === false ? false : props.submit || {});
1274
+ }, [props.submit]);
1275
+ (0, _faasjs_react.useEqualEffect)(() => {
1276
+ const nextInitialValues = props.initialValues ? JSON.parse(JSON.stringify(props.initialValues)) : Object.create(null);
1277
+ for (const key in nextInitialValues) nextInitialValues[key] = transferValue(props.items?.find((item) => isFormItemProps(item) && item.id === key)?.type, nextInitialValues[key]);
1278
+ if (props.items?.length) setItems(props.items.map((item) => {
1279
+ if (!isFormItemProps(item) || !item.if) return item;
1280
+ return {
1281
+ ...item,
1282
+ hidden: !item.if(nextInitialValues)
1283
+ };
1284
+ }));
1285
+ else setItems([]);
1286
+ if (props.initialValues) {
1287
+ setInitialValues(nextInitialValues);
1288
+ return;
1289
+ }
1290
+ setInitialValues(null);
1291
+ }, [props.initialValues, props.items]);
1292
+ (0, _faasjs_react.useEqualEffect)(() => {
1293
+ const propsCopy = { ...props };
1294
+ delete propsCopy.onFinish;
1295
+ delete propsCopy.faas;
1296
+ delete propsCopy.extendTypes;
1297
+ delete propsCopy.submit;
1298
+ delete propsCopy.items;
1299
+ delete propsCopy.initialValues;
1300
+ setAntdProps(propsCopy);
1301
+ }, [props]);
964
1302
  const onValuesChange = (0, _faasjs_react.useEqualCallback)((changedValues, allValues) => {
965
1303
  console.debug("Form:onValuesChange", changedValues, allValues);
966
1304
  if (props.onValuesChange) props.onValuesChange(changedValues, allValues);
967
- if (!props.items) return;
1305
+ if (!items.length) return;
968
1306
  for (const key in changedValues) {
969
- const item = computedProps?.items?.find((i) => isFormItemProps(i) && i.id === key);
1307
+ const item = items.find((i) => isFormItemProps(i) && i.id === key);
970
1308
  if (item?.onValueChange) item.onValueChange(changedValues[key], allValues, form);
971
1309
  }
972
- }, [computedProps]);
1310
+ }, [
1311
+ items,
1312
+ props.onValuesChange,
1313
+ form
1314
+ ]);
973
1315
  (0, _faasjs_react.useEqualEffect)(() => {
974
1316
  if (!initialValues) return;
975
1317
  console.debug("Form:initialValues", initialValues);
976
1318
  form.setFieldsValue(initialValues);
977
1319
  setInitialValues(null);
978
- }, [form, initialValues]);
979
- if (!computedProps) return null;
1320
+ }, [
1321
+ form,
1322
+ initialValues,
1323
+ items
1324
+ ]);
1325
+ if (!antdProps) return null;
1326
+ const submitButtonProps = typeof submit === "object" ? submit.buttonProps : void 0;
1327
+ const submitButtonLoading = loading ? true : submitButtonProps?.loading ?? false;
980
1328
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(antd.Form, {
981
- ...computedProps,
1329
+ ...antdProps,
1330
+ form,
1331
+ onFinish,
982
1332
  onValuesChange,
983
1333
  children: [
984
- computedProps.beforeItems,
985
- computedProps.items?.map((item) => {
1334
+ props.beforeItems,
1335
+ items.map((item) => {
986
1336
  if (isFormItemProps(item)) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormItem, {
987
1337
  ...item,
988
1338
  ...extendTypes ? { extendTypes } : {}
989
1339
  }, item.id);
990
1340
  return item;
991
1341
  }),
992
- computedProps.children,
1342
+ props.children,
993
1343
  typeof submit !== "boolean" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Button, {
1344
+ ...submitButtonProps,
994
1345
  htmlType: "submit",
995
- type: "primary",
996
- loading,
1346
+ type: submitButtonProps?.type || "primary",
1347
+ loading: submitButtonLoading,
997
1348
  children: submit?.text || config.theme.Form.submit.text
998
1349
  }),
999
- computedProps.footer
1350
+ props.footer
1000
1351
  ]
1001
1352
  });
1002
1353
  }
@@ -1010,15 +1361,27 @@ Form.Provider = antd.Form.Provider;
1010
1361
  //#endregion
1011
1362
  //#region src/Link.tsx
1012
1363
  /**
1013
- * Link component with button
1364
+ * Render a navigation-aware link or button.
1365
+ *
1366
+ * Internal links are pushed through React Router, while links with `_blank` targets are opened
1367
+ * with `window.open`.
1368
+ *
1369
+ * @param {LinkProps} props - Link props controlling navigation target, rendering mode, and button behavior.
1014
1370
  *
1015
1371
  * @example
1016
1372
  * ```tsx
1017
- * // pure link
1018
- * <Link href="/">Home</Link>
1373
+ * import { Link } from '@faasjs/ant-design'
1019
1374
  *
1020
- * // link with button
1021
- * <Link href="/" button={{ type:'primary' }}>Home</Link>
1375
+ * export function Navigation() {
1376
+ * return (
1377
+ * <>
1378
+ * <Link href="/">Home</Link>
1379
+ * <Link href="/users/new" button={{ type: 'primary' }}>
1380
+ * Create User
1381
+ * </Link>
1382
+ * </>
1383
+ * )
1384
+ * }
1022
1385
  * ```
1023
1386
  */
1024
1387
  function Link(props) {
@@ -1075,6 +1438,23 @@ function Link(props) {
1075
1438
  }
1076
1439
  //#endregion
1077
1440
  //#region src/Routers.tsx
1441
+ /**
1442
+ * Default 404 route element that uses the configured localized title.
1443
+ *
1444
+ * @example
1445
+ * ```tsx
1446
+ * import { PageNotFound, Routes } from '@faasjs/ant-design'
1447
+ *
1448
+ * export function AppRoutes() {
1449
+ * return (
1450
+ * <Routes
1451
+ * routes={[{ path: '/', element: <div>Home</div> }]}
1452
+ * notFound={<PageNotFound />}
1453
+ * />
1454
+ * )
1455
+ * }
1456
+ * ```
1457
+ */
1078
1458
  function PageNotFound() {
1079
1459
  const { theme } = useConfigContext();
1080
1460
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(antd.Result, {
@@ -1083,22 +1463,31 @@ function PageNotFound() {
1083
1463
  });
1084
1464
  }
1085
1465
  /**
1086
- * Routes with lazy loading and 404 page.
1466
+ * Render React Router routes with lazy-page support and a default 404 route.
1467
+ *
1468
+ * The wrapper adds a catch-all route automatically and uses an Ant Design `Skeleton` fallback when
1469
+ * `fallback` is not provided.
1470
+ *
1471
+ * @param {RoutesProps} props - Route definitions and optional fallback or 404 elements.
1087
1472
  *
1088
1473
  * @example
1089
1474
  * ```tsx
1090
1475
  * import { Routes, lazy } from '@faasjs/ant-design'
1091
1476
  * import { BrowserRouter } from 'react-router-dom'
1092
1477
  *
1093
- * export function App () {
1094
- * return <BrowserRouter>
1095
- * <Routes routes={[
1096
- * {
1097
- * path: '/',
1098
- * page: lazy(() => import('./pages/home'))
1099
- * }
1100
- * ]} />
1101
- * </BrowserRouter>
1478
+ * export function App() {
1479
+ * return (
1480
+ * <BrowserRouter>
1481
+ * <Routes
1482
+ * routes={[
1483
+ * {
1484
+ * path: '/',
1485
+ * page: lazy(() => import('./pages/home')),
1486
+ * },
1487
+ * ]}
1488
+ * />
1489
+ * </BrowserRouter>
1490
+ * )
1102
1491
  * }
1103
1492
  * ```
1104
1493
  */
@@ -1139,12 +1528,39 @@ function processValue(item, value) {
1139
1528
  return transferred;
1140
1529
  }
1141
1530
  /**
1142
- * Table component with Ant Design & FaasJS
1531
+ * Render an Ant Design table from FaasJS item metadata.
1532
+ *
1533
+ * The component can render local `dataSource` rows or resolve remote rows through `faasData`. It
1534
+ * also generates default filters and sorters for built-in item types unless you disable them with
1535
+ * the corresponding Ant Design column props.
1143
1536
  *
1144
- * - Based on [Ant Design Table](https://ant.design/components/table/).
1145
- * - Support FaasJS injection.
1146
- * - Auto generate filter dropdown (disable with `filterDropdown: false`).
1147
- * - Auto generate sorter (disable with `sorter: false`).
1537
+ * @template T - Row record type rendered by the table.
1538
+ * @template ExtendTypes - Additional item prop shape accepted by `items`.
1539
+ * @param {TableProps<T, ExtendTypes>} props - Table props including columns, data source, and optional Faas data config.
1540
+ * @throws {Error} When an entry in `extendTypes` omits both `children` and `render`.
1541
+ *
1542
+ * @example
1543
+ * ```tsx
1544
+ * import { Table } from '@faasjs/ant-design'
1545
+ *
1546
+ * const rows = [
1547
+ * { id: 1, name: 'Alice', active: true },
1548
+ * { id: 2, name: 'Bob', active: false },
1549
+ * ]
1550
+ *
1551
+ * export function UserTable() {
1552
+ * return (
1553
+ * <Table
1554
+ * rowKey="id"
1555
+ * dataSource={rows}
1556
+ * items={[
1557
+ * { id: 'name', title: 'Name' },
1558
+ * { id: 'active', type: 'boolean', title: 'Active' },
1559
+ * ]}
1560
+ * />
1561
+ * )
1562
+ * }
1563
+ * ```
1148
1564
  */
1149
1565
  function Table(props) {
1150
1566
  const [columns, setColumns] = (0, react.useState)();
@@ -1530,28 +1946,32 @@ function FaasDataTable({ props, columns, data, params, reload, loading }) {
1530
1946
  //#endregion
1531
1947
  //#region src/Tabs.tsx
1532
1948
  /**
1533
- * Tabs component with Ant Design & FaasJS
1949
+ * Render an Ant Design tabs wrapper that accepts FaasJS-style tab definitions.
1950
+ *
1951
+ * Missing `key` and `label` values are derived from each tab's `id` and `title`.
1534
1952
  *
1535
- * - Based on [Ant Design Tabs](https://ant.design/components/tabs/).
1536
- * - Support auto skip null/false tab item.
1537
- * - Support `id` as key and label.
1953
+ * @param {TabsProps} props - Tabs props including tab items and Ant Design tab options.
1538
1954
  *
1539
1955
  * @example
1540
1956
  * ```tsx
1541
1957
  * import { Tabs } from '@faasjs/ant-design'
1542
1958
  *
1543
- * <Tabs
1544
- * items={[
1545
- * {
1546
- * id: 'id',
1547
- * children: 'content',
1548
- * },
1549
- * 1 === 0 && {
1550
- * id: 'hidden',
1551
- * children: 'content',
1552
- * },
1553
- * ]}
1554
- * />
1959
+ * export function Page() {
1960
+ * return (
1961
+ * <Tabs
1962
+ * items={[
1963
+ * {
1964
+ * id: 'id',
1965
+ * children: 'content',
1966
+ * },
1967
+ * 1 === 0 && {
1968
+ * id: 'hidden',
1969
+ * children: 'content',
1970
+ * },
1971
+ * ]}
1972
+ * />
1973
+ * )
1974
+ * }
1555
1975
  * ```
1556
1976
  */
1557
1977
  function Tabs(props) {
@@ -1567,21 +1987,24 @@ function Tabs(props) {
1567
1987
  //#endregion
1568
1988
  //#region src/Title.tsx
1569
1989
  /**
1570
- * Title is used to change the title of the page
1990
+ * Update `document.title` and optionally render the title inline.
1571
1991
  *
1572
- * Return null by default.
1992
+ * The component returns `null` by default and is often used only for its side effect.
1573
1993
  *
1574
- * ```tsx
1575
- * // return null
1576
- * <Title title='hi' /> // => change the document.title to 'hi'
1577
- * <Title title={['a', 'b']} /> // => change the document.title to 'a - b'
1994
+ * @param {TitleProps} props - Title props controlling document title updates and optional inline rendering.
1578
1995
  *
1579
- * // return h1
1580
- * <Title title='hi' h1 /> // => <h1>hi</h1>
1581
- * <Title title={['a', 'b']} h1 /> // => <h1>a</h1>
1996
+ * @example
1997
+ * ```tsx
1998
+ * import { Title } from '@faasjs/ant-design'
1582
1999
  *
1583
- * // return children
1584
- * <Title title='hi'><CustomTitle /></Title> // => <CustomTitle />
2000
+ * export function DetailPage() {
2001
+ * return (
2002
+ * <>
2003
+ * <Title title={['Orders', 'Detail']} h1 />
2004
+ * <div>...</div>
2005
+ * </>
2006
+ * )
2007
+ * }
1585
2008
  * ```
1586
2009
  */
1587
2010
  function Title(props) {
@@ -1605,12 +2028,20 @@ function Title(props) {
1605
2028
  //#endregion
1606
2029
  //#region src/useThemeToken.ts
1607
2030
  /**
1608
- * Hook to retrieve the theme token from the Ant Design theme configuration.
2031
+ * Read the current Ant Design theme token.
1609
2032
  *
1610
- * This function uses the `theme.useToken` method to get the current theme configuration
1611
- * and returns the `token` property from the configuration.
2033
+ * @returns Ant Design global token object for the active theme.
1612
2034
  *
1613
- * @returns {GlobalToken} The theme token from the Ant Design theme configuration.
2035
+ * @example
2036
+ * ```tsx
2037
+ * import { useThemeToken } from '@faasjs/ant-design'
2038
+ *
2039
+ * function PrimarySwatch() {
2040
+ * const { colorPrimary } = useThemeToken()
2041
+ *
2042
+ * return <div style={{ width: 24, height: 24, background: colorPrimary }} />
2043
+ * }
2044
+ * ```
1614
2045
  */
1615
2046
  function useThemeToken() {
1616
2047
  return antd.theme.useToken().token;