@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.mjs CHANGED
@@ -8,15 +8,24 @@ import { CheckOutlined, CloseOutlined, MinusCircleOutlined, PlusOutlined } from
8
8
  import dayjs from "dayjs";
9
9
  //#region src/Loading.tsx
10
10
  /**
11
- * Loading component based on Spin
11
+ * Render an Ant Design loading spinner with an optional content fallback.
12
+ *
13
+ * @param {LoadingProps} props - Loading indicator props and optional wrapped children.
12
14
  *
13
15
  * @example
14
16
  * ```tsx
15
- * <Loading /> // display loading
17
+ * import { Loading } from '@faasjs/ant-design'
16
18
  *
17
- * <Loading loading={ !remoteData }>
18
- * <div>{remoteData}</div>
19
- * </Loading>
19
+ * export function Page({ remoteData }: { remoteData?: string }) {
20
+ * return (
21
+ * <>
22
+ * <Loading />
23
+ * <Loading loading={!remoteData}>
24
+ * <div>{remoteData}</div>
25
+ * </Loading>
26
+ * </>
27
+ * )
28
+ * }
20
29
  * ```
21
30
  */
22
31
  function Loading(props) {
@@ -35,18 +44,84 @@ function Loading(props) {
35
44
  //#endregion
36
45
  //#region src/FaasDataWrapper.tsx
37
46
  /**
38
- * FaasDataWrapper component with Loading
47
+ * Render the `@faasjs/react` data wrapper with an Ant Design loading fallback.
48
+ *
49
+ * When `loading` is not provided, the component renders {@link Loading} with `loadingProps` while
50
+ * the wrapped FaasJS request is pending.
51
+ *
52
+ * @template T - Action path or response data type used for inference.
53
+ * @param {FaasDataWrapperProps<T>} props - Wrapper props including loading fallbacks and request configuration.
39
54
  *
40
55
  * @example
41
56
  * ```tsx
42
- * function MyComponent (props: FaasDataInjection) {
43
- * return <div>{ props.data }</div>
57
+ * import { Alert, Button } from 'antd'
58
+ * import { FaasDataWrapper } from '@faasjs/ant-design'
59
+ *
60
+ * type User = {
61
+ * name: string
62
+ * }
63
+ *
64
+ * function UserView(props: {
65
+ * data?: User
66
+ * error?: Error
67
+ * reload?: () => void
68
+ * }) {
69
+ * if (props.error) {
70
+ * return (
71
+ * <Alert
72
+ * type="error"
73
+ * message={props.error.message}
74
+ * action={
75
+ * <Button size="small" onClick={() => props.reload?.()}>
76
+ * Retry
77
+ * </Button>
78
+ * }
79
+ * />
80
+ * )
81
+ * }
82
+ *
83
+ * return <div>Hello, {props.data?.name}</div>
84
+ * }
85
+ *
86
+ * // Render-prop mode
87
+ * export function UserProfile(props: { id: number }) {
88
+ * return (
89
+ * <FaasDataWrapper<User>
90
+ * action="user/get"
91
+ * params={{ id: props.id }}
92
+ * loading={<div>Loading user...</div>}
93
+ * render={({ data, error, reload }) => {
94
+ * if (error) {
95
+ * return (
96
+ * <Alert
97
+ * type="error"
98
+ * message={error.message}
99
+ * action={
100
+ * <Button size="small" onClick={() => reload()}>
101
+ * Retry
102
+ * </Button>
103
+ * }
104
+ * />
105
+ * )
106
+ * }
107
+ *
108
+ * return <div>Hello, {data.name}</div>
109
+ * }}
110
+ * />
111
+ * )
44
112
  * }
45
113
  *
46
- * function MyPage () {
47
- * return <FaasDataWrapper action="test" params={{ a: 1 }}>
48
- * <MyComponent />
49
- * </FaasDataWrapper>
114
+ * // Children injection mode
115
+ * export function UserProfileWithChildren(props: { id: number }) {
116
+ * return (
117
+ * <FaasDataWrapper<User>
118
+ * action="user/get"
119
+ * params={{ id: props.id }}
120
+ * loading={<div>Loading user...</div>}
121
+ * >
122
+ * <UserView />
123
+ * </FaasDataWrapper>
124
+ * )
50
125
  * }
51
126
  * ```
52
127
  */
@@ -57,11 +132,27 @@ function FaasDataWrapper(props) {
57
132
  });
58
133
  }
59
134
  /**
60
- * HOC to wrap a component with FaasDataWrapper and Loading
135
+ * Wrap a component with {@link FaasDataWrapper} and its Ant Design loading fallback.
136
+ *
137
+ * @template PathOrData - Action path or response data type used for inference.
138
+ * @template TComponentProps - Component props including injected Faas data fields.
139
+ * @param {React.FC<TComponentProps & Record<string, any>>} Component - Component that consumes injected Faas data props.
140
+ * @param {FaasDataWrapperProps<PathOrData>} faasProps - Request configuration forwarded to {@link FaasDataWrapper}.
141
+ * @returns Higher-order component that injects Faas data props.
61
142
  *
62
143
  * @example
63
144
  * ```tsx
64
- * const MyComponent = withFaasData(({ data }) => <div>{data.name}</div>, { action: 'test', params: { a: 1 } })
145
+ * import { withFaasData } from '@faasjs/ant-design'
146
+ *
147
+ * const UserCard = withFaasData(
148
+ * ({ data, error, reload }) =>
149
+ * error ? (
150
+ * <a onClick={() => reload()}>Retry</a>
151
+ * ) : (
152
+ * <div>{data.name}</div>
153
+ * ),
154
+ * { action: 'user/get', params: { id: 1 } },
155
+ * )
65
156
  * ```
66
157
  */
67
158
  function withFaasData(Component, faasProps) {
@@ -107,17 +198,29 @@ const baseTheme = {
107
198
  },
108
199
  Link: { style: {} }
109
200
  };
201
+ /**
202
+ * React context storing the resolved FaasJS Ant Design theme.
203
+ */
110
204
  const ConfigContext = createContext({ theme: baseTheme });
111
205
  /**
112
- * Config for `@faasjs/ant-design` components.
206
+ * Provide theme overrides and optional FaasJS client initialization for descendants.
207
+ *
208
+ * Theme overrides are merged with the built-in defaults. When `theme.lang` is omitted, the
209
+ * provider infers a default language from `navigator.language`.
210
+ *
211
+ * @param {ConfigProviderProps} props - Theme overrides and optional FaasJS client configuration.
113
212
  *
114
213
  * @example
115
214
  * ```tsx
116
- * import { ConfigProvider } from '@faasjs/ant-design'
215
+ * import { Blank, ConfigProvider } from '@faasjs/ant-design'
117
216
  *
118
- * <ConfigProvider theme={{ common: { blank: 'Empty' } }}>
119
- * <Blank />
120
- * </ConfigProvider>
217
+ * export function Page() {
218
+ * return (
219
+ * <ConfigProvider theme={{ common: { blank: 'Empty' } }}>
220
+ * <Blank />
221
+ * </ConfigProvider>
222
+ * )
223
+ * }
121
224
  * ```
122
225
  */
123
226
  function ConfigProvider(props) {
@@ -138,24 +241,60 @@ function ConfigProvider(props) {
138
241
  children: props.children
139
242
  });
140
243
  }
244
+ /**
245
+ * Read the current `@faasjs/ant-design` config context.
246
+ *
247
+ * @returns Current config context value containing the resolved theme.
248
+ *
249
+ * @example
250
+ * ```tsx
251
+ * import { Blank, ConfigProvider, useConfigContext } from '@faasjs/ant-design'
252
+ *
253
+ * function EmptyState() {
254
+ * const { theme } = useConfigContext()
255
+ *
256
+ * return <span>{theme.common.blank}</span>
257
+ * }
258
+ *
259
+ * export function Page() {
260
+ * return (
261
+ * <ConfigProvider theme={{ common: { blank: 'N/A' } }}>
262
+ * <EmptyState />
263
+ * </ConfigProvider>
264
+ * )
265
+ * }
266
+ * ```
267
+ */
141
268
  function useConfigContext() {
142
269
  return useContext(ConfigContext);
143
270
  }
144
271
  //#endregion
145
272
  //#region src/Drawer.tsx
146
273
  /**
147
- * Hook style drawer
274
+ * Create a hook-managed Ant Design drawer instance.
275
+ *
276
+ * The returned setter merges partial updates into the current drawer props instead of replacing the
277
+ * entire state object.
278
+ *
279
+ * @param {DrawerProps} [init] - Initial drawer props.
280
+ * @returns Hook-managed drawer element, current props, and a state-merging setter.
148
281
  *
282
+ * @example
149
283
  * ```tsx
284
+ * import { useDrawer } from '@faasjs/ant-design'
285
+ * import { Button } from 'antd'
286
+ *
150
287
  * function Example() {
151
288
  * const { drawer, setDrawerProps } = useDrawer()
152
289
  *
153
- * return <>
154
- * <Button onClick={ () => setDrawerProps(prev => ({ open: !prev.open})) }>
155
- * Toggle
156
- * </Button>
157
- * {drawer}
158
- * </>
290
+ * return (
291
+ * <>
292
+ * <Button onClick={() => setDrawerProps({ open: true, title: 'Details', children: <div>Content</div> })}>
293
+ * Open
294
+ * </Button>
295
+ * {drawer}
296
+ * </>
297
+ * )
159
298
  * }
160
299
  * ```
161
300
  */
@@ -200,6 +339,24 @@ function ErrorChildren(props) {
200
339
  }
201
340
  /**
202
341
  * Styled error boundary.
342
+ *
343
+ * When `errorChildren` is not provided, the fallback UI renders an Ant Design `Alert` containing
344
+ * the captured error message and description.
345
+ *
346
+ * @param {ErrorBoundaryProps} props - Error boundary props forwarded to the underlying React implementation.
347
+ *
348
+ * @example
349
+ * ```tsx
350
+ * import { ErrorBoundary } from '@faasjs/ant-design'
351
+ *
352
+ * export function Page() {
353
+ * return (
354
+ * <ErrorBoundary>
355
+ * <DangerousWidget />
356
+ * </ErrorBoundary>
357
+ * )
358
+ * }
359
+ * ```
203
360
  */
204
361
  function ErrorBoundary(props) {
205
362
  return /* @__PURE__ */ jsx(ErrorBoundary$1, {
@@ -210,16 +367,30 @@ function ErrorBoundary(props) {
210
367
  //#endregion
211
368
  //#region src/Modal.tsx
212
369
  /**
213
- * Hook style modal
370
+ * Create a hook-managed Ant Design modal instance.
371
+ *
372
+ * The returned setter merges partial updates into the current modal props instead of replacing the
373
+ * entire state object.
214
374
  *
375
+ * @param {ModalProps} [init] - Initial modal props.
376
+ * @returns Hook-managed modal element, current props, and a state-merging setter.
377
+ *
378
+ * @example
215
379
  * ```tsx
380
+ * import { useModal } from '@faasjs/ant-design'
381
+ * import { Button } from 'antd'
382
+ *
216
383
  * function Example() {
217
384
  * const { modal, setModalProps } = useModal()
218
385
  *
219
- * return <>
220
- * <Button onClick={() => setModalProps({ open: true })}>Open Modal</Button>
221
- * {modal}
222
- * </>
386
+ * return (
387
+ * <>
388
+ * <Button onClick={() => setModalProps({ open: true, title: 'Delete', children: 'Are you sure?' })}>
389
+ * Open Modal
390
+ * </Button>
391
+ * {modal}
392
+ * </>
393
+ * )
223
394
  * }
224
395
  * ```
225
396
  */
@@ -249,6 +420,9 @@ function useModal(init) {
249
420
  }
250
421
  //#endregion
251
422
  //#region src/useApp.ts
423
+ /**
424
+ * Shared context storing message, notification, modal, and drawer helpers.
425
+ */
252
426
  const AppContext = createSplittingContext([
253
427
  "message",
254
428
  "notification",
@@ -258,12 +432,38 @@ const AppContext = createSplittingContext([
258
432
  "setDrawerProps"
259
433
  ]);
260
434
  /**
261
- * Get app context.
435
+ * Read app-level services exposed by the root `App` component.
262
436
  *
263
- * ```ts
264
- * import { useApp } from '@faasjs/ant-design'
437
+ * @template NewT - Narrowed app context shape to read from `AppContext`.
438
+ * @returns Read-only app context value.
265
439
  *
266
- * const { message, notification, setModalProps, setDrawerProps } = useApp()
440
+ * @example
441
+ * ```tsx
442
+ * import { App, useApp } from '@faasjs/ant-design'
443
+ * import { Button } from 'antd'
444
+ *
445
+ * function Page() {
446
+ * const { message, setModalProps } = useApp()
447
+ *
448
+ * return (
449
+ * <Button
450
+ * onClick={() => {
451
+ * message.success('Saved')
452
+ * setModalProps({ open: true, title: 'Done', children: 'Profile updated.' })
453
+ * }}
454
+ * >
455
+ * Save
456
+ * </Button>
457
+ * )
458
+ * }
459
+ *
460
+ * export function Root() {
461
+ * return (
462
+ * <App>
463
+ * <Page />
464
+ * </App>
465
+ * )
466
+ * }
267
467
  * ```
268
468
  */
269
469
  function useApp() {
@@ -282,25 +482,25 @@ function RoutesApp(props) {
282
482
  return /* @__PURE__ */ jsx(Fragment, { children: props.children });
283
483
  }
284
484
  /**
285
- * App component with Ant Design & FaasJS
485
+ * Render the root provider shell for a FaasJS Ant Design application.
486
+ *
487
+ * `App` initializes Ant Design message and notification APIs, exposes hook-managed modal and
488
+ * drawer state through {@link AppContext}, wraps descendants with {@link ErrorBoundary}, and
489
+ * optionally mounts React Router's `BrowserRouter`.
286
490
  *
287
- * - Based on Ant Design's [ConfigProvider](https://ant.design/components/config-provider/).
288
- * - Integrated Ant Design's [Message](https://ant.design/components/message/) and [Notification](https://ant.design/components/notification/).
289
- * - Based on FaasJS's [ConfigProvider](https://faasjs.com/doc/ant-design/#configprovider).
290
- * - 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).
291
- * - Integrated React Router's [BrowserRouter](https://api.reactrouter.com/v7/interfaces/react_router.BrowserRouterProps.html).
491
+ * @param {AppProps} props - App shell props including providers, routing, and error handling options.
292
492
  *
293
493
  * @example
294
494
  * ```tsx
295
495
  * import { App } from '@faasjs/ant-design'
296
496
  *
297
- * export default function () {
497
+ * export default function Page() {
298
498
  * return (
299
499
  * <App
300
- * configProviderProps={{}} // https://ant.design/components/config-provider/#API
301
- * browserRouterProps={{}} // https://api.reactrouter.com/v7/interfaces/react_router.BrowserRouterProps.html
302
- * errorBoundaryProps={{}} // https://faasjs.com/doc/ant-design/#errorboundary
303
- * faasConfigProviderProps={{}} // https://faasjs.com/doc/ant-design/#configprovider
500
+ * configProviderProps={{}}
501
+ * browserRouterProps={{}}
502
+ * errorBoundaryProps={{}}
503
+ * faasConfigProviderProps={{}}
304
504
  * >
305
505
  * <div>content</div>
306
506
  * </App>
@@ -311,8 +511,8 @@ function RoutesApp(props) {
311
511
  function App(props) {
312
512
  const [messageApi, messageContextHolder] = message.useMessage();
313
513
  const [notificationApi, notificationContextHolder] = notification.useNotification();
314
- const { modal, modalProps, setModalProps } = useModal();
315
- const { drawer, drawerProps, setDrawerProps } = useDrawer();
514
+ const { modal, modalProps, setModalProps } = useModal({ destroyOnHidden: true });
515
+ const { drawer, drawerProps, setDrawerProps } = useDrawer({ destroyOnHidden: true });
316
516
  return /* @__PURE__ */ jsx(OptionalWrapper, {
317
517
  condition: !!props.configProviderProps,
318
518
  Wrapper: ConfigProvider$1,
@@ -358,15 +558,20 @@ function App(props) {
358
558
  //#endregion
359
559
  //#region src/Blank.tsx
360
560
  /**
361
- * Blank component.
561
+ * Render a disabled placeholder when a value is empty.
362
562
  *
363
- * If value is undefined or null, return text, otherwise return value.
563
+ * Empty values include `undefined`, `null`, empty strings, and empty arrays.
564
+ *
565
+ * @param {BlankProps} [options] - Placeholder text and value to render.
566
+ * @returns Rendered value or the configured placeholder text.
364
567
  *
365
568
  * @example
366
569
  * ```tsx
367
570
  * import { Blank } from '@faasjs/ant-design'
368
571
  *
369
- * <Blank value={undefined} text="Empty" />
572
+ * export function FieldPreview() {
573
+ * return <Blank value={undefined} text="Empty" />
574
+ * }
370
575
  * ```
371
576
  */
372
577
  function Blank(options) {
@@ -379,17 +584,14 @@ function Blank(options) {
379
584
  //#endregion
380
585
  //#region src/data.ts
381
586
  /**
382
- * Converts an identifier string to a title case string.
383
- *
384
- * This function takes an identifier string with words separated by underscores,
385
- * capitalizes the first letter of each word, and joins them together without spaces.
587
+ * Convert a snake_case, kebab-case, or spaced identifier into a title-style label.
386
588
  *
387
- * @param id - The identifier string to convert.
388
- * @returns The converted title case string.
589
+ * @param {string | number} id - Identifier to convert.
590
+ * @returns Generated label string.
389
591
  *
390
592
  * @example
391
- * ```typescript
392
- * idToTitle('example_id'); // returns 'ExampleId'
593
+ * ```ts
594
+ * idToTitle('example_id') // 'Example Id'
393
595
  * ```
394
596
  */
395
597
  function idToTitle(id) {
@@ -398,7 +600,24 @@ function idToTitle(id) {
398
600
  return splitted.charAt(0).toUpperCase() + splitted.slice(1);
399
601
  }
400
602
  /**
401
- * convert string[] or number[] to { label, value }[]
603
+ * Normalize primitive options into explicit `{ label, value }` objects.
604
+ *
605
+ * String and number options are converted with {@link idToTitle}, while pre-shaped option objects
606
+ * are returned as-is.
607
+ *
608
+ * @param {BaseOption[]} options - Raw option list to normalize.
609
+ * @returns Normalized option list.
610
+ *
611
+ * @example
612
+ * ```ts
613
+ * import { transferOptions } from '@faasjs/ant-design'
614
+ *
615
+ * transferOptions(['draft', { label: 'Published', value: 'published' }])
616
+ * // [
617
+ * // { label: 'Draft', value: 'draft' },
618
+ * // { label: 'Published', value: 'published' },
619
+ * // ]
620
+ * ```
402
621
  */
403
622
  function transferOptions(options) {
404
623
  if (!options) return [];
@@ -407,6 +626,25 @@ function transferOptions(options) {
407
626
  value: item
408
627
  });
409
628
  }
629
+ /**
630
+ * Normalize raw values into the runtime shape expected by FaasJS Ant Design components.
631
+ *
632
+ * Primitive strings such as `'null'` and `'undefined'` become `null`, comma-delimited array
633
+ * strings are split into arrays, and date or time values are converted to `dayjs` objects.
634
+ *
635
+ * @param {FaasItemType | null | undefined} type - Target field type.
636
+ * @param {any} value - Raw value to normalize.
637
+ * @returns Normalized value for rendering or form initialization.
638
+ *
639
+ * @example
640
+ * ```ts
641
+ * import { transferValue } from '@faasjs/ant-design'
642
+ *
643
+ * transferValue('number', '42') // 42
644
+ * transferValue('boolean', 'true') // true
645
+ * transferValue('string[]', 'a,b') // ['a', 'b']
646
+ * ```
647
+ */
410
648
  function transferValue(type, value) {
411
649
  if (!type) type = "string";
412
650
  if (!type.endsWith("[]") && (typeof value === "undefined" || value === null || value === "" || value === "null" || value === "undefined")) return null;
@@ -432,15 +670,27 @@ function transferValue(type, value) {
432
670
  return value;
433
671
  }
434
672
  /**
435
- * Clone a UnionFaasItemElement with the given props.
673
+ * Clone a {@link UnionFaasItemElement} with FaasJS injection props.
674
+ *
675
+ * React elements are cloned directly, while component references are first wrapped with
676
+ * `createElement`.
436
677
  *
437
- * This function takes a UnionFaasItemElement and props, and returns a cloned element.
438
- * If the provided element is a valid React element, it clones it with the new props.
439
- * Otherwise, it creates a new element from the provided element and props.
678
+ * @param {UnionFaasItemElement} element - Element or component to clone.
679
+ * @param {any} props - Injection props such as `scene`, `value`, `values`, and `index`.
680
+ * @returns Cloned React element ready for rendering.
440
681
  *
441
- * @param element - The UnionFaasItemElement to be cloned.
442
- * @param props - The props to be applied to the cloned element.
443
- * @returns The cloned element with the applied props.
682
+ * @example
683
+ * ```tsx
684
+ * import { cloneUnionFaasItemElement, type UnionFaasItemElement } from '@faasjs/ant-design'
685
+ *
686
+ * const Cell: UnionFaasItemElement<string> = ({ value }) => <span>{value}</span>
687
+ *
688
+ * const element = cloneUnionFaasItemElement(Cell, {
689
+ * scene: 'table',
690
+ * value: 'Hello',
691
+ * index: 0,
692
+ * })
693
+ * ```
444
694
  */
445
695
  function cloneUnionFaasItemElement(element, props) {
446
696
  return cloneElement(isValidElement(element) ? element : createElement(element), props);
@@ -523,31 +773,41 @@ function DescriptionItemContent(props) {
523
773
  }
524
774
  DescriptionItemContent.displayName = "DescriptionItemContent";
525
775
  /**
526
- * Description component
776
+ * Render an Ant Design description list from FaasJS item metadata.
527
777
  *
528
- * - Based on [Ant Design Descriptions](https://ant.design/components/descriptions/).
778
+ * The component can render a local `dataSource` directly or resolve one through `faasData`, and
779
+ * it applies the same item type normalization helpers used by the form and table components.
780
+ *
781
+ * @template T - Data record shape rendered by the component.
782
+ * @param {DescriptionProps<T>} props - Description props including items, data source, and optional Faas data config.
783
+ * @throws {Error} When an entry in `extendTypes` omits both `children` and `render`.
529
784
  *
530
785
  * @example
531
786
  * ```tsx
532
787
  * import { Description } from '@faasjs/ant-design'
533
788
  *
534
- * <Description
535
- * title="Title"
536
- * items={[
537
- * {
538
- * id: 'id',
539
- * title: 'Title',
540
- * type: 'string',
541
- * },
542
- * ]}
543
- * dataSource={{ id: 'value' }}
544
- * />
789
+ * export function Detail() {
790
+ * return (
791
+ * <Description
792
+ * title="Title"
793
+ * items={[
794
+ * {
795
+ * id: 'id',
796
+ * title: 'Title',
797
+ * type: 'string',
798
+ * },
799
+ * ]}
800
+ * dataSource={{ id: 'value' }}
801
+ * />
802
+ * )
803
+ * }
545
804
  * ```
546
805
  */
547
- function Description({ faasData, dataSource, renderTitle, extendTypes, ...props }) {
806
+ function Description(props) {
807
+ const { faasData, dataSource, renderTitle, extendTypes, ...descriptionProps } = props;
548
808
  if (faasData && !dataSource) return /* @__PURE__ */ jsx(FaasDataWrapper, {
549
809
  render: ({ data }) => /* @__PURE__ */ jsx(Description, {
550
- ...props,
810
+ ...descriptionProps,
551
811
  dataSource: data,
552
812
  ...renderTitle ? { renderTitle } : {},
553
813
  ...extendTypes ? { extendTypes } : {}
@@ -555,9 +815,9 @@ function Description({ faasData, dataSource, renderTitle, extendTypes, ...props
555
815
  ...faasData
556
816
  });
557
817
  return /* @__PURE__ */ jsx(Descriptions, {
558
- ...props,
559
- title: typeof renderTitle === "function" ? renderTitle(dataSource) : props.title,
560
- 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) => ({
818
+ ...descriptionProps,
819
+ title: typeof renderTitle === "function" ? renderTitle(dataSource) : descriptionProps.title,
820
+ 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) => ({
561
821
  ...item,
562
822
  key: item.id,
563
823
  label: item.title ?? idToTitle(item.id),
@@ -609,20 +869,30 @@ function processProps(propsCopy, config) {
609
869
  return propsCopy;
610
870
  }
611
871
  /**
612
- * FormItem
872
+ * Render a FaasJS-aware Ant Design form field or nested field group.
873
+ *
874
+ * The component derives default labels from `id`, applies required validation messages from the
875
+ * active theme, supports surface-specific union renderers, and can render nested `object` or
876
+ * `object[]` field structures.
613
877
  *
614
- * - Based on [Ant Design Form.Item](https://ant.design/components/form#formitem).
615
- * - Can be used without [Form](https://faasjs.com/doc/ant-design/#form).
878
+ * @template T - Value type rendered or edited by the form item.
879
+ * @param {FormItemProps<T>} props - Form item props including field metadata, rules, and custom renderers.
616
880
  *
617
881
  * @example
618
882
  * ```tsx
619
- * // use inline type
620
- * <FormItem type='string' id='name' />
883
+ * import { FormItem } from '@faasjs/ant-design'
884
+ * import { Input } from 'antd'
621
885
  *
622
- * // use custom type
623
- * <FormItem id='password'>
624
- * <Input.Password />
625
- * </>
886
+ * export function AccountFields() {
887
+ * return (
888
+ * <>
889
+ * <FormItem id="name" type="string" />
890
+ * <FormItem id="password">
891
+ * <Input.Password />
892
+ * </FormItem>
893
+ * </>
894
+ * )
895
+ * }
626
896
  * ```
627
897
  */
628
898
  function FormItem(props) {
@@ -871,108 +1141,189 @@ function isFormItemProps(item) {
871
1141
  return item.id !== void 0;
872
1142
  }
873
1143
  /**
874
- * Form component with Ant Design & FaasJS
1144
+ * Render a data-aware Ant Design form with optional FaasJS submission helpers.
1145
+ *
1146
+ * The component normalizes `initialValues` with {@link transferValue}, renders item definitions
1147
+ * through {@link FormItem}, and can either delegate submission to a custom `onFinish` handler or
1148
+ * the built-in FaasJS request flow configured by `faas`.
1149
+ *
1150
+ * @template Values - Form values shape.
1151
+ * @param {FormProps<Values>} props - Form props including items, submit behavior, and FaasJS integration.
1152
+ *
1153
+ * @example
1154
+ * ```tsx
1155
+ * import { Form } from '@faasjs/ant-design'
1156
+ *
1157
+ * export function ProfileForm() {
1158
+ * return (
1159
+ * <Form
1160
+ * items={[
1161
+ * { id: 'name', required: true },
1162
+ * { id: 'email', required: true },
1163
+ * ]}
1164
+ * onFinish={async (values) => {
1165
+ * console.log(values)
1166
+ * }}
1167
+ * />
1168
+ * )
1169
+ * }
1170
+ * ```
1171
+ *
1172
+ * @example
1173
+ * ```tsx
1174
+ * import { Form } from '@faasjs/ant-design'
875
1175
  *
876
- * - Based on [Ant Design Form](https://ant.design/components/form/).
1176
+ * export function CreateUserForm() {
1177
+ * return (
1178
+ * <Form
1179
+ * initialValues={{ role: 'user' }}
1180
+ * items={[
1181
+ * { id: 'name', required: true },
1182
+ * { id: 'role', options: ['user', 'admin'] },
1183
+ * ]}
1184
+ * faas={{
1185
+ * action: 'user/create',
1186
+ * params: (values) => ({
1187
+ * role: values.role || 'user',
1188
+ * }),
1189
+ * }}
1190
+ * />
1191
+ * )
1192
+ * }
1193
+ * ```
877
1194
  */
878
1195
  function Form(props) {
879
1196
  const [loading, setLoading] = useState(false);
880
- const [computedProps, setComputedProps] = useState();
881
- const [submit, setSubmit] = useState();
1197
+ const [antdProps, setAntdProps] = useState();
1198
+ const [submit, setSubmit] = useState(props.submit === false ? false : {});
1199
+ const [items, setItems] = useState([]);
882
1200
  const config = useConfigContext();
883
1201
  const [extendTypes, setExtendTypes] = useState();
884
1202
  const [form] = Form$1.useForm(props.form);
885
1203
  const [initialValues, setInitialValues] = useState(props.initialValues || Object.create(null));
1204
+ const [onFinish, setOnFinish] = useState();
886
1205
  useEqualEffect(() => {
887
- const { submit, ...propsCopy } = {
888
- ...props,
889
- form
890
- };
891
- let nextInitialValues = propsCopy.initialValues;
892
- if (typeof submit !== "undefined") setSubmit(submit);
893
- if (propsCopy.initialValues && propsCopy.items?.length) {
894
- for (const key in propsCopy.initialValues) propsCopy.initialValues[key] = transferValue(propsCopy.items.find((item) => isFormItemProps(item) && item.id === key)?.type, propsCopy.initialValues[key]);
895
- nextInitialValues = propsCopy.initialValues;
896
- setInitialValues(propsCopy.initialValues);
897
- delete propsCopy.initialValues;
898
- }
899
- if (propsCopy.items?.length) {
900
- for (const item of propsCopy.items) if (isFormItemProps(item) && item.if) item.hidden = !item.if(nextInitialValues || Object.create(null));
1206
+ if (props.onFinish) {
1207
+ setOnFinish(() => async (values) => {
1208
+ if (!props.onFinish) return;
1209
+ setLoading(true);
1210
+ try {
1211
+ return await props.onFinish(values);
1212
+ } finally {
1213
+ setLoading(false);
1214
+ }
1215
+ });
1216
+ return;
901
1217
  }
902
- const submitTo = typeof submit === "object" ? submit.to : void 0;
903
- if (propsCopy.onFinish) {
904
- const originOnFinish = propsCopy.onFinish;
905
- propsCopy.onFinish = async (values) => {
1218
+ if (props.faas?.action) {
1219
+ setOnFinish(() => async (values) => {
1220
+ if (!props.faas?.action) return;
906
1221
  setLoading(true);
1222
+ let submitValues = values;
907
1223
  try {
908
- if (submitTo?.action) await originOnFinish(values, async (nextValues) => faas(submitTo.action, submitTo.params ? {
909
- ...nextValues,
910
- ...submitTo.params
911
- } : nextValues));
912
- else await originOnFinish(values);
1224
+ if (props.faas?.transformValues) submitValues = await props.faas.transformValues(values);
1225
+ const extraParams = typeof props.faas?.params === "function" ? props.faas.params(submitValues) : props.faas.params;
1226
+ if (extraParams) submitValues = {
1227
+ ...submitValues,
1228
+ ...extraParams
1229
+ };
1230
+ const result = await faas(props.faas.action, submitValues);
1231
+ props.faas.onSuccess?.(result, submitValues);
1232
+ return result;
913
1233
  } catch (error) {
914
- console.error(error);
1234
+ props.faas.onError?.(error, submitValues);
1235
+ throw error;
1236
+ } finally {
1237
+ props.faas.onFinally?.();
1238
+ setLoading(false);
915
1239
  }
916
- setLoading(false);
917
- };
918
- } else if (submitTo?.action) propsCopy.onFinish = async (values) => {
919
- setLoading(true);
920
- return faas(submitTo.action, submitTo.params ? {
921
- ...values,
922
- ...submitTo.params
923
- } : values).then((result) => {
924
- submitTo.then?.(result);
925
- return result;
926
- }).catch((error) => {
927
- submitTo.catch?.(error);
928
- return Promise.reject(error);
929
- }).finally(() => {
930
- submitTo.finally?.();
931
- setLoading(false);
932
1240
  });
933
- };
934
- if (propsCopy.extendTypes) {
935
- setExtendTypes(propsCopy.extendTypes);
936
- delete propsCopy.extendTypes;
1241
+ return;
937
1242
  }
938
- setComputedProps(propsCopy);
939
- }, [form, props]);
1243
+ setOnFinish(void 0);
1244
+ }, [props.onFinish, props.faas]);
1245
+ useEqualEffect(() => {
1246
+ setExtendTypes(props.extendTypes);
1247
+ }, [props.extendTypes]);
1248
+ useEqualEffect(() => {
1249
+ setSubmit(props.submit === false ? false : props.submit || {});
1250
+ }, [props.submit]);
1251
+ useEqualEffect(() => {
1252
+ const nextInitialValues = props.initialValues ? JSON.parse(JSON.stringify(props.initialValues)) : Object.create(null);
1253
+ for (const key in nextInitialValues) nextInitialValues[key] = transferValue(props.items?.find((item) => isFormItemProps(item) && item.id === key)?.type, nextInitialValues[key]);
1254
+ if (props.items?.length) setItems(props.items.map((item) => {
1255
+ if (!isFormItemProps(item) || !item.if) return item;
1256
+ return {
1257
+ ...item,
1258
+ hidden: !item.if(nextInitialValues)
1259
+ };
1260
+ }));
1261
+ else setItems([]);
1262
+ if (props.initialValues) {
1263
+ setInitialValues(nextInitialValues);
1264
+ return;
1265
+ }
1266
+ setInitialValues(null);
1267
+ }, [props.initialValues, props.items]);
1268
+ useEqualEffect(() => {
1269
+ const propsCopy = { ...props };
1270
+ delete propsCopy.onFinish;
1271
+ delete propsCopy.faas;
1272
+ delete propsCopy.extendTypes;
1273
+ delete propsCopy.submit;
1274
+ delete propsCopy.items;
1275
+ delete propsCopy.initialValues;
1276
+ setAntdProps(propsCopy);
1277
+ }, [props]);
940
1278
  const onValuesChange = useEqualCallback((changedValues, allValues) => {
941
1279
  console.debug("Form:onValuesChange", changedValues, allValues);
942
1280
  if (props.onValuesChange) props.onValuesChange(changedValues, allValues);
943
- if (!props.items) return;
1281
+ if (!items.length) return;
944
1282
  for (const key in changedValues) {
945
- const item = computedProps?.items?.find((i) => isFormItemProps(i) && i.id === key);
1283
+ const item = items.find((i) => isFormItemProps(i) && i.id === key);
946
1284
  if (item?.onValueChange) item.onValueChange(changedValues[key], allValues, form);
947
1285
  }
948
- }, [computedProps]);
1286
+ }, [
1287
+ items,
1288
+ props.onValuesChange,
1289
+ form
1290
+ ]);
949
1291
  useEqualEffect(() => {
950
1292
  if (!initialValues) return;
951
1293
  console.debug("Form:initialValues", initialValues);
952
1294
  form.setFieldsValue(initialValues);
953
1295
  setInitialValues(null);
954
- }, [form, initialValues]);
955
- if (!computedProps) return null;
1296
+ }, [
1297
+ form,
1298
+ initialValues,
1299
+ items
1300
+ ]);
1301
+ if (!antdProps) return null;
1302
+ const submitButtonProps = typeof submit === "object" ? submit.buttonProps : void 0;
1303
+ const submitButtonLoading = loading ? true : submitButtonProps?.loading ?? false;
956
1304
  return /* @__PURE__ */ jsxs(Form$1, {
957
- ...computedProps,
1305
+ ...antdProps,
1306
+ form,
1307
+ onFinish,
958
1308
  onValuesChange,
959
1309
  children: [
960
- computedProps.beforeItems,
961
- computedProps.items?.map((item) => {
1310
+ props.beforeItems,
1311
+ items.map((item) => {
962
1312
  if (isFormItemProps(item)) return /* @__PURE__ */ jsx(FormItem, {
963
1313
  ...item,
964
1314
  ...extendTypes ? { extendTypes } : {}
965
1315
  }, item.id);
966
1316
  return item;
967
1317
  }),
968
- computedProps.children,
1318
+ props.children,
969
1319
  typeof submit !== "boolean" && /* @__PURE__ */ jsx(Button, {
1320
+ ...submitButtonProps,
970
1321
  htmlType: "submit",
971
- type: "primary",
972
- loading,
1322
+ type: submitButtonProps?.type || "primary",
1323
+ loading: submitButtonLoading,
973
1324
  children: submit?.text || config.theme.Form.submit.text
974
1325
  }),
975
- computedProps.footer
1326
+ props.footer
976
1327
  ]
977
1328
  });
978
1329
  }
@@ -986,15 +1337,27 @@ Form.Provider = Form$1.Provider;
986
1337
  //#endregion
987
1338
  //#region src/Link.tsx
988
1339
  /**
989
- * Link component with button
1340
+ * Render a navigation-aware link or button.
1341
+ *
1342
+ * Internal links are pushed through React Router, while links with `_blank` targets are opened
1343
+ * with `window.open`.
1344
+ *
1345
+ * @param {LinkProps} props - Link props controlling navigation target, rendering mode, and button behavior.
990
1346
  *
991
1347
  * @example
992
1348
  * ```tsx
993
- * // pure link
994
- * <Link href="/">Home</Link>
1349
+ * import { Link } from '@faasjs/ant-design'
995
1350
  *
996
- * // link with button
997
- * <Link href="/" button={{ type:'primary' }}>Home</Link>
1351
+ * export function Navigation() {
1352
+ * return (
1353
+ * <>
1354
+ * <Link href="/">Home</Link>
1355
+ * <Link href="/users/new" button={{ type: 'primary' }}>
1356
+ * Create User
1357
+ * </Link>
1358
+ * </>
1359
+ * )
1360
+ * }
998
1361
  * ```
999
1362
  */
1000
1363
  function Link(props) {
@@ -1051,6 +1414,23 @@ function Link(props) {
1051
1414
  }
1052
1415
  //#endregion
1053
1416
  //#region src/Routers.tsx
1417
+ /**
1418
+ * Default 404 route element that uses the configured localized title.
1419
+ *
1420
+ * @example
1421
+ * ```tsx
1422
+ * import { PageNotFound, Routes } from '@faasjs/ant-design'
1423
+ *
1424
+ * export function AppRoutes() {
1425
+ * return (
1426
+ * <Routes
1427
+ * routes={[{ path: '/', element: <div>Home</div> }]}
1428
+ * notFound={<PageNotFound />}
1429
+ * />
1430
+ * )
1431
+ * }
1432
+ * ```
1433
+ */
1054
1434
  function PageNotFound() {
1055
1435
  const { theme } = useConfigContext();
1056
1436
  return /* @__PURE__ */ jsx(Result, {
@@ -1059,22 +1439,31 @@ function PageNotFound() {
1059
1439
  });
1060
1440
  }
1061
1441
  /**
1062
- * Routes with lazy loading and 404 page.
1442
+ * Render React Router routes with lazy-page support and a default 404 route.
1443
+ *
1444
+ * The wrapper adds a catch-all route automatically and uses an Ant Design `Skeleton` fallback when
1445
+ * `fallback` is not provided.
1446
+ *
1447
+ * @param {RoutesProps} props - Route definitions and optional fallback or 404 elements.
1063
1448
  *
1064
1449
  * @example
1065
1450
  * ```tsx
1066
1451
  * import { Routes, lazy } from '@faasjs/ant-design'
1067
1452
  * import { BrowserRouter } from 'react-router-dom'
1068
1453
  *
1069
- * export function App () {
1070
- * return <BrowserRouter>
1071
- * <Routes routes={[
1072
- * {
1073
- * path: '/',
1074
- * page: lazy(() => import('./pages/home'))
1075
- * }
1076
- * ]} />
1077
- * </BrowserRouter>
1454
+ * export function App() {
1455
+ * return (
1456
+ * <BrowserRouter>
1457
+ * <Routes
1458
+ * routes={[
1459
+ * {
1460
+ * path: '/',
1461
+ * page: lazy(() => import('./pages/home')),
1462
+ * },
1463
+ * ]}
1464
+ * />
1465
+ * </BrowserRouter>
1466
+ * )
1078
1467
  * }
1079
1468
  * ```
1080
1469
  */
@@ -1115,12 +1504,39 @@ function processValue(item, value) {
1115
1504
  return transferred;
1116
1505
  }
1117
1506
  /**
1118
- * Table component with Ant Design & FaasJS
1507
+ * Render an Ant Design table from FaasJS item metadata.
1508
+ *
1509
+ * The component can render local `dataSource` rows or resolve remote rows through `faasData`. It
1510
+ * also generates default filters and sorters for built-in item types unless you disable them with
1511
+ * the corresponding Ant Design column props.
1119
1512
  *
1120
- * - Based on [Ant Design Table](https://ant.design/components/table/).
1121
- * - Support FaasJS injection.
1122
- * - Auto generate filter dropdown (disable with `filterDropdown: false`).
1123
- * - Auto generate sorter (disable with `sorter: false`).
1513
+ * @template T - Row record type rendered by the table.
1514
+ * @template ExtendTypes - Additional item prop shape accepted by `items`.
1515
+ * @param {TableProps<T, ExtendTypes>} props - Table props including columns, data source, and optional Faas data config.
1516
+ * @throws {Error} When an entry in `extendTypes` omits both `children` and `render`.
1517
+ *
1518
+ * @example
1519
+ * ```tsx
1520
+ * import { Table } from '@faasjs/ant-design'
1521
+ *
1522
+ * const rows = [
1523
+ * { id: 1, name: 'Alice', active: true },
1524
+ * { id: 2, name: 'Bob', active: false },
1525
+ * ]
1526
+ *
1527
+ * export function UserTable() {
1528
+ * return (
1529
+ * <Table
1530
+ * rowKey="id"
1531
+ * dataSource={rows}
1532
+ * items={[
1533
+ * { id: 'name', title: 'Name' },
1534
+ * { id: 'active', type: 'boolean', title: 'Active' },
1535
+ * ]}
1536
+ * />
1537
+ * )
1538
+ * }
1539
+ * ```
1124
1540
  */
1125
1541
  function Table(props) {
1126
1542
  const [columns, setColumns] = useState();
@@ -1506,28 +1922,32 @@ function FaasDataTable({ props, columns, data, params, reload, loading }) {
1506
1922
  //#endregion
1507
1923
  //#region src/Tabs.tsx
1508
1924
  /**
1509
- * Tabs component with Ant Design & FaasJS
1925
+ * Render an Ant Design tabs wrapper that accepts FaasJS-style tab definitions.
1926
+ *
1927
+ * Missing `key` and `label` values are derived from each tab's `id` and `title`.
1510
1928
  *
1511
- * - Based on [Ant Design Tabs](https://ant.design/components/tabs/).
1512
- * - Support auto skip null/false tab item.
1513
- * - Support `id` as key and label.
1929
+ * @param {TabsProps} props - Tabs props including tab items and Ant Design tab options.
1514
1930
  *
1515
1931
  * @example
1516
1932
  * ```tsx
1517
1933
  * import { Tabs } from '@faasjs/ant-design'
1518
1934
  *
1519
- * <Tabs
1520
- * items={[
1521
- * {
1522
- * id: 'id',
1523
- * children: 'content',
1524
- * },
1525
- * 1 === 0 && {
1526
- * id: 'hidden',
1527
- * children: 'content',
1528
- * },
1529
- * ]}
1530
- * />
1935
+ * export function Page() {
1936
+ * return (
1937
+ * <Tabs
1938
+ * items={[
1939
+ * {
1940
+ * id: 'id',
1941
+ * children: 'content',
1942
+ * },
1943
+ * 1 === 0 && {
1944
+ * id: 'hidden',
1945
+ * children: 'content',
1946
+ * },
1947
+ * ]}
1948
+ * />
1949
+ * )
1950
+ * }
1531
1951
  * ```
1532
1952
  */
1533
1953
  function Tabs(props) {
@@ -1543,21 +1963,24 @@ function Tabs(props) {
1543
1963
  //#endregion
1544
1964
  //#region src/Title.tsx
1545
1965
  /**
1546
- * Title is used to change the title of the page
1966
+ * Update `document.title` and optionally render the title inline.
1547
1967
  *
1548
- * Return null by default.
1968
+ * The component returns `null` by default and is often used only for its side effect.
1549
1969
  *
1550
- * ```tsx
1551
- * // return null
1552
- * <Title title='hi' /> // => change the document.title to 'hi'
1553
- * <Title title={['a', 'b']} /> // => change the document.title to 'a - b'
1970
+ * @param {TitleProps} props - Title props controlling document title updates and optional inline rendering.
1554
1971
  *
1555
- * // return h1
1556
- * <Title title='hi' h1 /> // => <h1>hi</h1>
1557
- * <Title title={['a', 'b']} h1 /> // => <h1>a</h1>
1972
+ * @example
1973
+ * ```tsx
1974
+ * import { Title } from '@faasjs/ant-design'
1558
1975
  *
1559
- * // return children
1560
- * <Title title='hi'><CustomTitle /></Title> // => <CustomTitle />
1976
+ * export function DetailPage() {
1977
+ * return (
1978
+ * <>
1979
+ * <Title title={['Orders', 'Detail']} h1 />
1980
+ * <div>...</div>
1981
+ * </>
1982
+ * )
1983
+ * }
1561
1984
  * ```
1562
1985
  */
1563
1986
  function Title(props) {
@@ -1581,12 +2004,20 @@ function Title(props) {
1581
2004
  //#endregion
1582
2005
  //#region src/useThemeToken.ts
1583
2006
  /**
1584
- * Hook to retrieve the theme token from the Ant Design theme configuration.
2007
+ * Read the current Ant Design theme token.
1585
2008
  *
1586
- * This function uses the `theme.useToken` method to get the current theme configuration
1587
- * and returns the `token` property from the configuration.
2009
+ * @returns Ant Design global token object for the active theme.
1588
2010
  *
1589
- * @returns {GlobalToken} The theme token from the Ant Design theme configuration.
2011
+ * @example
2012
+ * ```tsx
2013
+ * import { useThemeToken } from '@faasjs/ant-design'
2014
+ *
2015
+ * function PrimarySwatch() {
2016
+ * const { colorPrimary } = useThemeToken()
2017
+ *
2018
+ * return <div style={{ width: 24, height: 24, background: colorPrimary }} />
2019
+ * }
2020
+ * ```
1590
2021
  */
1591
2022
  function useThemeToken() {
1592
2023
  return theme.useToken().token;