@zeyos/zx 4.4.0 → 5.2.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 (252) hide show
  1. package/CHANGELOG.md +447 -0
  2. package/README.md +44 -11
  3. package/dist/types/components/account-menu/account-menu.d.ts +8 -0
  4. package/dist/types/components/activity-list/activity-list.d.ts +31 -1
  5. package/dist/types/components/app-icon/app-icon.d.ts +14 -2
  6. package/dist/types/components/app-sidebar/app-sidebar.d.ts +293 -17
  7. package/dist/types/components/avatar/avatar.d.ts +46 -7
  8. package/dist/types/components/badge/badge.d.ts +21 -1
  9. package/dist/types/components/button/button.d.ts +15 -0
  10. package/dist/types/components/calendar/calendar.d.ts +67 -5
  11. package/dist/types/components/card-view/card-view.d.ts +7 -4
  12. package/dist/types/components/card-view/record-card.d.ts +11 -0
  13. package/dist/types/components/chart/chart.d.ts +10 -0
  14. package/dist/types/components/checklist/checklist.d.ts +9 -0
  15. package/dist/types/components/code-editor/code-editor.d.ts +6 -17
  16. package/dist/types/components/context-menu/context-menu.d.ts +6 -2
  17. package/dist/types/components/data-filter/data-filter.d.ts +4 -4
  18. package/dist/types/components/datebox/date-range-box.d.ts +5 -1
  19. package/dist/types/components/datebox/datebox.d.ts +7 -2
  20. package/dist/types/components/dialog/dialog.d.ts +46 -6
  21. package/dist/types/components/dropdown/dropdown.d.ts +24 -9
  22. package/dist/types/components/entity-ref/entity-ref.d.ts +2 -2
  23. package/dist/types/components/field/field.d.ts +54 -2
  24. package/dist/types/components/fieldset/external-adapter.d.ts +83 -0
  25. package/dist/types/components/fieldset/fieldset.d.ts +26 -1
  26. package/dist/types/components/file-item/file-item.d.ts +34 -6
  27. package/dist/types/components/file-list/file-list.d.ts +35 -3
  28. package/dist/types/components/filter/filter.d.ts +127 -3
  29. package/dist/types/components/filter-panel/filter-panel.d.ts +36 -7
  30. package/dist/types/components/form/form.d.ts +12 -1
  31. package/dist/types/components/kanban-view/kanban-card.d.ts +8 -0
  32. package/dist/types/components/launcher/launcher.d.ts +268 -15
  33. package/dist/types/components/list-toolbar/list-toolbar.d.ts +16 -7
  34. package/dist/types/components/menu-button/menu-button.d.ts +39 -5
  35. package/dist/types/components/message/message.d.ts +132 -17
  36. package/dist/types/components/modal/modal.d.ts +43 -2
  37. package/dist/types/components/module-bar/module-bar.d.ts +395 -0
  38. package/dist/types/components/pagination/pagination.d.ts +17 -3
  39. package/dist/types/components/select/color.d.ts +12 -0
  40. package/dist/types/components/select/glyphs.d.ts +124 -0
  41. package/dist/types/components/select/select.d.ts +17 -3
  42. package/dist/types/components/sheet/sheet.d.ts +15 -0
  43. package/dist/types/components/stat-tile/stat-tile.d.ts +16 -5
  44. package/dist/types/components/tabbox/tabbox.d.ts +9 -0
  45. package/dist/types/components/table/table.d.ts +311 -18
  46. package/dist/types/components/tag-picker/tag-picker.d.ts +21 -4
  47. package/dist/types/components/tree/tree.d.ts +13 -0
  48. package/dist/types/components/view/record-view.d.ts +59 -2
  49. package/dist/types/components/view-settings/view-settings.d.ts +879 -0
  50. package/dist/types/components/workspace-switcher/workspace-switcher.d.ts +327 -0
  51. package/dist/types/core/breakpoint.d.ts +1 -1
  52. package/dist/types/core/component.d.ts +12 -2
  53. package/dist/types/core/i18n.d.ts +27 -3
  54. package/dist/types/core/icons.d.ts +23 -25
  55. package/dist/types/core/keyboard.d.ts +98 -1
  56. package/dist/types/core/messages.d.ts +382 -0
  57. package/dist/types/core/motion.d.ts +59 -0
  58. package/dist/types/core/overlay-host.d.ts +42 -1
  59. package/dist/types/core/overlay-stack.d.ts +260 -0
  60. package/dist/types/index.d.ts +10 -5
  61. package/dist/types/internal/app-rail.d.ts +38 -0
  62. package/dist/types/internal/menu-items.d.ts +71 -3
  63. package/dist/types/zeyos/connect.d.ts +6 -0
  64. package/dist/types/zeyos/form.d.ts +50 -9
  65. package/dist/types/zeyos/index.d.ts +2 -1
  66. package/dist/types/zeyos/launcher.d.ts +9 -0
  67. package/dist/types/zeyos/module-bar.d.ts +176 -0
  68. package/dist/types/zeyos/query.d.ts +110 -5
  69. package/dist/types/zeyos/schema.d.ts +24 -1
  70. package/dist/types/zeyos/table.d.ts +345 -22
  71. package/dist/types/zeyos/view.d.ts +26 -2
  72. package/dist/zx-zeyos.esm.js +6178 -1008
  73. package/dist/zx-zeyos.esm.min.js +53 -5
  74. package/dist/zx.css +2861 -827
  75. package/dist/zx.esm.js +8909 -1480
  76. package/dist/zx.esm.min.js +55 -7
  77. package/dist/zx.global.js +8911 -1482
  78. package/dist/zx.global.min.js +55 -7
  79. package/dist/zx.min.css +1 -1
  80. package/docs/api.json +1079 -117
  81. package/docs/llms.md +956 -154
  82. package/docs/llms.txt +9 -5
  83. package/package.json +1 -1
  84. package/src/components/account-menu/account-menu.css +35 -10
  85. package/src/components/account-menu/account-menu.js +12 -0
  86. package/src/components/activity-item/activity-item.css +1 -1
  87. package/src/components/activity-item/activity-item.js +3 -0
  88. package/src/components/activity-list/activity-list.css +38 -3
  89. package/src/components/activity-list/activity-list.js +61 -1
  90. package/src/components/app-icon/app-icon.css +14 -3
  91. package/src/components/app-icon/app-icon.js +11 -1
  92. package/src/components/app-rail/app-rail.css +59 -79
  93. package/src/components/app-sidebar/app-sidebar.css +304 -168
  94. package/src/components/app-sidebar/app-sidebar.js +667 -46
  95. package/src/components/aurora/aurora.css +5 -3
  96. package/src/components/avatar/avatar.css +32 -7
  97. package/src/components/avatar/avatar.js +72 -8
  98. package/src/components/badge/badge.css +54 -2
  99. package/src/components/badge/badge.js +29 -2
  100. package/src/components/breadcrumb/breadcrumb.css +1 -1
  101. package/src/components/button/button.css +67 -9
  102. package/src/components/button/button.js +38 -0
  103. package/src/components/calendar/calendar.css +47 -47
  104. package/src/components/calendar/calendar.js +26 -52
  105. package/src/components/card/card.css +4 -4
  106. package/src/components/card-view/card-view.css +14 -12
  107. package/src/components/card-view/card-view.js +13 -9
  108. package/src/components/card-view/record-card.js +14 -3
  109. package/src/components/chart/chart.css +4 -1
  110. package/src/components/chart/chart.js +12 -0
  111. package/src/components/check-button/check-button.css +10 -2
  112. package/src/components/checklist/checklist.css +9 -2
  113. package/src/components/checklist/checklist.js +11 -5
  114. package/src/components/checklist/field-adapter.js +3 -1
  115. package/src/components/code-editor/code-editor.css +7 -6
  116. package/src/components/code-editor/code-editor.js +19 -1
  117. package/src/components/code-editor/field-adapter.js +9 -2
  118. package/src/components/context-menu/context-menu.css +9 -4
  119. package/src/components/context-menu/context-menu.js +8 -2
  120. package/src/components/copy/copy-button.css +6 -0
  121. package/src/components/copy/copy-input.css +9 -4
  122. package/src/components/data-filter/data-filter.css +5 -4
  123. package/src/components/data-filter/data-filter.js +2 -2
  124. package/src/components/date-picker/date-picker.css +6 -6
  125. package/src/components/date-picker/date-range-picker.css +3 -3
  126. package/src/components/date-picker/time-picker.css +3 -3
  127. package/src/components/datebox/date-range-box.css +79 -2
  128. package/src/components/datebox/date-range-box.js +13 -2
  129. package/src/components/datebox/datebox.css +79 -2
  130. package/src/components/datebox/datebox.js +13 -2
  131. package/src/components/dialog/dialog.css +26 -0
  132. package/src/components/dialog/dialog.js +99 -17
  133. package/src/components/dock/dock.css +5 -4
  134. package/src/components/dropdown/dropdown.css +1 -1
  135. package/src/components/dropdown/dropdown.js +159 -72
  136. package/src/components/empty-state/empty-state.css +2 -2
  137. package/src/components/entity-ref/entity-ref.css +1 -1
  138. package/src/components/entity-ref/entity-ref.js +1 -1
  139. package/src/components/field/field.css +28 -9
  140. package/src/components/field/field.js +101 -29
  141. package/src/components/field-upload/field-upload.css +1 -1
  142. package/src/components/fieldset/external-adapter.js +363 -0
  143. package/src/components/fieldset/fieldset.css +5 -3
  144. package/src/components/fieldset/fieldset.js +38 -64
  145. package/src/components/file-item/file-item.js +144 -10
  146. package/src/components/file-list/file-list.js +144 -21
  147. package/src/components/filter/filter.css +86 -2
  148. package/src/components/filter/filter.js +307 -11
  149. package/src/components/filter-panel/filter-panel.css +1 -1
  150. package/src/components/filter-panel/filter-panel.js +149 -32
  151. package/src/components/finder/finder.css +16 -14
  152. package/src/components/form/form.css +5 -4
  153. package/src/components/form/form.js +11 -2
  154. package/src/components/grid/grid.css +1 -1
  155. package/src/components/groupbox/groupbox.css +1 -1
  156. package/src/components/kanban-view/kanban-card.js +11 -3
  157. package/src/components/kanban-view/kanban-view.css +42 -18
  158. package/src/components/kanban-view/kanban-view.js +9 -6
  159. package/src/components/launcher/launcher.css +108 -26
  160. package/src/components/launcher/launcher.js +530 -42
  161. package/src/components/list-toolbar/list-toolbar.css +13 -8
  162. package/src/components/list-toolbar/list-toolbar.js +40 -14
  163. package/src/components/master-panel/master-panel.css +2 -0
  164. package/src/components/master-panel/master-panel.js +0 -1
  165. package/src/components/menu-button/menu-button.css +72 -4
  166. package/src/components/menu-button/menu-button.js +160 -18
  167. package/src/components/message/message.css +33 -3
  168. package/src/components/message/message.js +264 -22
  169. package/src/components/modal/modal.css +1 -1
  170. package/src/components/modal/modal.js +83 -15
  171. package/src/components/module-bar/module-bar.css +486 -0
  172. package/src/components/module-bar/module-bar.js +1074 -0
  173. package/src/components/multi-value-editor/multi-value-editor.css +1 -1
  174. package/src/components/navigation-bar/navigation-bar.css +18 -9
  175. package/src/components/number-field/field-adapter.js +3 -1
  176. package/src/components/number-field/number-field.css +70 -0
  177. package/src/components/number-field/number-field.js +2 -0
  178. package/src/components/pagination/pagination.css +13 -11
  179. package/src/components/pagination/pagination.js +20 -4
  180. package/src/components/panel/panel.css +4 -3
  181. package/src/components/questionnaire/questionnaire.css +4 -4
  182. package/src/components/rating/field-adapter.js +2 -1
  183. package/src/components/rating/rating.css +8 -1
  184. package/src/components/search/search.css +4 -1
  185. package/src/components/select/color.js +22 -0
  186. package/src/components/select/field-adapter.js +2 -0
  187. package/src/components/select/glyphs.js +165 -0
  188. package/src/components/select/select.css +138 -45
  189. package/src/components/select/select.js +92 -47
  190. package/src/components/sheet/sheet.css +12 -9
  191. package/src/components/sheet/sheet.js +74 -32
  192. package/src/components/slider/field-adapter.js +6 -1
  193. package/src/components/slider/slider.css +18 -4
  194. package/src/components/slider/slider.js +1 -1
  195. package/src/components/sort-control/sort-control.css +4 -1
  196. package/src/components/split-view/split-view.css +1 -1
  197. package/src/components/stat-tile/stat-tile.css +42 -47
  198. package/src/components/stat-tile/stat-tile.js +17 -6
  199. package/src/components/stepper/stepper.css +1 -1
  200. package/src/components/tabbox/tabbox.css +41 -32
  201. package/src/components/tabbox/tabbox.js +5 -1
  202. package/src/components/table/table.css +187 -37
  203. package/src/components/table/table.js +718 -73
  204. package/src/components/tag-picker/field-adapter.js +2 -1
  205. package/src/components/tag-picker/tag-picker.css +83 -5
  206. package/src/components/tag-picker/tag-picker.js +25 -8
  207. package/src/components/timebox/timebox.css +22 -2
  208. package/src/components/toggle/toggle.css +12 -1
  209. package/src/components/toolbar/toolbar.js +2 -2
  210. package/src/components/tooltip/tooltip.css +4 -0
  211. package/src/components/tooltip/tooltip.js +44 -19
  212. package/src/components/tree/tree.css +26 -17
  213. package/src/components/tree/tree.js +31 -0
  214. package/src/components/value-list/value-list.css +3 -3
  215. package/src/components/view/record-view.css +1 -1
  216. package/src/components/view/record-view.js +178 -18
  217. package/src/components/view-settings/view-settings.css +285 -0
  218. package/src/components/view-settings/view-settings.js +1830 -0
  219. package/src/components/workspace-switcher/workspace-switcher.css +188 -0
  220. package/src/components/workspace-switcher/workspace-switcher.js +463 -0
  221. package/src/core/breakpoint.js +1 -1
  222. package/src/core/component.js +88 -17
  223. package/src/core/fontawesome.js +5 -1
  224. package/src/core/i18n.js +47 -4
  225. package/src/core/icons.js +204 -61
  226. package/src/core/keyboard.js +206 -11
  227. package/src/core/messages.js +427 -0
  228. package/src/core/motion.js +147 -0
  229. package/src/core/overlay-host.js +77 -3
  230. package/src/core/overlay-stack.js +487 -0
  231. package/src/core/position.js +22 -3
  232. package/src/index.js +10 -5
  233. package/src/internal/app-rail.js +135 -55
  234. package/src/internal/menu-items.js +61 -3
  235. package/src/zeyos/connect.js +6 -2
  236. package/src/zeyos/form.js +69 -27
  237. package/src/zeyos/index.js +4 -1
  238. package/src/zeyos/launcher.js +8 -3
  239. package/src/zeyos/module-bar.js +181 -0
  240. package/src/zeyos/query.js +197 -13
  241. package/src/zeyos/schema.js +144 -27
  242. package/src/zeyos/table.js +287 -84
  243. package/src/zeyos/view.js +49 -93
  244. package/styles/base.css +72 -1
  245. package/styles/icons.css +11 -1
  246. package/styles/print.css +1 -1
  247. package/styles/tokens/dark.css +117 -37
  248. package/styles/tokens/global.css +121 -27
  249. package/styles/tokens/semantic.css +140 -43
  250. package/styles/tokens/themes.css +7 -6
  251. package/styles/zx.css +3 -1
  252. package/styles/tokens/density.css +0 -9
@@ -4,19 +4,60 @@ import { buildListQuery } from './query.js';
4
4
  import { fieldToZxColumn, resolveFields } from './schema.js';
5
5
  import { normalizeZeyosListResult } from './select.js';
6
6
 
7
+ /** @typedef {import('../components/table/table.js').TableOptions} TableOptions */
8
+
7
9
  /**
8
10
  * @typedef {Object} ZeyosTableOptions
9
11
  * @property {string[]} [fields] Curated ordered field allow-list.
10
12
  * @property {Record<string, string>} [labels] Per-field label overrides.
13
+ * @property {Record<string, string>} [formats] Per-field semantic format overrides, such as
14
+ * `{ duedate: 'date' }` for a Unix-second field that holds a calendar day.
15
+ * @property {string} [timeZone] IANA time zone of the ZeyOS instance. Date-only columns show the
16
+ * calendar day their stored midnight names in this zone; defaults to the runtime's own zone.
11
17
  * @property {string|((row: Record<string, unknown>) => unknown)} [rowId='ID'] Row id accessor.
12
- * @property {number} [pageSize=50] Server page size.
18
+ * @property {number} [pageSize=50] Server page size, a positive integer. `load({pageSize})` changes
19
+ * it later — what a `Pagination` page-size select does.
13
20
  * @property {{id: string, dir: 'asc'|'desc'}|null} [sort=null] Initial server sort.
14
21
  * @property {Record<string, unknown>} [filters] Initial ZeyOS predicates.
15
22
  * @property {string} [search=''] Initial full-text query.
16
23
  * @property {false|'single'|'multi'} [selectable=false] Row selection mode.
17
24
  * @property {number|string|null} [height=null] Table scroll-region height.
18
25
  * @property {(row: Record<string, unknown>, event: CustomEvent<Record<string, unknown>>) => void} [onRowClick] Row activation callback.
26
+ * @property {TableOptions['variant']} [variant='default'] Table frame treatment; `'plain'` for a
27
+ * table inside a pane or card that already draws the box.
28
+ * @property {boolean} [zebra=true] Whether alternate rows use the zebra background.
29
+ * @property {boolean} [stickyHeader=true] Whether column headers stick while scrolling.
30
+ * @property {TableOptions['responsive']} [responsive=false] Width below which rows stack.
31
+ * @property {TableOptions['growing']} [growing=false] Rows rendered at first from a loaded page.
32
+ * @property {TableOptions['emptyText']} [emptyText=null] What the table shows with no rows.
33
+ * @property {TableOptions['rowClass']} [rowClass=null] Additional row class callback.
34
+ * @property {TableOptions['rowKind']} [rowKind=null] Row property or callback naming each row's kind.
35
+ * @property {TableOptions['rowKinds']} [rowKinds=null] Presentation per row kind.
36
+ * @property {boolean} [columnVisibility=false] Whether a column visibility chooser is rendered.
37
+ * @property {boolean} [columnReorder=false] Whether the column chooser exposes reorder controls.
38
+ * @property {string[]} [hiddenColumns=[]] Initially hidden column ids.
39
+ * @property {TableOptions['onrowclick']} [onrowclick] Table `rowclick` listener, beside `onRowClick`.
40
+ * @property {TableOptions['onrowdblclick']} [onrowdblclick] Table `rowdblclick` listener.
41
+ * @property {TableOptions['onsort']} [onsort] Table `sort` listener; the binding reloads either way.
42
+ * @property {TableOptions['onselectionchange']} [onselectionchange] Table `selectionchange` listener.
43
+ * @property {TableOptions['ondatachange']} [ondatachange] Table `datachange` listener.
44
+ * @property {TableOptions['onstackedchange']} [onstackedchange] Table `stackedchange` listener.
45
+ * @property {TableOptions['ongrow']} [ongrow] Table `grow` listener.
46
+ * @property {TableOptions['oncolumnvisibilitychange']} [oncolumnvisibilitychange] Table `columnvisibilitychange` listener.
47
+ * @property {TableOptions['oncolumnorderchange']} [oncolumnorderchange] Table `columnorderchange` listener.
48
+ */
49
+
50
+ /**
51
+ * Table options a binding passes through unchanged: presentation and listeners. The binding owns
52
+ * `columns`, `data`, and `sortMode`; editing, row reordering, and `hierarchy` are left out because
53
+ * they rearrange or change rows the server pages and never persists.
19
54
  */
55
+ const TABLE_OPTION_KEYS = [
56
+ 'variant', 'zebra', 'stickyHeader', 'responsive', 'growing', 'emptyText', 'rowClass', 'rowKind',
57
+ 'rowKinds', 'columnVisibility', 'columnReorder', 'hiddenColumns', 'onrowdblclick', 'onsort',
58
+ 'onselectionchange', 'ondatachange', 'onstackedchange', 'ongrow', 'oncolumnvisibilitychange',
59
+ 'oncolumnorderchange'
60
+ ];
20
61
 
21
62
  /**
22
63
  * @typedef {Object} ZeyosTableConfig
@@ -30,7 +71,12 @@ import { normalizeZeyosListResult } from './select.js';
30
71
  * @typedef {Object} ZeyosTableLoadOptions
31
72
  * @property {string} [search] Replacement full-text query.
32
73
  * @property {Record<string, unknown>} [filters] Replacement ZeyOS predicates.
33
- * @property {number} [page=0] Zero-based page; pages above zero append rows.
74
+ * @property {number} [page=0] Zero-based page, a non-negative integer. Pages above zero append
75
+ * rows unless `replace` is set.
76
+ * @property {boolean} [replace] Replace the rows with this page instead of appending it — what a
77
+ * numbered pager needs. Page zero always replaces.
78
+ * @property {number} [pageSize] New page size for this and every later load; the request offset is
79
+ * `page * pageSize`.
34
80
  */
35
81
 
36
82
  /**
@@ -51,15 +97,65 @@ import { normalizeZeyosListResult } from './select.js';
51
97
  * @property {() => Promise<ZeyosTableLoadResult>} loadMore Appends the next page when available.
52
98
  * @property {number|null} count Current server count.
53
99
  * @property {number} page Current zero-based page.
100
+ * @property {number} pageSize Current server page size.
54
101
  * @property {boolean} hasMore Whether another page is available.
55
- * @property {() => void} destroy Destroys the generated table.
102
+ * @property {() => void} destroy Destroys the generated table. A request still in flight is
103
+ * ignored when it resolves, and later loads reject.
104
+ */
105
+
106
+ /**
107
+ * The surface a server pager renders into: a Zx `Table`, or any RecordView.
108
+ * @typedef {Object} ServerPagerTarget
109
+ * @property {(rows: Record<string, unknown>[]) => unknown} setData Replaces the rows.
110
+ * @property {(rows: Record<string, unknown>[]) => unknown} addData Appends rows.
111
+ * @property {(loading: boolean) => unknown} setLoading Shows or hides the loading state.
112
+ */
113
+
114
+ /**
115
+ * @typedef {Object} ServerPagerOptions
116
+ * @property {Record<string, any>} client Injected ZeyOS client (for result normalization).
117
+ * @property {{list: (resource: string, query: Record<string, unknown>) => Promise<unknown>}} facade
118
+ * Connection facade from `connect(client)`.
119
+ * @property {string} resource Resource name.
120
+ * @property {Record<string, string>} projection API projection.
121
+ * @property {ServerPagerTarget} target Component the rows go to.
122
+ * @property {unknown} [pageSize=50] Initial page size; must be a positive integer.
123
+ * @property {{id: string, dir: 'asc'|'desc'}|null} [sort=null] Initial sort.
124
+ * @property {unknown} [filters={}] Initial predicates; must be a plain object.
125
+ * @property {unknown} [search=''] Initial full-text query.
126
+ * @property {string[]} [searchFields] UI search-field metadata passed to `buildListQuery`.
127
+ * @property {string} [label='ZeyOS binding'] Name used in error messages.
128
+ */
129
+
130
+ /**
131
+ * @typedef {Object} ServerPager
132
+ * @property {(options?: ZeyosTableLoadOptions) => Promise<ZeyosTableLoadResult>} load Loads a page.
133
+ * @property {(options?: ZeyosTableLoadOptions) => {page: number, pageSize: number, filters: Record<string, unknown>, search: string}} check
134
+ * Validates load options without applying them; throws what `load()` would reject with.
135
+ * @property {() => Promise<ZeyosTableLoadResult>} loadMore Appends the next page when available,
136
+ * else resolves without a request.
137
+ * @property {(sort: {id: string, dir: 'asc'|'desc'}|null) => void} setSort Replaces the sort used
138
+ * by the next load.
139
+ * @property {(state: {filters?: unknown, search?: unknown}) => void} setQuery Replaces filters and
140
+ * search without loading.
141
+ * @property {() => void} reset Forgets the loaded page, count, and `hasMore`.
142
+ * @property {() => void} destroy Stops rendering: in-flight responses are ignored, later loads reject.
143
+ * @property {boolean} destroyed Whether `destroy()` ran.
144
+ * @property {number|null} count Current server count.
145
+ * @property {number} page Current zero-based page.
146
+ * @property {number} pageSize Current page size.
147
+ * @property {boolean} hasMore Whether another page is available.
148
+ * @property {{id: string, dir: 'asc'|'desc'}|null} sort Current sort.
149
+ * @property {Record<string, unknown>} filters Copy of the current predicates.
150
+ * @property {string} search Current full-text query.
56
151
  */
57
152
 
58
153
  /**
59
154
  * Builds the DOM-free schema-derived columns and Table options used by {@link zeyosTable}. Entity
60
155
  * projections include an `<field>_label` alias from a real label field on the referenced resource.
61
156
  * Amount fields use each row's real `currency` value for display while retaining the WP14 typed
62
- * column strategy and right alignment.
157
+ * column strategy and right alignment. Enum columns show labels, date-only columns calendar days in
158
+ * `timeZone`, and Table presentation options and listeners pass through to `tableOptions`.
63
159
  * @param {Record<string, any>} client Injected ZeyOS client instance.
64
160
  * @param {string} resource Resource name such as `transactions`.
65
161
  * @param {ZeyosTableOptions} [opts={}] Schema, table, and initial query options.
@@ -68,7 +164,9 @@ import { normalizeZeyosListResult } from './select.js';
68
164
  export function buildZeyosTableConfig(client, resource, opts = {}) {
69
165
  const fieldMeta = resolveFields(client, resource, {
70
166
  fields: opts.fields,
71
- labels: opts.labels
167
+ labels: opts.labels,
168
+ formats: opts.formats,
169
+ timeZone: opts.timeZone
72
170
  });
73
171
  const projection = {};
74
172
  const columns = fieldMeta.map((sourceMeta) => {
@@ -94,11 +192,16 @@ export function buildZeyosTableConfig(client, resource, opts = {}) {
94
192
  if (typeof rowId === 'string' && !Object.prototype.hasOwnProperty.call(projection, rowId)) {
95
193
  projection[rowId] = rowId;
96
194
  }
195
+ const passed = {};
196
+ for (const key of TABLE_OPTION_KEYS) {
197
+ if (opts[key] !== undefined) passed[key] = opts[key];
198
+ }
97
199
  return {
98
200
  fieldMeta,
99
201
  columns,
100
202
  projection,
101
203
  tableOptions: {
204
+ ...passed,
102
205
  columns,
103
206
  data: [],
104
207
  rowId,
@@ -106,17 +209,23 @@ export function buildZeyosTableConfig(client, resource, opts = {}) {
106
209
  sortMode: 'server',
107
210
  selectable: opts.selectable ?? false,
108
211
  height: opts.height ?? null,
109
- onrowclick: typeof opts.onRowClick === 'function'
110
- ? (event) => opts.onRowClick(event.detail.row, event)
111
- : undefined
212
+ onrowclick: rowClickListener(opts)
112
213
  }
113
214
  };
114
215
  }
115
216
 
116
217
  /**
117
- * Generates a server-backed Zx Table. Sorting automatically reloads page zero. `load({page: 0})`
118
- * replaces data, higher pages append it, and `loadMore()` uses `limit`/`offset` plus the returned
119
- * count. DataFilter callers can map state with `dataFilterStateToFilters` before `setFilters()`.
218
+ * Generates a server-backed Zx Table. Sorting automatically reloads page zero. Paging is
219
+ * zero-based: `load({page: 0})` replaces data, higher pages append it — load-more paging, which
220
+ * `loadMore()` drives from `limit`/`offset` plus the returned count — and `replace: true` replaces
221
+ * at any page, which is what a numbered `Pagination` needs. `Pagination` is one-based, so its
222
+ * `change` maps as `load({page: detail.page - 1, pageSize: detail.pageSize, replace: true})`, the
223
+ * request offset being `(page - 1) * pageSize`; feed `result.count` back with `setTotal()`.
224
+ * DataFilter callers can map state with `dataFilterStateToFilters` before `setFilters()`.
225
+ * Paging is shared with `zeyosView()` through {@link createServerPager}: the same validation, and
226
+ * a response that resolves after `destroy()` never touches the destroyed table.
227
+ * Table presentation options (`variant`, `zebra`, `stickyHeader`, `responsive`, `emptyText`, …) and
228
+ * listeners are passed through to the generated Table.
120
229
  * @param {Record<string, any>} client Injected ZeyOS client instance.
121
230
  * @param {string} resource Resource name such as `transactions`.
122
231
  * @param {ZeyosTableOptions} [opts={}] Schema, table, and initial query options.
@@ -126,82 +235,171 @@ export function zeyosTable(client, resource, opts = {}) {
126
235
  const config = buildZeyosTableConfig(client, resource, opts);
127
236
  const facade = connect(client);
128
237
  const table = new Table(null, config.tableOptions);
129
- const pageSize = normalizePageSize(opts.pageSize);
130
- let currentSort = opts.sort ?? null;
131
- let currentFilters = copyFilters(opts.filters ?? {});
132
- let currentSearch = String(opts.search ?? '');
133
- let currentPage = 0;
134
- let totalCount = null;
238
+ let pager;
239
+ try {
240
+ pager = createServerPager({
241
+ client, facade, resource, projection: config.projection, target: table,
242
+ pageSize: opts.pageSize, sort: opts.sort ?? null, filters: opts.filters ?? {}, search: opts.search,
243
+ label: 'ZeyOS table'
244
+ });
245
+ } catch (error) {
246
+ table.destroy();
247
+ throw error;
248
+ }
249
+
250
+ const binding = {
251
+ table,
252
+ load: pager.load,
253
+ setSearch: (search) => pager.load({ search, page: 0 }),
254
+ setFilters: (filters) => pager.load({ filters, page: 0 }),
255
+ loadMore: pager.loadMore,
256
+ get count() { return pager.count; },
257
+ get page() { return pager.page; },
258
+ get pageSize() { return pager.pageSize; },
259
+ get hasMore() { return pager.hasMore; },
260
+ destroy: () => {
261
+ if (pager.destroyed) return;
262
+ pager.destroy();
263
+ table.destroy();
264
+ }
265
+ };
266
+
267
+ table.on('sort', (event) => {
268
+ if (pager.destroyed) return;
269
+ const id = event.detail?.id;
270
+ pager.setSort(id == null ? null : { id: String(id), dir: event.detail.dir === 'desc' ? 'desc' : 'asc' });
271
+ void pager.load({ page: 0 }).catch(() => { /* The connection facade already reported the failure. */ });
272
+ });
273
+ return binding;
274
+ }
275
+
276
+ /**
277
+ * The one server-paging loader behind {@link zeyosTable} and `zeyosView`, so the two bindings
278
+ * cannot drift apart. It builds the list request (`limit = pageSize`, `offset = page * pageSize`,
279
+ * always with `count`), renders the response into its target — page zero or `replace` replaces
280
+ * the rows, a higher page appends them — and keeps `count`, `page`, and `hasMore`.
281
+ *
282
+ * Only the newest request may render: an older one that resolves late is returned to its caller
283
+ * but never touches the target. After `destroy()` nothing renders at all — a response that arrives
284
+ * afterwards is ignored, the loading state is left alone — and further loads reject.
285
+ *
286
+ * Validation is strict and the same for both bindings: a page must be a non-negative integer and a
287
+ * page size a positive integer (`RangeError`), filters a plain object (`TypeError`). Every argument
288
+ * of a call is checked before any of it is applied, so a rejected call changes nothing.
289
+ * @param {ServerPagerOptions} options Pager configuration.
290
+ * @returns {ServerPager}
291
+ */
292
+ export function createServerPager(options) {
293
+ const { client, facade, resource, projection, target, searchFields } = options;
294
+ const label = String(options.label ?? 'ZeyOS binding');
295
+ let pageSize = normalizePageSize(options.pageSize, label);
296
+ let sort = options.sort ?? null;
297
+ let filters = copyFilters(options.filters ?? {}, label);
298
+ let search = String(options.search ?? '');
299
+ let page = 0;
300
+ let count = null;
135
301
  let more = false;
136
- let requestSequence = 0;
302
+ let sequence = 0;
303
+ let destroyed = false;
137
304
 
138
- const load = async (options = {}) => {
139
- if (Object.prototype.hasOwnProperty.call(options, 'search')) currentSearch = String(options.search ?? '');
140
- if (Object.prototype.hasOwnProperty.call(options, 'filters')) currentFilters = copyFilters(options.filters ?? {});
141
- const page = normalizePage(options.page ?? 0);
142
- const query = buildListQuery({
143
- fields: config.projection,
144
- sort: currentSort,
145
- filters: currentFilters,
146
- search: currentSearch,
147
- limit: pageSize,
148
- offset: page * pageSize,
149
- count: true
150
- });
151
- const sequence = ++requestSequence;
152
- table.setLoading(true);
305
+ const query = (forPage) => buildListQuery({
306
+ fields: projection, sort, filters, search, searchFields,
307
+ limit: pageSize, offset: forPage * pageSize, count: true
308
+ });
309
+
310
+ /**
311
+ * Validates a load request without applying any of it.
312
+ * @param {ZeyosTableLoadOptions} [request={}] Load options.
313
+ * @returns {{page: number, pageSize: number, filters: Record<string, unknown>, search: string}}
314
+ */
315
+ const check = (request = {}) => {
316
+ const source = request && typeof request === 'object' ? request : {};
317
+ const has = (key) => Object.prototype.hasOwnProperty.call(source, key);
318
+ return {
319
+ page: normalizePage(source.page ?? 0, label),
320
+ pageSize: has('pageSize') && source.pageSize !== undefined
321
+ ? normalizePageSize(source.pageSize, label) : pageSize,
322
+ filters: has('filters') ? copyFilters(source.filters ?? {}, label) : filters,
323
+ search: has('search') ? String(source.search ?? '') : search
324
+ };
325
+ };
326
+
327
+ /** @param {ZeyosTableLoadOptions} [request={}] @returns {Promise<ZeyosTableLoadResult>} */
328
+ const load = async (request = {}) => {
329
+ if (destroyed) throw new Error(`${label} binding has been destroyed`);
330
+ // Every argument is validated before any state changes, so a call that rejects leaves the
331
+ // binding exactly as it was: no page size or search half-applied ahead of a bad filter.
332
+ const next = check(request);
333
+ const nextPage = next.page;
334
+ pageSize = next.pageSize;
335
+ filters = next.filters;
336
+ search = next.search;
337
+ const replace = nextPage === 0 || request.replace === true;
338
+ const sent = query(nextPage);
339
+ const mine = ++sequence;
340
+ target.setLoading(true);
153
341
  try {
154
- const normalized = normalizeZeyosListResult(client, await facade.list(resource, query));
155
- const count = normalized.count ?? null;
156
- const hasMore = count == null
342
+ const normalized = normalizeZeyosListResult(client, await facade.list(resource, sent));
343
+ const total = normalized.count ?? null;
344
+ const hasMore = total == null
157
345
  ? normalized.data.length === pageSize
158
- : (page * pageSize + normalized.data.length) < count;
159
- if (sequence === requestSequence) {
160
- currentPage = page;
161
- totalCount = count;
346
+ : nextPage * pageSize + normalized.data.length < total;
347
+ if (!destroyed && mine === sequence) {
348
+ page = nextPage;
349
+ count = total;
162
350
  more = hasMore;
163
- if (page === 0) table.setData(normalized.data);
164
- else table.addData(normalized.data);
351
+ if (replace) target.setData(normalized.data);
352
+ else target.addData(normalized.data);
165
353
  }
166
- return { data: normalized.data, count, page, hasMore, query };
354
+ return { data: normalized.data, count: total, page: nextPage, hasMore, query: sent };
167
355
  } finally {
168
- if (sequence === requestSequence) table.setLoading(false);
356
+ if (!destroyed && mine === sequence) target.setLoading(false);
169
357
  }
170
358
  };
171
359
 
172
- const binding = {
173
- table,
360
+ return {
174
361
  load,
175
- setSearch: (search) => load({ search, page: 0 }),
176
- setFilters: (filters) => load({ filters, page: 0 }),
177
- loadMore: () => more ? load({ page: currentPage + 1 }) : Promise.resolve({
178
- data: [],
179
- count: totalCount,
180
- page: currentPage,
181
- hasMore: false,
182
- query: buildListQuery({
183
- fields: config.projection,
184
- sort: currentSort,
185
- filters: currentFilters,
186
- search: currentSearch,
187
- limit: pageSize,
188
- offset: currentPage * pageSize,
189
- count: true
190
- })
191
- }),
192
- get count() { return totalCount; },
193
- get page() { return currentPage; },
362
+ check,
363
+ loadMore: () => more ? load({ page: page + 1 }) : (destroyed
364
+ ? Promise.reject(new Error(`${label} binding has been destroyed`))
365
+ : Promise.resolve({ data: [], count, page, hasMore: false, query: query(page) })),
366
+ setSort: (next) => { sort = next ?? null; },
367
+ setQuery: (state = {}) => {
368
+ const nextFilters = Object.prototype.hasOwnProperty.call(state, 'filters')
369
+ ? copyFilters(state.filters ?? {}, label) : filters;
370
+ if (Object.prototype.hasOwnProperty.call(state, 'search')) search = String(state.search ?? '');
371
+ filters = nextFilters;
372
+ },
373
+ reset: () => { page = 0; count = null; more = false; },
374
+ destroy: () => {
375
+ if (destroyed) return;
376
+ destroyed = true;
377
+ sequence += 1;
378
+ },
379
+ get destroyed() { return destroyed; },
380
+ get count() { return count; },
381
+ get page() { return page; },
382
+ get pageSize() { return pageSize; },
194
383
  get hasMore() { return more; },
195
- destroy: () => table.destroy()
384
+ get sort() { return sort; },
385
+ get filters() { return { ...filters }; },
386
+ get search() { return search; }
196
387
  };
388
+ }
197
389
 
198
- table.on('sort', (event) => {
199
- const id = event.detail?.id;
200
- currentSort = id == null ? null
201
- : { id: String(id), dir: event.detail.dir === 'desc' ? 'desc' : 'asc' };
202
- void load({ page: 0 }).catch(() => { /* The connection facade already reported the failure. */ });
203
- });
204
- return binding;
390
+ /**
391
+ * Combines the Table-style `onrowclick` listener with the binding's row-first `onRowClick`.
392
+ * @param {ZeyosTableOptions} opts
393
+ * @returns {((event: CustomEvent<Record<string, any>>) => void)|undefined}
394
+ */
395
+ function rowClickListener(opts) {
396
+ const listener = typeof opts.onrowclick === 'function' ? opts.onrowclick : null;
397
+ const callback = typeof opts.onRowClick === 'function' ? opts.onRowClick : null;
398
+ if (!listener && !callback) return undefined;
399
+ return (event) => {
400
+ listener?.(event);
401
+ callback?.(event.detail.row, event);
402
+ };
205
403
  }
206
404
 
207
405
  /** @param {Record<string, any>} meta @returns {string|null} */
@@ -252,23 +450,28 @@ function schemaHasField(client, resource, id) {
252
450
  return Boolean(fields && typeof fields === 'object' && Object.prototype.hasOwnProperty.call(fields, id));
253
451
  }
254
452
 
255
- /** @param {unknown} value @returns {number} */
256
- function normalizePageSize(value) {
257
- const number = Number(value ?? 50);
258
- return Number.isInteger(number) && number > 0 ? number : 50;
453
+ /** Default server page size shared by `zeyosTable()` and `zeyosView()`. */
454
+ export const DEFAULT_SERVER_PAGE_SIZE = 50;
455
+
456
+ /** @param {unknown} value @param {string} [label='ZeyOS table'] @returns {number} */
457
+ function normalizePageSize(value, label = 'ZeyOS table') {
458
+ if (value === undefined || value === null) return DEFAULT_SERVER_PAGE_SIZE;
459
+ const number = Number(value);
460
+ if (!Number.isInteger(number) || number < 1) throw new RangeError(`${label} page size must be a positive integer`);
461
+ return number;
259
462
  }
260
463
 
261
- /** @param {unknown} value @returns {number} */
262
- function normalizePage(value) {
464
+ /** @param {unknown} value @param {string} [label='ZeyOS table'] @returns {number} */
465
+ function normalizePage(value, label = 'ZeyOS table') {
263
466
  const number = Number(value);
264
- if (!Number.isInteger(number) || number < 0) throw new RangeError('ZeyOS table page must be a non-negative integer');
467
+ if (!Number.isInteger(number) || number < 0) throw new RangeError(`${label} page must be a non-negative integer`);
265
468
  return number;
266
469
  }
267
470
 
268
- /** @param {Record<string, unknown>} filters @returns {Record<string, unknown>} */
269
- function copyFilters(filters) {
471
+ /** @param {unknown} filters @param {string} [label='ZeyOS table'] @returns {Record<string, unknown>} */
472
+ function copyFilters(filters, label = 'ZeyOS table') {
270
473
  if (!filters || typeof filters !== 'object' || Array.isArray(filters)) {
271
- throw new TypeError('ZeyOS table filters must be an object');
474
+ throw new TypeError(`${label} filters must be an object`);
272
475
  }
273
- return { ...filters };
476
+ return { .../** @type {Record<string, unknown>} */ (filters) };
274
477
  }