@vobs/ui 0.2.0 → 1.0.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 (208) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -0
  3. package/package.json +52 -102
  4. package/src/activity-rail.ts +125 -0
  5. package/src/alert.ts +65 -0
  6. package/src/assets/icons/alert-circle.svg +1 -0
  7. package/src/assets/icons/alert-triangle.svg +1 -0
  8. package/src/assets/icons/alert.d27e24.svg +1 -0
  9. package/src/assets/icons/alert.svg +1 -0
  10. package/src/assets/icons/apple.svg +1 -0
  11. package/src/assets/icons/arrow-down.svg +1 -0
  12. package/src/assets/icons/arrow-expand.svg +1 -0
  13. package/src/assets/icons/arrow-left.svg +1 -0
  14. package/src/assets/icons/arrow-minimize.svg +1 -0
  15. package/src/assets/icons/arrow-right-to-line.svg +1 -0
  16. package/src/assets/icons/arrow-right.svg +1 -0
  17. package/src/assets/icons/arrow-up-right.svg +1 -0
  18. package/src/assets/icons/arrow-up.0c0c0d.svg +1 -0
  19. package/src/assets/icons/arrow-up.svg +1 -0
  20. package/src/assets/icons/at.svg +1 -0
  21. package/src/assets/icons/atom.0c0c0d.svg +1 -0
  22. package/src/assets/icons/atom.svg +1 -0
  23. package/src/assets/icons/bar.svg +1 -0
  24. package/src/assets/icons/bell.svg +1 -0
  25. package/src/assets/icons/box.svg +1 -0
  26. package/src/assets/icons/bug.svg +1 -0
  27. package/src/assets/icons/calendar.svg +1 -0
  28. package/src/assets/icons/check-circle.33c192.svg +1 -0
  29. package/src/assets/icons/check-circle.svg +1 -0
  30. package/src/assets/icons/check.d1d3db.svg +1 -0
  31. package/src/assets/icons/check.svg +1 -0
  32. package/src/assets/icons/chevron-down.svg +1 -0
  33. package/src/assets/icons/chevron-right.svg +1 -0
  34. package/src/assets/icons/chevron-up.svg +1 -0
  35. package/src/assets/icons/circle.svg +1 -0
  36. package/src/assets/icons/clock.svg +1 -0
  37. package/src/assets/icons/cmd.svg +1 -0
  38. package/src/assets/icons/code.svg +1 -0
  39. package/src/assets/icons/columns.svg +1 -0
  40. package/src/assets/icons/copy.svg +1 -0
  41. package/src/assets/icons/cpu.svg +1 -0
  42. package/src/assets/icons/dashboard.0c0c0d.svg +1 -0
  43. package/src/assets/icons/dashboard.svg +1 -0
  44. package/src/assets/icons/dollar.svg +1 -0
  45. package/src/assets/icons/download.svg +1 -0
  46. package/src/assets/icons/edit.svg +1 -0
  47. package/src/assets/icons/extensions.svg +1 -0
  48. package/src/assets/icons/external.svg +1 -0
  49. package/src/assets/icons/eye.svg +1 -0
  50. package/src/assets/icons/file-text.svg +1 -0
  51. package/src/assets/icons/file.svg +1 -0
  52. package/src/assets/icons/files.svg +1 -0
  53. package/src/assets/icons/filter.svg +1 -0
  54. package/src/assets/icons/folder.svg +1 -0
  55. package/src/assets/icons/gear.svg +1 -0
  56. package/src/assets/icons/git.svg +1 -0
  57. package/src/assets/icons/github.svg +1 -0
  58. package/src/assets/icons/globe.svg +1 -0
  59. package/src/assets/icons/grid-2x2.svg +1 -0
  60. package/src/assets/icons/hash.svg +1 -0
  61. package/src/assets/icons/heart.svg +1 -0
  62. package/src/assets/icons/help.svg +1 -0
  63. package/src/assets/icons/home.svg +1 -0
  64. package/src/assets/icons/image.svg +1 -0
  65. package/src/assets/icons/info-circle.svg +1 -0
  66. package/src/assets/icons/info-square.svg +1 -0
  67. package/src/assets/icons/info.387bff.svg +1 -0
  68. package/src/assets/icons/info.svg +1 -0
  69. package/src/assets/icons/key.svg +1 -0
  70. package/src/assets/icons/layers.svg +1 -0
  71. package/src/assets/icons/layout.svg +1 -0
  72. package/src/assets/icons/link.svg +1 -0
  73. package/src/assets/icons/list.svg +1 -0
  74. package/src/assets/icons/lock.svg +1 -0
  75. package/src/assets/icons/log-out.svg +1 -0
  76. package/src/assets/icons/logo.svg +1 -0
  77. package/src/assets/icons/mail.svg +1 -0
  78. package/src/assets/icons/menu.svg +1 -0
  79. package/src/assets/icons/message-circle.svg +1 -0
  80. package/src/assets/icons/message-plus.svg +1 -0
  81. package/src/assets/icons/message-square.svg +1 -0
  82. package/src/assets/icons/mic.svg +1 -0
  83. package/src/assets/icons/minus.svg +1 -0
  84. package/src/assets/icons/moon.svg +1 -0
  85. package/src/assets/icons/more-h.svg +1 -0
  86. package/src/assets/icons/mouse.svg +1 -0
  87. package/src/assets/icons/palette.svg +1 -0
  88. package/src/assets/icons/panel-bottom.svg +1 -0
  89. package/src/assets/icons/pause.svg +1 -0
  90. package/src/assets/icons/play.svg +1 -0
  91. package/src/assets/icons/plug.svg +1 -0
  92. package/src/assets/icons/plus.0c0c0d.svg +1 -0
  93. package/src/assets/icons/plus.svg +1 -0
  94. package/src/assets/icons/refresh.svg +1 -0
  95. package/src/assets/icons/ruler.svg +1 -0
  96. package/src/assets/icons/scroll-text.svg +1 -0
  97. package/src/assets/icons/search-menu.svg +1 -0
  98. package/src/assets/icons/search.svg +1 -0
  99. package/src/assets/icons/send.0c0c0d.svg +1 -0
  100. package/src/assets/icons/send.svg +1 -0
  101. package/src/assets/icons/settings.0c0c0d.svg +1 -0
  102. package/src/assets/icons/settings.svg +1 -0
  103. package/src/assets/icons/shield.svg +1 -0
  104. package/src/assets/icons/sidebar-left.svg +1 -0
  105. package/src/assets/icons/sidebar-right.svg +1 -0
  106. package/src/assets/icons/sliders.svg +1 -0
  107. package/src/assets/icons/sparkles.32f08c.svg +1 -0
  108. package/src/assets/icons/sparkles.svg +1 -0
  109. package/src/assets/icons/square.svg +1 -0
  110. package/src/assets/icons/star.svg +1 -0
  111. package/src/assets/icons/sun.svg +1 -0
  112. package/src/assets/icons/terminal.svg +1 -0
  113. package/src/assets/icons/trash.svg +1 -0
  114. package/src/assets/icons/trending-down.svg +1 -0
  115. package/src/assets/icons/trending-up.svg +1 -0
  116. package/src/assets/icons/type.svg +1 -0
  117. package/src/assets/icons/upload.svg +1 -0
  118. package/src/assets/icons/user-circle.svg +1 -0
  119. package/src/assets/icons/user.svg +1 -0
  120. package/src/assets/icons/users.svg +1 -0
  121. package/src/assets/icons/wrench.svg +1 -0
  122. package/src/assets/icons/x-circle.svg +1 -0
  123. package/src/assets/icons/x.svg +1 -0
  124. package/src/assets/icons/zap.0c0c0d.svg +1 -0
  125. package/src/assets/icons/zap.svg +1 -0
  126. package/src/avatar.ts +64 -0
  127. package/src/button.ts +88 -0
  128. package/src/card.ts +59 -0
  129. package/src/chat-composer.ts +156 -0
  130. package/src/dialog.ts +127 -0
  131. package/src/drawer.ts +131 -0
  132. package/src/editor-tabs.ts +184 -0
  133. package/src/file-tree.ts +214 -0
  134. package/src/forms.ts +270 -0
  135. package/src/icon.ts +117 -0
  136. package/src/icons.js +165 -0
  137. package/src/index.test.ts +213 -0
  138. package/src/index.ts +94 -0
  139. package/src/kbd.ts +72 -0
  140. package/src/menu.ts +119 -0
  141. package/src/nav-list.ts +157 -0
  142. package/src/overlay.test.ts +66 -0
  143. package/src/overlay.ts +205 -0
  144. package/src/page-header.ts +97 -0
  145. package/src/pagination.ts +163 -0
  146. package/src/second-batch.test.ts +205 -0
  147. package/src/stat-card.ts +92 -0
  148. package/src/status-bar.ts +108 -0
  149. package/src/styles/base.css +240 -0
  150. package/src/styles/components.css +1234 -0
  151. package/{dist → src/styles}/styles.css +2 -0
  152. package/src/styles/toast.css +57 -0
  153. package/src/styles/tokens.css +438 -0
  154. package/src/styles/vui.css +47 -0
  155. package/src/table.ts +191 -0
  156. package/src/tabs.ts +100 -0
  157. package/src/tag.ts +33 -0
  158. package/src/third-batch.test.ts +137 -0
  159. package/src/toast.test.ts +55 -0
  160. package/src/toast.ts +79 -0
  161. package/src/types.ts +25 -0
  162. package/src/utils.ts +221 -0
  163. package/src/workbench-titlebar.ts +137 -0
  164. package/dist/app-shell-dom.d.ts +0 -24
  165. package/dist/app-shell-dom.js +0 -573
  166. package/dist/app-shell.d.ts +0 -199
  167. package/dist/app-shell.js +0 -370
  168. package/dist/base.css +0 -52
  169. package/dist/calendar-picker.d.ts +0 -45
  170. package/dist/calendar-picker.js +0 -217
  171. package/dist/calendar.d.ts +0 -95
  172. package/dist/calendar.js +0 -194
  173. package/dist/complex-inputs.d.ts +0 -300
  174. package/dist/complex-inputs.js +0 -862
  175. package/dist/components.css +0 -4568
  176. package/dist/data-display.d.ts +0 -1063
  177. package/dist/data-display.js +0 -2298
  178. package/dist/feedback.d.ts +0 -88
  179. package/dist/feedback.js +0 -281
  180. package/dist/forms.d.ts +0 -378
  181. package/dist/forms.js +0 -1015
  182. package/dist/icons-config.d.ts +0 -25
  183. package/dist/icons-config.js +0 -14
  184. package/dist/icons.d.ts +0 -41
  185. package/dist/icons.js +0 -185
  186. package/dist/index.d.ts +0 -27
  187. package/dist/index.js +0 -34
  188. package/dist/locale/en-US.d.ts +0 -31
  189. package/dist/locale/en-US.js +0 -20
  190. package/dist/locale/zh-CN.d.ts +0 -31
  191. package/dist/locale/zh-CN.js +0 -20
  192. package/dist/navigation.d.ts +0 -609
  193. package/dist/navigation.js +0 -1707
  194. package/dist/overlay.d.ts +0 -304
  195. package/dist/overlay.js +0 -969
  196. package/dist/primitives.d.ts +0 -268
  197. package/dist/primitives.js +0 -764
  198. package/dist/theme-data.d.ts +0 -363
  199. package/dist/theme-data.js +0 -374
  200. package/dist/theme.d.ts +0 -79
  201. package/dist/theme.js +0 -262
  202. package/dist/tokens.css +0 -449
  203. package/dist/tree.d.ts +0 -144
  204. package/dist/tree.js +0 -469
  205. package/dist/virtual-list.d.ts +0 -190
  206. package/dist/virtual-list.js +0 -521
  207. package/dist/workbench.d.ts +0 -136
  208. package/dist/workbench.js +0 -156
@@ -1,2298 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { classNames, createButtonPrimitive, createSkeletonPrimitive, createSpinPrimitive, createUiDomPlan, } from './primitives.js';
6
- export function createTablePlans(options) {
7
- const columns = options.columns.filter((column) => column.hidden !== true);
8
- const sortColumn = columns.find((column) => column.sortDirection !== undefined);
9
- return {
10
- root: createUiDomPlan('table', 'table', {
11
- ...(options.className === undefined ? {} : { className: options.className }),
12
- ...(options.density === undefined ? {} : { density: options.density }),
13
- attrs: {
14
- id: options.id,
15
- 'aria-labelledby': options.captionId,
16
- ...options.attrs,
17
- 'data-columns': columns.length,
18
- 'data-rows': options.rows.length,
19
- 'data-selectable': options.selectable === true ? 'true' : undefined,
20
- 'data-sticky-header': options.stickyHeader === true ? 'true' : undefined,
21
- 'data-sort-column': sortColumn?.id,
22
- 'data-sort-direction': sortColumn?.sortDirection,
23
- },
24
- }),
25
- caption: options.captionId === undefined
26
- ? undefined
27
- : createUiDomPlan('caption', 'table-caption', { attrs: { id: options.captionId } }),
28
- selectionHeader: options.selectable === true
29
- ? createUiDomPlan('th', 'table-selection-header', {
30
- attrs: {
31
- scope: 'col',
32
- 'aria-label': 'Select rows',
33
- 'data-sticky': 'start',
34
- },
35
- })
36
- : undefined,
37
- headers: columns.map((column) => createUiDomPlan('th', 'table-header', {
38
- attrs: {
39
- scope: 'col',
40
- role: column.sortable === true ? 'columnheader' : undefined,
41
- 'aria-sort': ariaSort(column),
42
- 'data-column-id': column.id,
43
- 'data-align': column.align,
44
- 'data-sortable': column.sortable === true ? 'true' : undefined,
45
- 'data-sort-direction': column.sortDirection,
46
- 'data-sticky': column.sticky,
47
- style: tableColumnStyle(column),
48
- },
49
- })),
50
- rows: options.rows.map((row) => createUiDomPlan('tr', 'table-row', {
51
- state: row.selected === true ? 'selected' : row.disabled === true ? 'disabled' : 'idle',
52
- attrs: {
53
- 'data-row-id': row.id,
54
- 'aria-selected': row.selected === true ? 'true' : undefined,
55
- 'aria-checked': row.checked === true ? 'true' : undefined,
56
- 'aria-disabled': row.disabled === true ? 'true' : undefined,
57
- 'aria-expanded': row.expanded === undefined ? undefined : row.expanded ? 'true' : 'false',
58
- },
59
- })),
60
- selectionCells: options.selectable === true
61
- ? options.rows.map((row) => createUiDomPlan('td', 'table-selection-cell', {
62
- attrs: {
63
- 'data-row-id': row.id,
64
- 'data-sticky': 'start',
65
- 'data-checked': row.checked === true ? 'true' : 'false',
66
- },
67
- }))
68
- : [],
69
- cells: options.rows.flatMap((row) => columns.map((column) => createUiDomPlan('td', 'table-cell', {
70
- attrs: {
71
- 'data-row-id': row.id,
72
- 'data-column-id': column.id,
73
- 'data-align': column.align,
74
- 'data-sticky': column.sticky,
75
- style: tableColumnStyle(column),
76
- },
77
- }))),
78
- };
79
- }
80
- export function getNextTableSortDirection(current, cycle = 'tri-state') {
81
- if (current === undefined || current === 'none')
82
- return 'ascending';
83
- if (current === 'ascending')
84
- return 'descending';
85
- return cycle === 'binary' ? 'ascending' : 'none';
86
- }
87
- export function toggleTableRowSelection(selectedIds, rowId, multiselect = true) {
88
- if (selectedIds.has(rowId)) {
89
- const next = new Set(selectedIds);
90
- next.delete(rowId);
91
- return next;
92
- }
93
- if (!multiselect)
94
- return new Set([rowId]);
95
- return new Set([...selectedIds, rowId]);
96
- }
97
- export function createTableToolbarStateContract(options = {}) {
98
- const mode = options.mode ?? 'local';
99
- const query = (options.query ?? '').trim();
100
- const activeFilterIds = (options.filters ?? [])
101
- .filter((filter) => filter.active === true)
102
- .map((filter) => filter.id);
103
- const selectedIds = normalizeSelectedIds(options.selectedIds);
104
- const selectedCount = selectedIds.length;
105
- const totalCount = normalizeCount(options.totalCount);
106
- const filteredCount = normalizeCount(options.filteredCount);
107
- const sortColumnId = options.sortColumnId ?? '';
108
- const sortDirection = options.sortDirection ?? 'none';
109
- const summary = formatDataToolbarSummary({
110
- ...(totalCount === undefined ? {} : { totalCount }),
111
- ...(filteredCount === undefined ? {} : { filteredCount }),
112
- selectedCount,
113
- });
114
- const state = tableToolbarState({
115
- activeFilterCount: activeFilterIds.length,
116
- filteredCount,
117
- loading: options.loading === true,
118
- query,
119
- selectedCount,
120
- sortDirection,
121
- totalCount,
122
- });
123
- return {
124
- mode,
125
- state,
126
- query,
127
- activeFilterIds,
128
- activeFilterCount: activeFilterIds.length,
129
- sortColumnId,
130
- sortDirection,
131
- selectedIds,
132
- selectedCount,
133
- totalCount,
134
- filteredCount,
135
- summary,
136
- attrs: {
137
- 'data-source': mode,
138
- 'data-state': state,
139
- 'data-query': query,
140
- 'data-active-filter-ids': activeFilterIds.join(','),
141
- 'data-active-filter-count': activeFilterIds.length,
142
- 'data-sort-column': sortColumnId,
143
- 'data-sort-direction': sortDirection,
144
- 'data-selected-ids': selectedIds.join(','),
145
- 'data-selected-count': selectedCount,
146
- 'data-total-count': totalCount,
147
- 'data-filtered-count': filteredCount,
148
- 'data-summary': summary,
149
- },
150
- };
151
- }
152
- export function createTableColumnPreferenceStateContract(options) {
153
- const knownIds = new Set(options.columns.map((column) => column.id));
154
- const density = options.density ?? options.defaultDensity ?? 'comfortable';
155
- const orderedIds = normalizeColumnPreferenceIds(options.orderIds, knownIds);
156
- const visibleSet = options.visibleIds === undefined
157
- ? undefined
158
- : new Set(normalizeColumnPreferenceIds(options.visibleIds, knownIds));
159
- const hiddenSet = new Set(normalizeColumnPreferenceIds(options.hiddenIds, knownIds));
160
- const stickyStartSet = options.stickyStartIds === undefined
161
- ? undefined
162
- : new Set(normalizeColumnPreferenceIds(options.stickyStartIds, knownIds));
163
- const stickyEndSet = options.stickyEndIds === undefined
164
- ? undefined
165
- : new Set(normalizeColumnPreferenceIds(options.stickyEndIds, knownIds));
166
- const orderedColumns = orderTableColumns(options.columns, orderedIds);
167
- const columns = orderedColumns.map((column) => {
168
- const visible = (visibleSet === undefined || visibleSet.has(column.id)) && !hiddenSet.has(column.id);
169
- const sticky = tableColumnPreferenceSticky(column, stickyStartSet, stickyEndSet);
170
- const { hidden: _hidden, sticky: _sticky, ...baseColumn } = column;
171
- void _hidden;
172
- void _sticky;
173
- return sticky === undefined
174
- ? { ...baseColumn, hidden: !visible }
175
- : { ...baseColumn, hidden: !visible, sticky };
176
- });
177
- const columnContracts = columns.map((column, index) => {
178
- const visible = column.hidden !== true;
179
- return {
180
- id: column.id,
181
- label: column.label ?? column.id,
182
- visible,
183
- order: index,
184
- sticky: column.sticky,
185
- attrs: {
186
- 'data-column-id': column.id,
187
- 'data-column-order': index,
188
- 'data-visible': visible ? 'true' : 'false',
189
- 'data-hidden': visible ? undefined : 'true',
190
- 'data-sticky': column.sticky,
191
- },
192
- };
193
- });
194
- const visibleIds = columnContracts.filter((column) => column.visible).map((column) => column.id);
195
- const hiddenIds = columnContracts.filter((column) => !column.visible).map((column) => column.id);
196
- const stickyStartIds = columnContracts
197
- .filter((column) => column.sticky === 'start' && column.visible)
198
- .map((column) => column.id);
199
- const stickyEndIds = columnContracts
200
- .filter((column) => column.sticky === 'end' && column.visible)
201
- .map((column) => column.id);
202
- const state = tableColumnPreferenceState({
203
- columns,
204
- density,
205
- defaultDensity: options.defaultDensity,
206
- hiddenIds,
207
- orderIds: orderedIds,
208
- sourceColumns: options.columns,
209
- stickyEndIds: options.stickyEndIds,
210
- stickyStartIds: options.stickyStartIds,
211
- visibleIds: options.visibleIds,
212
- });
213
- return {
214
- state,
215
- density,
216
- columnCount: options.columns.length,
217
- visibleCount: visibleIds.length,
218
- hiddenCount: hiddenIds.length,
219
- orderedIds: columns.map((column) => column.id),
220
- visibleIds,
221
- hiddenIds,
222
- stickyStartIds,
223
- stickyEndIds,
224
- columns,
225
- columnContracts,
226
- attrs: {
227
- 'data-table-column-preference-contract': 'true',
228
- 'data-state': state,
229
- 'data-density': density,
230
- 'data-column-count': options.columns.length,
231
- 'data-visible-column-count': visibleIds.length,
232
- 'data-hidden-column-count': hiddenIds.length,
233
- 'data-column-order': columns.map((column) => column.id).join(','),
234
- 'data-visible-column-ids': visibleIds.join(','),
235
- 'data-hidden-column-ids': hiddenIds.join(','),
236
- 'data-sticky-start-ids': stickyStartIds.join(','),
237
- 'data-sticky-end-ids': stickyEndIds.join(','),
238
- },
239
- };
240
- }
241
- export function createTableCellStateContract(options) {
242
- const kind = options.kind ?? 'text';
243
- const intent = options.intent ?? '';
244
- const state = tableCellState(options);
245
- return {
246
- rowId: options.rowId,
247
- columnId: options.columnId,
248
- kind,
249
- state,
250
- intent,
251
- attrs: {
252
- 'data-table-cell-contract': 'true',
253
- 'data-row-id': options.rowId,
254
- 'data-column-id': options.columnId,
255
- 'data-cell-kind': kind,
256
- 'data-cell-state': state,
257
- 'data-cell-intent': intent,
258
- 'data-value': options.value,
259
- 'data-tone': options.tone,
260
- 'data-interactive': options.interactive === true ? 'true' : undefined,
261
- 'data-truncate': options.truncate === true ? 'true' : undefined,
262
- 'aria-label': options.label,
263
- 'aria-describedby': options.describedBy,
264
- 'aria-disabled': options.disabled === true ? 'true' : undefined,
265
- 'aria-busy': options.loading === true ? 'true' : undefined,
266
- ...options.attrs,
267
- },
268
- };
269
- }
270
- export function createTableBatchActionStateContract(options = {}) {
271
- const selectedIds = normalizeSelectedIds(options.selectedIds);
272
- const selectedCount = selectedIds.length;
273
- const totalCount = normalizeCount(options.totalCount);
274
- const eligibleCount = normalizeCount(options.eligibleCount) ?? totalCount;
275
- const selectionState = tableSelectionSummaryState(selectedCount, eligibleCount);
276
- const selectionSummary = formatTableSelectionSummary({
277
- eligibleCount,
278
- selectedCount,
279
- totalCount,
280
- });
281
- const actionContracts = (options.actions ?? []).map((action) => tableBatchActionContract(action, selectedCount));
282
- const availableActionIds = actionContracts
283
- .filter((action) => action.state === 'available')
284
- .map((action) => action.id);
285
- const disabledActionIds = actionContracts
286
- .filter((action) => action.state === 'disabled')
287
- .map((action) => action.id);
288
- const pendingActionIds = actionContracts
289
- .filter((action) => action.state === 'pending')
290
- .map((action) => action.id);
291
- const hiddenActionIds = actionContracts
292
- .filter((action) => action.state === 'hidden')
293
- .map((action) => action.id);
294
- return {
295
- selectionState,
296
- selectedIds,
297
- selectedCount,
298
- totalCount,
299
- eligibleCount,
300
- selectionSummary,
301
- actionContracts,
302
- availableActionIds,
303
- disabledActionIds,
304
- pendingActionIds,
305
- hiddenActionIds,
306
- attrs: {
307
- 'data-table-batch-action-contract': 'true',
308
- 'data-selection-state': selectionState,
309
- 'data-selected-ids': selectedIds.join(','),
310
- 'data-selected-count': selectedCount,
311
- 'data-total-count': totalCount,
312
- 'data-eligible-count': eligibleCount,
313
- 'data-selection-summary': selectionSummary,
314
- 'data-action-count': actionContracts.length,
315
- 'data-available-action-ids': availableActionIds.join(','),
316
- 'data-disabled-action-ids': disabledActionIds.join(','),
317
- 'data-pending-action-ids': pendingActionIds.join(','),
318
- 'data-hidden-action-ids': hiddenActionIds.join(','),
319
- },
320
- };
321
- }
322
- export function createFilterBarPlans(options) {
323
- const activeCount = options.filters.filter((filter) => filter.active === true).length;
324
- return {
325
- root: createUiDomPlan('div', 'filter-bar', {
326
- ...(options.className === undefined ? {} : { className: options.className }),
327
- ...(options.density === undefined ? {} : { density: options.density }),
328
- state: activeCount === 0 ? 'empty' : 'filtered',
329
- attrs: {
330
- id: options.id,
331
- role: 'list',
332
- 'aria-label': options.label ?? 'Filters',
333
- ...options.attrs,
334
- 'data-filter-count': options.filters.length,
335
- 'data-active-filter-count': activeCount,
336
- },
337
- }),
338
- chips: options.filters.map((filter) => createUiDomPlan('button', 'filter-chip', {
339
- ...(filter.disabled === undefined ? {} : { disabled: filter.disabled }),
340
- state: filter.disabled === true ? 'disabled' : filter.active === true ? 'active' : 'idle',
341
- attrs: {
342
- type: 'button',
343
- role: 'listitem',
344
- 'aria-pressed': filter.active === true ? 'true' : 'false',
345
- 'aria-label': filterChipLabel(filter),
346
- 'data-filter-id': filter.id,
347
- 'data-tone': filter.tone ?? 'neutral',
348
- 'data-removable': filter.removable === true ? 'true' : undefined,
349
- 'data-label': filter.label,
350
- 'data-value': filter.value,
351
- },
352
- })),
353
- clear: activeCount === 0
354
- ? undefined
355
- : createUiDomPlan('button', 'filter-clear', {
356
- attrs: {
357
- type: 'button',
358
- 'aria-label': options.clearLabel ?? 'Clear filters',
359
- 'data-count': activeCount,
360
- },
361
- }),
362
- };
363
- }
364
- export function createDataToolbarPlans(options = {}) {
365
- const filters = options.filters ?? [];
366
- const filterBar = createFilterBarPlans({
367
- filters,
368
- label: 'Active filters',
369
- ...(options.id === undefined ? {} : { id: `${options.id}-filters` }),
370
- ...(options.density === undefined ? {} : { density: options.density }),
371
- });
372
- return {
373
- root: createUiDomPlan('div', 'data-toolbar', {
374
- ...(options.className === undefined ? {} : { className: options.className }),
375
- ...(options.density === undefined ? {} : { density: options.density }),
376
- state: filters.some((filter) => filter.active === true) ? 'filtered' : 'idle',
377
- attrs: {
378
- id: options.id,
379
- role: 'toolbar',
380
- ...options.attrs,
381
- 'data-action-count': options.actions?.length ?? 0,
382
- 'data-filter-count': filters.length,
383
- 'data-selected-count': options.selectedCount,
384
- },
385
- }),
386
- search: options.searchValue === undefined && options.searchPlaceholder === undefined
387
- ? undefined
388
- : createUiDomPlan('input', 'data-toolbar-search', {
389
- attrs: {
390
- type: 'search',
391
- role: 'searchbox',
392
- value: options.searchValue,
393
- placeholder: options.searchPlaceholder,
394
- 'aria-label': options.searchLabel ?? 'Search data',
395
- },
396
- }),
397
- filterBar,
398
- summary: createUiDomPlan('output', 'data-toolbar-summary', {
399
- attrs: {
400
- 'aria-live': 'polite',
401
- 'data-summary': formatDataToolbarSummary(options),
402
- },
403
- }),
404
- actions: (options.actions ?? []).map((action) => createUiDomPlan('button', 'data-toolbar-action', {
405
- ...(action.disabled === undefined ? {} : { disabled: action.disabled }),
406
- state: action.disabled === true ? 'disabled' : action.pressed === true ? 'pressed' : 'idle',
407
- attrs: {
408
- type: 'button',
409
- 'aria-label': action.label,
410
- 'aria-pressed': action.pressed === undefined ? undefined : action.pressed ? 'true' : 'false',
411
- 'data-action-id': action.id,
412
- 'data-tone': action.tone ?? 'secondary',
413
- 'data-label': action.label,
414
- },
415
- })),
416
- };
417
- }
418
- export function formatDataToolbarSummary(options) {
419
- const total = normalizeCount(options.totalCount);
420
- const filtered = normalizeCount(options.filteredCount);
421
- const selected = normalizeCount(options.selectedCount);
422
- const visible = filtered ?? total;
423
- const parts = [];
424
- if (visible !== undefined &&
425
- total !== undefined &&
426
- filtered !== undefined &&
427
- filtered !== total) {
428
- parts.push(`${visible} of ${total}`);
429
- }
430
- else if (visible !== undefined) {
431
- parts.push(`${visible} rows`);
432
- }
433
- if (selected !== undefined && selected > 0)
434
- parts.push(`${selected} selected`);
435
- return parts.length === 0 ? 'No summary' : parts.join(' · ');
436
- }
437
- export function createEmptyPlan(options = {}) {
438
- const status = options.status ?? 'empty';
439
- return createUiDomPlan('div', 'empty', {
440
- ...(options.className === undefined ? {} : { className: options.className }),
441
- state: status,
442
- attrs: {
443
- id: options.id,
444
- role: 'status',
445
- 'aria-label': options.label ?? 'No data',
446
- 'aria-labelledby': options.titleId,
447
- 'aria-describedby': options.descriptionId,
448
- ...options.attrs,
449
- 'data-status': status,
450
- 'data-icon-ready': options.icon === false ? 'false' : 'true',
451
- 'data-actionable': options.action === undefined ? undefined : '',
452
- },
453
- });
454
- }
455
- export function createEmptyPlans(options = {}) {
456
- return {
457
- root: createEmptyPlan(options),
458
- ...(options.icon === false
459
- ? {}
460
- : {
461
- icon: createUiDomPlan('span', 'empty-icon', {
462
- attrs: { 'aria-hidden': 'true', 'data-status': options.status ?? 'empty' },
463
- }),
464
- }),
465
- ...(options.titleId === undefined
466
- ? {}
467
- : {
468
- title: createUiDomPlan('strong', 'empty-title', {
469
- attrs: { id: options.titleId },
470
- }),
471
- }),
472
- ...(options.descriptionId === undefined
473
- ? {}
474
- : {
475
- description: createUiDomPlan('p', 'empty-description', {
476
- attrs: { id: options.descriptionId },
477
- }),
478
- }),
479
- ...(options.action === undefined
480
- ? {}
481
- : { actionButton: createDataDisplayActionButton('empty', options.action) }),
482
- };
483
- }
484
- export function createResultPlans(options = {}) {
485
- const status = options.status ?? 'info';
486
- return {
487
- root: createUiDomPlan('section', 'result', {
488
- ...(options.className === undefined ? {} : { className: options.className }),
489
- state: status,
490
- attrs: {
491
- id: options.id,
492
- role: status === 'error' || status === 'warning' ? 'alert' : 'status',
493
- 'aria-labelledby': options.titleId,
494
- 'aria-describedby': options.descriptionId,
495
- ...options.attrs,
496
- 'data-status': status,
497
- 'data-icon-ready': options.icon === false ? 'false' : 'true',
498
- 'data-actionable': options.action === undefined ? undefined : '',
499
- 'data-dismissible': options.closeLabel === undefined ? undefined : '',
500
- },
501
- }),
502
- ...(options.icon === false
503
- ? {}
504
- : {
505
- icon: createUiDomPlan('span', 'result-icon', {
506
- attrs: { 'aria-hidden': 'true', 'data-status': status },
507
- }),
508
- }),
509
- ...(options.titleId === undefined
510
- ? {}
511
- : { title: createUiDomPlan('h3', 'result-title', { attrs: { id: options.titleId } }) }),
512
- ...(options.descriptionId === undefined
513
- ? {}
514
- : {
515
- description: createUiDomPlan('p', 'result-description', {
516
- attrs: { id: options.descriptionId },
517
- }),
518
- }),
519
- ...(options.action === undefined
520
- ? {}
521
- : { actionButton: createDataDisplayActionButton('result', options.action) }),
522
- ...(options.closeLabel === undefined
523
- ? {}
524
- : {
525
- closeButton: createUiDomPlan('button', 'result-close', {
526
- attrs: {
527
- type: 'button',
528
- 'aria-label': options.closeLabel,
529
- 'data-display-close': '',
530
- },
531
- }),
532
- }),
533
- };
534
- }
535
- export function createBadgePlan(options = {}) {
536
- const tone = options.tone ?? 'neutral';
537
- return createUiDomPlan('span', 'badge', {
538
- ...(options.className === undefined ? {} : { className: options.className }),
539
- state: tone,
540
- attrs: {
541
- 'aria-label': options.label,
542
- ...options.attrs,
543
- 'data-tone': tone,
544
- },
545
- });
546
- }
547
- export function createBadgeCountOverflowContract(options = {}) {
548
- const count = normalizeCount(options.count) ?? 0;
549
- const max = Math.max(1, normalizeCount(options.max) ?? 99);
550
- const dot = options.dot === true;
551
- const overflow = !dot && count > max;
552
- const state = dot
553
- ? 'dot'
554
- : count <= 0
555
- ? 'empty'
556
- : overflow
557
- ? 'overflow'
558
- : 'count';
559
- const displayValue = dot || count <= 0 ? '' : overflow ? `${max}+` : String(count);
560
- return {
561
- state,
562
- count,
563
- max,
564
- displayValue,
565
- overflow,
566
- dot,
567
- attrs: {
568
- 'data-badge-count-overflow-contract': 'true',
569
- 'data-badge-state': state,
570
- 'data-count': count,
571
- 'data-max': max,
572
- 'data-display-value': displayValue,
573
- 'data-overflow': overflow ? 'true' : undefined,
574
- 'data-dot': dot ? 'true' : undefined,
575
- 'aria-label': options.label ?? badgeCountOverflowLabel({ count, displayValue, dot, overflow }),
576
- },
577
- };
578
- }
579
- export function createTagPlans(options) {
580
- const variant = options.variant ?? 'tag';
581
- const interactive = options.selected !== undefined || options.disabled === true;
582
- const tag = createUiDomPlan(interactive ? 'button' : 'span', variant, {
583
- ...(options.className === undefined ? {} : { className: options.className }),
584
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
585
- state: options.disabled === true ? 'disabled' : options.selected === true ? 'selected' : 'idle',
586
- attrs: {
587
- id: options.id,
588
- type: interactive ? 'button' : undefined,
589
- role: interactive ? 'option' : undefined,
590
- 'aria-selected': options.selected === undefined ? undefined : String(options.selected),
591
- 'aria-label': tagLabel(options),
592
- ...options.attrs,
593
- 'data-tone': options.tone ?? 'neutral',
594
- 'data-variant': variant,
595
- 'data-label': options.label,
596
- 'data-value': options.value,
597
- 'data-removable': options.removable === true ? 'true' : undefined,
598
- 'data-count': options.count,
599
- },
600
- });
601
- return {
602
- root: tag,
603
- ...(options.icon === true
604
- ? {
605
- icon: createUiDomPlan('span', `${variant}-icon`, {
606
- attrs: {
607
- 'aria-hidden': 'true',
608
- 'data-tone': options.tone ?? 'neutral',
609
- },
610
- }),
611
- }
612
- : {}),
613
- label: createUiDomPlan('span', `${variant}-label`, {
614
- attrs: { 'data-label': options.label },
615
- }),
616
- ...(options.value === undefined
617
- ? {}
618
- : {
619
- value: createUiDomPlan('span', `${variant}-value`, {
620
- attrs: { 'data-value': options.value },
621
- }),
622
- }),
623
- ...(options.count === undefined
624
- ? {}
625
- : {
626
- count: createUiDomPlan('span', `${variant}-count`, {
627
- attrs: { 'aria-label': `${options.count} items`, 'data-count': options.count },
628
- }),
629
- }),
630
- ...(options.removable === true
631
- ? {
632
- removeButton: createUiDomPlan('button', `${variant}-remove`, {
633
- attrs: {
634
- type: 'button',
635
- 'aria-label': `Remove ${options.label}`,
636
- 'data-remove-id': options.id,
637
- },
638
- }),
639
- }
640
- : {}),
641
- };
642
- }
643
- export function createChipPlans(options) {
644
- return createTagPlans({ ...options, variant: 'chip' });
645
- }
646
- export function createNotePlans(options = {}) {
647
- return createNoteLikePlans('note', options);
648
- }
649
- export function createCalloutPlans(options = {}) {
650
- return createNoteLikePlans('callout', options);
651
- }
652
- export function createNoteRichContentStateContract(options) {
653
- const density = options.density ?? 'comfortable';
654
- const blocks = (options.blocks ?? []).map((block) => ({
655
- ...block,
656
- attrs: {
657
- 'data-note-rich-content-block': 'true',
658
- 'data-block-id': block.id,
659
- 'data-block-kind': block.kind,
660
- },
661
- }));
662
- const blockIds = blocks.map((block) => block.id);
663
- const state = blocks.length === 0 ? 'empty' : 'ready';
664
- const actionState = options.actionId === undefined
665
- ? 'hidden'
666
- : options.actionDisabled === true
667
- ? 'disabled'
668
- : 'ready';
669
- const closeState = options.closeAvailable !== true
670
- ? 'hidden'
671
- : options.closeDisabled === true
672
- ? 'disabled'
673
- : 'ready';
674
- return {
675
- kind: options.kind,
676
- density,
677
- state,
678
- actionState,
679
- closeState,
680
- blockCount: blocks.length,
681
- blockIds,
682
- blocks,
683
- attrs: {
684
- 'data-note-rich-content-contract': 'true',
685
- 'data-note-kind': options.kind,
686
- 'data-content-density': density,
687
- 'data-content-state': state,
688
- 'data-block-count': blocks.length,
689
- 'data-block-ids': blockIds.join(','),
690
- 'data-action-state': actionState,
691
- 'data-close-state': closeState,
692
- },
693
- contentAttrs: {
694
- 'data-note-rich-content': 'true',
695
- 'data-content-density': density,
696
- 'data-content-state': state,
697
- },
698
- actionAttrs: {
699
- type: 'button',
700
- 'data-action-id': options.actionId,
701
- 'data-action-state': actionState,
702
- hidden: actionState === 'hidden' ? true : undefined,
703
- disabled: actionState === 'disabled' ? true : undefined,
704
- 'aria-disabled': actionState === 'disabled' ? 'true' : undefined,
705
- },
706
- closeAttrs: {
707
- type: 'button',
708
- 'data-close-state': closeState,
709
- hidden: closeState === 'hidden' ? true : undefined,
710
- disabled: closeState === 'disabled' ? true : undefined,
711
- 'aria-disabled': closeState === 'disabled' ? 'true' : undefined,
712
- },
713
- };
714
- }
715
- export function createPanelPlans(options = {}) {
716
- return createPanelLikePlans('panel', options);
717
- }
718
- export function createCardPlans(options = {}) {
719
- return createPanelLikePlans('card', options);
720
- }
721
- export function createPanelCardMediaActionStateContract(options) {
722
- const mediaState = options.mediaId === undefined ? 'empty' : 'ready';
723
- const mediaPlacement = options.mediaPlacement ?? 'top';
724
- const selectionState = options.selected === true ? 'selected' : 'idle';
725
- const selectedEmphasis = selectionState === 'selected' && options.selectedEmphasis === true;
726
- const hiddenActionIds = new Set(options.hiddenActionIds ?? []);
727
- const actionContracts = (options.actions ?? []).map((action) => {
728
- const state = hiddenActionIds.has(action.id)
729
- ? 'hidden'
730
- : action.disabled === true
731
- ? 'disabled'
732
- : 'ready';
733
- return {
734
- id: action.id,
735
- state,
736
- attrs: {
737
- type: 'button',
738
- 'data-action-id': action.id,
739
- 'data-action-state': state,
740
- hidden: state === 'hidden' ? true : undefined,
741
- disabled: state === 'disabled' ? true : undefined,
742
- 'aria-disabled': state === 'disabled' ? 'true' : undefined,
743
- },
744
- };
745
- });
746
- const readyActionIds = actionContracts
747
- .filter((action) => action.state === 'ready')
748
- .map((action) => action.id);
749
- const disabledActionIds = actionContracts
750
- .filter((action) => action.state === 'disabled')
751
- .map((action) => action.id);
752
- const resolvedHiddenActionIds = actionContracts
753
- .filter((action) => action.state === 'hidden')
754
- .map((action) => action.id);
755
- return {
756
- kind: options.kind,
757
- mediaState,
758
- mediaPlacement,
759
- selectionState,
760
- selectedEmphasis,
761
- actionCount: actionContracts.length,
762
- readyActionIds,
763
- disabledActionIds,
764
- hiddenActionIds: resolvedHiddenActionIds,
765
- attrs: {
766
- 'data-panel-card-media-action-contract': 'true',
767
- 'data-panel-card-kind': options.kind,
768
- 'data-media-state': mediaState,
769
- 'data-media-placement': mediaPlacement,
770
- 'data-selection-state': selectionState,
771
- 'data-selected-emphasis': selectedEmphasis ? 'true' : undefined,
772
- 'data-action-count': actionContracts.length,
773
- 'data-ready-action-ids': readyActionIds.join(','),
774
- 'data-disabled-action-ids': disabledActionIds.join(','),
775
- 'data-hidden-action-ids': resolvedHiddenActionIds.join(','),
776
- },
777
- mediaAttrs: {
778
- id: options.mediaId,
779
- role: mediaState === 'ready' ? 'img' : undefined,
780
- 'aria-label': mediaState === 'ready' ? options.mediaLabel : undefined,
781
- 'aria-hidden': mediaState === 'empty' ? 'true' : undefined,
782
- hidden: mediaState === 'empty' ? true : undefined,
783
- 'data-media-state': mediaState,
784
- 'data-media-placement': mediaPlacement,
785
- },
786
- actionContracts,
787
- };
788
- }
789
- export function createPageHeaderPlans(options) {
790
- const actions = (options.actions ?? []).map((action) => createDataDisplayActionButton('page-header', action));
791
- return {
792
- root: createUiDomPlan('header', 'product-page-header', {
793
- ...(options.className === undefined ? {} : { className: options.className }),
794
- ...(options.density === undefined ? {} : { density: options.density }),
795
- attrs: {
796
- id: options.id,
797
- 'aria-labelledby': options.titleId,
798
- 'aria-describedby': options.subtitleId,
799
- ...options.attrs,
800
- 'data-has-breadcrumb': options.breadcrumbLabel === undefined ? undefined : 'true',
801
- 'data-action-count': actions.length,
802
- 'data-tabs-ready': options.tabsId === undefined ? undefined : 'true',
803
- },
804
- }),
805
- ...(options.breadcrumbLabel === undefined
806
- ? {}
807
- : {
808
- breadcrumbSlot: createUiDomPlan('nav', 'page-header-breadcrumb', {
809
- attrs: { 'aria-label': options.breadcrumbLabel, 'data-slot': 'breadcrumb' },
810
- }),
811
- }),
812
- content: createUiDomPlan('div', 'page-header-content'),
813
- title: createUiDomPlan('h1', 'page-header-title', { attrs: { id: options.titleId } }),
814
- ...(options.subtitleId === undefined
815
- ? {}
816
- : {
817
- subtitle: createUiDomPlan('p', 'page-header-subtitle', {
818
- attrs: { id: options.subtitleId },
819
- }),
820
- }),
821
- ...(actions.length === 0
822
- ? {}
823
- : {
824
- actionsSlot: createUiDomPlan('div', 'page-header-actions', {
825
- attrs: { role: 'toolbar', 'data-slot': 'actions', 'data-action-count': actions.length },
826
- }),
827
- }),
828
- actions,
829
- ...(options.tabsId === undefined
830
- ? {}
831
- : {
832
- tabsSlot: createUiDomPlan('div', 'page-header-tabs', {
833
- attrs: { id: `${options.tabsId}-slot`, 'data-slot': 'tabs' },
834
- }),
835
- }),
836
- };
837
- }
838
- export function createStatCardPlans(options) {
839
- const tone = options.tone ?? 'neutral';
840
- const loading = options.loading === true;
841
- return {
842
- root: createUiDomPlan('article', 'stat-card', {
843
- ...(options.className === undefined ? {} : { className: options.className }),
844
- loading,
845
- attrs: {
846
- id: options.id,
847
- 'aria-labelledby': options.titleId,
848
- 'aria-describedby': options.descriptionId,
849
- ...options.attrs,
850
- 'data-tone': tone,
851
- 'data-value': options.value,
852
- 'data-unit': options.unit,
853
- 'data-trend-direction': options.trendDirection,
854
- 'data-actionable': options.action === undefined ? undefined : '',
855
- },
856
- }),
857
- header: createUiDomPlan('header', 'stat-card-header'),
858
- title: createUiDomPlan('h3', 'stat-card-title', { attrs: { id: options.titleId } }),
859
- value: createUiDomPlan('output', 'stat-card-value', {
860
- attrs: {
861
- 'aria-live': 'polite',
862
- 'data-value': loading ? undefined : options.value,
863
- },
864
- }),
865
- ...(options.unit === undefined
866
- ? {}
867
- : {
868
- unit: createUiDomPlan('span', 'stat-card-unit', { attrs: { 'data-unit': options.unit } }),
869
- }),
870
- ...(options.trend === undefined
871
- ? {}
872
- : {
873
- trend: createUiDomPlan('span', 'stat-card-trend', {
874
- attrs: {
875
- 'aria-label': options.trend,
876
- 'data-trend': options.trend,
877
- 'data-direction': options.trendDirection ?? 'flat',
878
- },
879
- }),
880
- }),
881
- ...(options.descriptionId === undefined
882
- ? {}
883
- : {
884
- description: createUiDomPlan('p', 'stat-card-description', {
885
- attrs: { id: options.descriptionId },
886
- }),
887
- }),
888
- ...(options.action === undefined
889
- ? {}
890
- : { actionButton: createDataDisplayActionButton('stat-card', options.action) }),
891
- };
892
- }
893
- export function createTablePanelPlans(options) {
894
- const actions = (options.actions ?? []).map((action) => createDataDisplayActionButton('table-panel', action));
895
- const emptyOptions = normalizeEmptyOptions(options.empty);
896
- const table = createTablePlans({
897
- ...(options.id === undefined ? {} : { id: `${options.id}-table` }),
898
- columns: options.columns,
899
- rows: options.rows,
900
- ...(options.captionId === undefined ? {} : { captionId: options.captionId }),
901
- ...(options.selectable === undefined ? {} : { selectable: options.selectable }),
902
- ...(options.density === undefined ? {} : { density: options.density }),
903
- ...(options.stickyHeader === undefined ? {} : { stickyHeader: options.stickyHeader }),
904
- ...(options.tableAttrs === undefined ? {} : { attrs: options.tableAttrs }),
905
- });
906
- return {
907
- root: createUiDomPlan('section', 'table-panel', {
908
- ...(options.className === undefined ? {} : { className: options.className }),
909
- ...(options.density === undefined ? {} : { density: options.density }),
910
- ...(options.loading === undefined ? {} : { loading: options.loading }),
911
- attrs: {
912
- id: options.id,
913
- 'aria-labelledby': options.titleId,
914
- 'aria-describedby': options.descriptionId,
915
- ...options.attrs,
916
- 'data-row-count': options.rows.length,
917
- 'data-column-count': options.columns.filter((column) => column.hidden !== true).length,
918
- 'data-action-count': actions.length,
919
- 'data-has-toolbar': options.toolbarLabel === undefined ? undefined : 'true',
920
- 'data-empty': emptyOptions === undefined ? undefined : 'true',
921
- },
922
- }),
923
- ...(options.titleId === undefined && options.descriptionId === undefined && actions.length === 0
924
- ? {}
925
- : { header: createUiDomPlan('header', 'table-panel-header') }),
926
- ...(options.titleId === undefined
927
- ? {}
928
- : { title: createUiDomPlan('h3', 'table-panel-title', { attrs: { id: options.titleId } }) }),
929
- ...(options.descriptionId === undefined
930
- ? {}
931
- : {
932
- description: createUiDomPlan('p', 'table-panel-description', {
933
- attrs: { id: options.descriptionId },
934
- }),
935
- }),
936
- ...(options.toolbarLabel === undefined
937
- ? {}
938
- : {
939
- toolbarSlot: createUiDomPlan('div', 'table-panel-toolbar', {
940
- attrs: {
941
- role: 'toolbar',
942
- 'aria-label': options.toolbarLabel,
943
- 'data-slot': 'toolbar',
944
- },
945
- }),
946
- }),
947
- actions,
948
- body: createUiDomPlan('div', 'table-panel-body'),
949
- scroller: createUiDomPlan('div', 'table-panel-scroller'),
950
- table,
951
- ...(emptyOptions === undefined ? {} : { empty: createEmptyPlan(emptyOptions) }),
952
- };
953
- }
954
- export function createProgressPlan(options) {
955
- const max = options.max ?? 100;
956
- const value = Math.min(Math.max(options.value, 0), max);
957
- return createUiDomPlan('div', 'progress', {
958
- ...(options.className === undefined ? {} : { className: options.className }),
959
- state: options.status ?? 'idle',
960
- attrs: {
961
- role: 'progressbar',
962
- 'aria-label': options.label,
963
- 'aria-valuemin': 0,
964
- 'aria-valuemax': max,
965
- 'aria-valuenow': value,
966
- ...options.attrs,
967
- 'data-status': options.status,
968
- style: `--kui-progress-value: ${(value / max) * 100}%;${styleSuffix(options.attrs?.style)}`,
969
- },
970
- });
971
- }
972
- export function createTypographyPlan(options = {}) {
973
- const variant = options.variant ?? 'body';
974
- const tag = options.as ?? typographyTag(variant);
975
- return createUiDomPlan(tag, 'typography', {
976
- ...(options.className === undefined ? {} : { className: options.className }),
977
- state: variant,
978
- attrs: {
979
- id: options.id,
980
- href: tag === 'a' ? options.href : undefined,
981
- 'aria-label': options.label,
982
- ...options.attrs,
983
- 'data-variant': variant,
984
- 'data-truncate': options.truncate === true ? 'true' : undefined,
985
- },
986
- });
987
- }
988
- export function createTypographyRichContentStateContract(options = {}) {
989
- const source = options.source ?? 'rich-text';
990
- const density = options.density ?? 'comfortable';
991
- const blocks = (options.blocks ?? []).map((block) => ({
992
- ...block,
993
- attrs: {
994
- 'data-rich-content-block': 'true',
995
- 'data-block-id': block.id,
996
- 'data-block-kind': block.kind,
997
- },
998
- }));
999
- const blockIds = blocks.map((block) => block.id);
1000
- const headingCount = blocks.filter((block) => block.kind === 'heading').length;
1001
- const state = blocks.length === 0 ? 'empty' : 'ready';
1002
- return {
1003
- source,
1004
- density,
1005
- state,
1006
- blockCount: blocks.length,
1007
- headingCount,
1008
- blockIds,
1009
- blocks,
1010
- attrs: {
1011
- 'data-typography-rich-content-contract': 'true',
1012
- 'data-content-source': source,
1013
- 'data-content-density': density,
1014
- 'data-content-state': state,
1015
- 'data-block-count': blocks.length,
1016
- 'data-heading-count': headingCount,
1017
- 'data-block-ids': blockIds.join(','),
1018
- 'aria-label': options.label,
1019
- },
1020
- };
1021
- }
1022
- export function createDividerPlans(options = {}) {
1023
- const orientation = options.orientation ?? 'horizontal';
1024
- return {
1025
- root: createUiDomPlan('div', 'divider', {
1026
- ...(options.className === undefined ? {} : { className: options.className }),
1027
- state: options.label === undefined ? 'line' : 'labelled',
1028
- attrs: {
1029
- id: options.id,
1030
- role: 'separator',
1031
- 'aria-orientation': orientation,
1032
- 'aria-labelledby': options.labelId,
1033
- ...options.attrs,
1034
- 'data-orientation': orientation,
1035
- 'data-inset': dividerInset(options.inset),
1036
- 'data-label': options.label,
1037
- },
1038
- }),
1039
- ...(options.label === undefined
1040
- ? {}
1041
- : {
1042
- label: createUiDomPlan('span', 'divider-label', {
1043
- attrs: {
1044
- id: options.labelId,
1045
- 'data-label': options.label,
1046
- },
1047
- }),
1048
- }),
1049
- };
1050
- }
1051
- export function createDividerSectionHeadingStateContract(options) {
1052
- const state = options.current === true ? 'current' : 'idle';
1053
- const level = options.level ?? 2;
1054
- return {
1055
- state,
1056
- headingId: options.headingId,
1057
- sectionId: options.sectionId,
1058
- level,
1059
- attrs: {
1060
- 'data-divider-section-heading-contract': 'true',
1061
- 'data-section-heading-state': state,
1062
- 'data-section-id': options.sectionId,
1063
- 'data-heading-id': options.headingId,
1064
- 'data-heading-level': level,
1065
- 'aria-labelledby': options.headingId,
1066
- 'aria-describedby': options.descriptionId,
1067
- },
1068
- headingAttrs: {
1069
- id: options.headingId,
1070
- role: 'heading',
1071
- 'aria-level': level,
1072
- 'aria-describedby': options.descriptionId,
1073
- 'data-section-heading-state': state,
1074
- },
1075
- sectionAttrs: {
1076
- id: options.sectionId,
1077
- 'aria-labelledby': options.headingId,
1078
- 'aria-describedby': options.descriptionId,
1079
- 'data-section-heading-state': state,
1080
- },
1081
- };
1082
- }
1083
- export function createAvatarPlans(options) {
1084
- const size = options.size ?? 'md';
1085
- const initials = normalizeInitials(options.initials ?? initialsFromName(options.name));
1086
- return {
1087
- root: createUiDomPlan('span', 'avatar', {
1088
- ...(options.className === undefined ? {} : { className: options.className }),
1089
- state: options.src === undefined ? 'fallback' : 'image',
1090
- attrs: {
1091
- id: options.id,
1092
- role: 'img',
1093
- 'aria-label': options.name,
1094
- ...options.attrs,
1095
- 'data-size': size,
1096
- 'data-name': options.name,
1097
- 'data-initials': initials,
1098
- 'data-status': options.status,
1099
- },
1100
- }),
1101
- ...(options.src === undefined
1102
- ? {}
1103
- : {
1104
- image: createUiDomPlan('img', 'avatar-image', {
1105
- attrs: {
1106
- src: options.src,
1107
- alt: options.alt ?? '',
1108
- },
1109
- }),
1110
- }),
1111
- fallback: createUiDomPlan('span', 'avatar-fallback', {
1112
- attrs: {
1113
- 'aria-hidden': options.src === undefined ? undefined : 'true',
1114
- 'data-initials': initials,
1115
- },
1116
- }),
1117
- ...(options.status === undefined
1118
- ? {}
1119
- : {
1120
- status: createUiDomPlan('span', 'avatar-status', {
1121
- attrs: {
1122
- 'aria-label': `${options.name} is ${options.status}`,
1123
- 'data-status': options.status,
1124
- },
1125
- }),
1126
- }),
1127
- };
1128
- }
1129
- export function createAvatarGroupPlans(options) {
1130
- const maxVisible = Math.max(0, Math.trunc(options.maxVisible ?? options.avatars.length));
1131
- const visible = options.avatars.slice(0, maxVisible);
1132
- const overflowCount = Math.max(0, options.avatars.length - visible.length);
1133
- return {
1134
- root: createUiDomPlan('div', 'avatar-group', {
1135
- ...(options.className === undefined ? {} : { className: options.className }),
1136
- attrs: {
1137
- id: options.id,
1138
- role: 'group',
1139
- 'aria-label': options.label ?? `${options.avatars.length} people`,
1140
- ...options.attrs,
1141
- 'data-count': options.avatars.length,
1142
- 'data-visible-count': visible.length,
1143
- 'data-overflow-count': overflowCount,
1144
- 'data-size': options.size ?? 'md',
1145
- },
1146
- }),
1147
- avatars: visible.map((avatar) => createAvatarPlans({ ...avatar, size: avatar.size ?? options.size ?? 'md' })),
1148
- ...(overflowCount === 0
1149
- ? {}
1150
- : {
1151
- overflow: createUiDomPlan('span', 'avatar-overflow', {
1152
- attrs: {
1153
- role: 'img',
1154
- 'aria-label': `${overflowCount} more`,
1155
- 'data-overflow-count': overflowCount,
1156
- },
1157
- }),
1158
- }),
1159
- };
1160
- }
1161
- export function createAvatarImagePresenceStateContract(options) {
1162
- const imageState = options.imageState ?? avatarImageState(options);
1163
- const presenceSource = options.presenceSource ?? 'unknown';
1164
- const cacheState = options.cacheState ?? (options.cacheKey === undefined ? 'unbound' : 'miss');
1165
- const fallbackInitials = normalizeInitials(options.fallbackInitials ?? initialsFromName(options.name));
1166
- return {
1167
- imageState,
1168
- presenceSource,
1169
- cacheState,
1170
- fallbackInitials,
1171
- attrs: {
1172
- 'data-avatar-image-presence-contract': 'true',
1173
- 'data-image-state': imageState,
1174
- 'data-image-src': options.src,
1175
- 'data-image-error': options.imageError,
1176
- 'data-presence-status': options.status,
1177
- 'data-presence-source': presenceSource,
1178
- 'data-presence-updated-at': options.presenceUpdatedAt,
1179
- 'data-cache-state': cacheState,
1180
- 'data-cache-key': options.cacheKey,
1181
- 'data-cached-at': options.cachedAt,
1182
- 'data-fallback-initials': fallbackInitials,
1183
- 'aria-busy': imageState === 'loading' ? 'true' : undefined,
1184
- },
1185
- };
1186
- }
1187
- export function createKbdPlan(options) {
1188
- return createUiDomPlan('kbd', 'kbd', {
1189
- ...(options.className === undefined ? {} : { className: options.className }),
1190
- attrs: {
1191
- 'aria-label': options.label ?? options.key,
1192
- ...options.attrs,
1193
- 'data-key': options.key,
1194
- },
1195
- });
1196
- }
1197
- export function createShortcutPlans(options) {
1198
- const label = options.label ?? formatShortcutLabel(options.keys);
1199
- return {
1200
- root: createUiDomPlan('span', 'shortcut', {
1201
- ...(options.className === undefined ? {} : { className: options.className }),
1202
- attrs: {
1203
- role: 'img',
1204
- 'aria-label': label,
1205
- ...options.attrs,
1206
- 'data-shortcut': label,
1207
- 'data-key-count': options.keys.length,
1208
- },
1209
- }),
1210
- keys: options.keys.map((key) => createKbdPlan({ key })),
1211
- separators: options.keys.slice(1).map((_, index) => createUiDomPlan('span', 'shortcut-separator', {
1212
- attrs: {
1213
- 'aria-hidden': 'true',
1214
- 'data-index': index,
1215
- },
1216
- })),
1217
- };
1218
- }
1219
- export function createShortcutDisplayStateContract(options) {
1220
- const platform = options.platform ?? 'generic';
1221
- const keys = options.keys.map((key) => key.trim()).filter((key) => key !== '');
1222
- const keyContracts = keys.map((key) => {
1223
- const alias = options.modifierAliases?.[key]?.trim() ?? '';
1224
- const displayKey = alias === '' ? key : alias;
1225
- const aliased = displayKey !== key;
1226
- return {
1227
- key,
1228
- displayKey,
1229
- aliased,
1230
- attrs: {
1231
- 'data-key': key,
1232
- 'data-display-key': displayKey,
1233
- 'data-modifier-alias': aliased ? 'true' : undefined,
1234
- },
1235
- };
1236
- });
1237
- const displayKeys = keyContracts.map((key) => key.displayKey);
1238
- const aliasedKeys = keyContracts.filter((key) => key.aliased).map((key) => key.key);
1239
- const state = keys.length === 0 ? 'empty' : aliasedKeys.length === 0 ? 'ready' : 'aliased';
1240
- const label = options.label?.trim() || formatShortcutLabel(displayKeys);
1241
- return {
1242
- platform,
1243
- state,
1244
- keys,
1245
- displayKeys,
1246
- aliasedKeys,
1247
- label,
1248
- keyContracts,
1249
- attrs: {
1250
- 'data-shortcut-display-contract': 'true',
1251
- 'data-shortcut-platform': platform,
1252
- 'data-shortcut-display-state': state,
1253
- 'data-shortcut-keys': keys.join(','),
1254
- 'data-shortcut-display-keys': displayKeys.join(','),
1255
- 'data-aliased-keys': aliasedKeys.join(','),
1256
- 'data-key-count': keys.length,
1257
- 'data-shortcut-label': label,
1258
- 'aria-label': label,
1259
- },
1260
- };
1261
- }
1262
- export function createPaginationPlans(options) {
1263
- const pageCount = getPaginationPageCount(options);
1264
- const page = clampPage(options.page, pageCount);
1265
- const items = createPaginationItems({
1266
- page,
1267
- pageCount,
1268
- ...(options.siblingCount === undefined ? {} : { siblingCount: options.siblingCount }),
1269
- ...(options.boundaryCount === undefined ? {} : { boundaryCount: options.boundaryCount }),
1270
- });
1271
- return {
1272
- root: createUiDomPlan('nav', 'pagination', {
1273
- ...(options.className === undefined ? {} : { className: options.className }),
1274
- attrs: {
1275
- id: options.id,
1276
- 'aria-label': options.label ?? 'Pagination',
1277
- ...options.attrs,
1278
- 'data-page': page,
1279
- 'data-page-count': pageCount,
1280
- 'data-total-items': normalizeCount(options.totalItems),
1281
- },
1282
- }),
1283
- list: createUiDomPlan('ol', 'pagination-list', { attrs: { role: 'list' } }),
1284
- previous: createPaginationControl('previous', page <= 1 ? 1 : page - 1, page <= 1, options.previousLabel ?? 'Previous page'),
1285
- pages: items.map((item, index) => createPaginationItemPlan(item, index)),
1286
- next: createPaginationControl('next', page >= pageCount ? pageCount : page + 1, page >= pageCount, options.nextLabel ?? 'Next page'),
1287
- summary: createUiDomPlan('output', 'pagination-summary', {
1288
- attrs: {
1289
- 'aria-live': 'polite',
1290
- 'data-summary': formatPaginationSummary({
1291
- ...options,
1292
- page,
1293
- pageCount,
1294
- }),
1295
- },
1296
- }),
1297
- };
1298
- }
1299
- export function createPaginationItems(options) {
1300
- const pageCount = Math.max(1, Math.trunc(options.pageCount ?? 1));
1301
- const page = clampPage(options.page, pageCount);
1302
- const siblingCount = Math.max(0, Math.trunc(options.siblingCount ?? 1));
1303
- const boundaryCount = Math.max(0, Math.trunc(options.boundaryCount ?? 1));
1304
- const pages = new Set();
1305
- for (let value = 1; value <= Math.min(boundaryCount, pageCount); value += 1)
1306
- pages.add(value);
1307
- for (let value = Math.max(1, pageCount - boundaryCount + 1); value <= pageCount; value += 1) {
1308
- pages.add(value);
1309
- }
1310
- for (let value = Math.max(1, page - siblingCount); value <= Math.min(pageCount, page + siblingCount); value += 1) {
1311
- pages.add(value);
1312
- }
1313
- const sorted = [...pages].sort((left, right) => left - right);
1314
- const items = [];
1315
- let previous = 0;
1316
- for (const value of sorted) {
1317
- if (previous > 0 && value - previous > 1)
1318
- items.push({ kind: 'ellipsis' });
1319
- items.push({ kind: 'page', page: value, current: value === page });
1320
- previous = value;
1321
- }
1322
- return items;
1323
- }
1324
- export function formatPaginationSummary(options) {
1325
- const pageCount = getPaginationPageCount(options);
1326
- const page = clampPage(options.page, pageCount);
1327
- const total = normalizeCount(options.totalItems);
1328
- const pageSize = normalizeCount(options.pageSize);
1329
- if (total !== undefined && pageSize !== undefined && total > 0) {
1330
- const start = (page - 1) * pageSize + 1;
1331
- const end = Math.min(total, page * pageSize);
1332
- return `${start}-${end} of ${total}`;
1333
- }
1334
- return `Page ${page} of ${pageCount}`;
1335
- }
1336
- export function createListPlans(options) {
1337
- const selectionMode = options.selectionMode ?? 'none';
1338
- return {
1339
- root: createUiDomPlan(options.ordered === true ? 'ol' : 'ul', 'list', {
1340
- ...(options.className === undefined ? {} : { className: options.className }),
1341
- ...(options.density === undefined ? {} : { density: options.density }),
1342
- attrs: {
1343
- id: options.id,
1344
- role: selectionMode === 'none' ? 'list' : 'listbox',
1345
- 'aria-label': options.label,
1346
- 'aria-multiselectable': selectionMode === 'multiple' ? 'true' : undefined,
1347
- ...options.attrs,
1348
- 'data-count': options.items.length,
1349
- 'data-selection-mode': selectionMode,
1350
- },
1351
- }),
1352
- items: options.items.map((item) => createUiDomPlan('li', 'list-item', {
1353
- ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
1354
- state: item.disabled === true ? 'disabled' : item.selected === true ? 'selected' : 'idle',
1355
- attrs: {
1356
- role: selectionMode === 'none' ? 'listitem' : 'option',
1357
- 'aria-selected': selectionMode === 'none' ? undefined : String(item.selected === true),
1358
- 'data-item-id': item.id,
1359
- 'data-label': item.label,
1360
- 'data-description': item.description,
1361
- 'data-meta': item.meta,
1362
- 'data-actionable': item.actionLabel === undefined ? undefined : '',
1363
- },
1364
- })),
1365
- icons: options.items.map((item) => item.icon === true
1366
- ? createUiDomPlan('span', 'list-item-icon', {
1367
- attrs: { 'aria-hidden': 'true', 'data-item-id': item.id },
1368
- })
1369
- : undefined),
1370
- labels: options.items.map((item) => createUiDomPlan('span', 'list-item-label', {
1371
- attrs: { 'data-label': item.label ?? item.id },
1372
- })),
1373
- descriptions: options.items.map((item) => item.description === undefined
1374
- ? undefined
1375
- : createUiDomPlan('p', 'list-item-description', {
1376
- attrs: { 'data-description': item.description },
1377
- })),
1378
- metas: options.items.map((item) => item.meta === undefined
1379
- ? undefined
1380
- : createUiDomPlan('span', 'list-item-meta', {
1381
- attrs: { 'data-meta': item.meta },
1382
- })),
1383
- actions: options.items.map((item) => item.actionLabel === undefined
1384
- ? undefined
1385
- : createUiDomPlan('button', 'list-item-action', {
1386
- attrs: {
1387
- type: 'button',
1388
- 'aria-label': item.actionLabel,
1389
- 'data-item-id': item.id,
1390
- },
1391
- })),
1392
- };
1393
- }
1394
- export function createListGroupedItemActionStateContract(options) {
1395
- const itemById = new Map(options.items.map((item) => [item.id, item]));
1396
- const groupedIds = new Set();
1397
- const groups = (options.groups ?? []).map((group) => {
1398
- const itemIds = group.itemIds.filter((id) => {
1399
- if (!itemById.has(id) || groupedIds.has(id))
1400
- return false;
1401
- groupedIds.add(id);
1402
- return true;
1403
- });
1404
- const selectedCount = itemIds.filter((id) => itemById.get(id)?.selected === true).length;
1405
- const selectionState = itemIds.length === 0
1406
- ? 'empty'
1407
- : selectedCount === 0
1408
- ? 'none'
1409
- : selectedCount === itemIds.length
1410
- ? 'all'
1411
- : 'partial';
1412
- return {
1413
- id: group.id,
1414
- label: group.label,
1415
- itemIds,
1416
- itemCount: itemIds.length,
1417
- selectedCount,
1418
- selectionState,
1419
- attrs: {
1420
- role: 'group',
1421
- 'aria-labelledby': group.headingId,
1422
- 'data-group-id': group.id,
1423
- 'data-group-selection-state': selectionState,
1424
- 'data-group-item-count': itemIds.length,
1425
- 'data-group-selected-count': selectedCount,
1426
- },
1427
- headingAttrs: {
1428
- id: group.headingId,
1429
- role: 'heading',
1430
- 'aria-level': 3,
1431
- 'data-group-id': group.id,
1432
- 'data-group-label': group.label,
1433
- },
1434
- };
1435
- });
1436
- const groupByItemId = new Map(groups.flatMap((group) => group.itemIds.map((itemId) => [itemId, group.id])));
1437
- const disabledSet = new Set(options.actionDisabledIds ?? []);
1438
- const hiddenSet = new Set(options.actionHiddenIds ?? []);
1439
- const items = options.items.map((item) => {
1440
- const actionable = item.actionLabel !== undefined;
1441
- const actionState = !actionable || hiddenSet.has(item.id)
1442
- ? 'hidden'
1443
- : item.disabled === true || disabledSet.has(item.id)
1444
- ? 'disabled'
1445
- : 'ready';
1446
- return {
1447
- id: item.id,
1448
- groupId: groupByItemId.get(item.id) ?? '',
1449
- actionState,
1450
- attrs: {
1451
- 'data-item-id': item.id,
1452
- 'data-group-id': groupByItemId.get(item.id),
1453
- 'data-action-state': actionState,
1454
- },
1455
- actionAttrs: {
1456
- type: 'button',
1457
- 'data-item-id': item.id,
1458
- 'data-action-state': actionState,
1459
- hidden: actionState === 'hidden' ? true : undefined,
1460
- disabled: actionState === 'disabled' ? true : undefined,
1461
- 'aria-disabled': actionState === 'disabled' ? 'true' : undefined,
1462
- },
1463
- };
1464
- });
1465
- const actionableItems = items.filter((item) => itemById.get(item.id)?.actionLabel !== undefined);
1466
- const readyActionIds = actionableItems
1467
- .filter((item) => item.actionState === 'ready')
1468
- .map((item) => item.id);
1469
- const disabledActionIds = actionableItems
1470
- .filter((item) => item.actionState === 'disabled')
1471
- .map((item) => item.id);
1472
- const hiddenActionIds = actionableItems
1473
- .filter((item) => item.actionState === 'hidden')
1474
- .map((item) => item.id);
1475
- const state = options.items.length === 0
1476
- ? 'empty'
1477
- : groups.length === 0
1478
- ? 'plain'
1479
- : groupedIds.size === options.items.length
1480
- ? 'grouped'
1481
- : 'partial';
1482
- return {
1483
- state,
1484
- itemCount: options.items.length,
1485
- groupCount: groups.length,
1486
- groupedItemCount: groupedIds.size,
1487
- actionCount: actionableItems.length,
1488
- groupIds: groups.map((group) => group.id),
1489
- readyActionIds,
1490
- disabledActionIds,
1491
- hiddenActionIds,
1492
- attrs: {
1493
- 'data-list-grouped-item-action-contract': 'true',
1494
- 'data-list-grouped-state': state,
1495
- 'data-group-count': groups.length,
1496
- 'data-grouped-item-count': groupedIds.size,
1497
- 'data-group-ids': groups.map((group) => group.id).join(','),
1498
- 'data-action-count': actionableItems.length,
1499
- 'data-ready-action-ids': readyActionIds.join(','),
1500
- 'data-disabled-action-ids': disabledActionIds.join(','),
1501
- 'data-hidden-action-ids': hiddenActionIds.join(','),
1502
- },
1503
- groups,
1504
- items,
1505
- };
1506
- }
1507
- export function createCarouselPlans(options) {
1508
- const slideCount = options.slides.length;
1509
- const currentIndex = Math.max(0, options.slides.findIndex((slide) => slide.id === options.currentSlideId));
1510
- const currentSlide = options.slides[currentIndex];
1511
- const currentSlideId = currentSlide?.id ?? '';
1512
- const state = slideCount === 0 ? 'empty' : 'ready';
1513
- const playbackState = slideCount > 1 && options.playback === 'playing' ? 'playing' : 'paused';
1514
- const previousTarget = currentIndex > 0 ? options.slides[currentIndex - 1] : undefined;
1515
- const nextTarget = currentIndex < slideCount - 1 ? options.slides[currentIndex + 1] : undefined;
1516
- const previousState = previousTarget === undefined ? 'disabled' : 'ready';
1517
- const nextState = nextTarget === undefined ? 'disabled' : 'ready';
1518
- const playbackDisabled = slideCount < 2;
1519
- const rootId = options.id;
1520
- const viewportId = rootId === undefined ? undefined : `${rootId}-viewport`;
1521
- const trackId = rootId === undefined ? undefined : `${rootId}-track`;
1522
- return {
1523
- state,
1524
- playbackState,
1525
- currentSlideId,
1526
- currentSlidePosition: currentSlide === undefined ? 0 : currentIndex + 1,
1527
- slideCount,
1528
- root: createUiDomPlan('section', 'carousel', {
1529
- ...(options.className === undefined ? {} : { className: options.className }),
1530
- attrs: {
1531
- id: rootId,
1532
- role: 'region',
1533
- 'aria-roledescription': 'carousel',
1534
- 'aria-label': options.label,
1535
- ...options.attrs,
1536
- 'data-carousel-structure-playback-contract': 'true',
1537
- 'data-carousel-state': state,
1538
- 'data-playback-state': playbackState,
1539
- 'data-slide-count': slideCount,
1540
- 'data-slide-ids': options.slides.map((slide) => slide.id).join(','),
1541
- 'data-current-slide-id': currentSlideId,
1542
- 'data-current-slide-position': currentSlide === undefined ? 0 : currentIndex + 1,
1543
- },
1544
- }),
1545
- viewport: createUiDomPlan('div', 'carousel-viewport', {
1546
- attrs: {
1547
- id: viewportId,
1548
- 'aria-live': playbackState === 'playing' ? 'off' : 'polite',
1549
- 'aria-atomic': 'true',
1550
- 'data-current-slide-id': currentSlideId,
1551
- },
1552
- }),
1553
- track: createUiDomPlan('div', 'carousel-track', { attrs: { id: trackId } }),
1554
- slides: options.slides.map((slide, index) => {
1555
- const current = index === currentIndex;
1556
- return createUiDomPlan('article', 'carousel-slide', {
1557
- attrs: {
1558
- id: slide.id,
1559
- role: 'group',
1560
- 'aria-roledescription': 'slide',
1561
- 'aria-label': `${index + 1} of ${slideCount}: ${slide.label}`,
1562
- 'aria-current': current ? 'true' : undefined,
1563
- 'aria-hidden': current ? undefined : 'true',
1564
- hidden: current ? undefined : true,
1565
- 'data-slide-id': slide.id,
1566
- 'data-slide-label': slide.label,
1567
- 'data-slide-state': current ? 'current' : 'inactive',
1568
- },
1569
- });
1570
- }),
1571
- controls: createUiDomPlan('div', 'carousel-controls', {
1572
- attrs: { role: 'group', 'aria-label': 'Carousel controls' },
1573
- }),
1574
- previous: createUiDomPlan('button', 'carousel-control', {
1575
- disabled: previousState === 'disabled',
1576
- attrs: {
1577
- type: 'button',
1578
- 'aria-label': options.previousLabel ?? 'Previous slide',
1579
- 'aria-controls': trackId,
1580
- 'data-control': 'previous',
1581
- 'data-carousel-control-state': previousState,
1582
- 'data-target-slide-id': previousTarget?.id,
1583
- },
1584
- }),
1585
- next: createUiDomPlan('button', 'carousel-control', {
1586
- disabled: nextState === 'disabled',
1587
- attrs: {
1588
- type: 'button',
1589
- 'aria-label': options.nextLabel ?? 'Next slide',
1590
- 'aria-controls': trackId,
1591
- 'data-control': 'next',
1592
- 'data-carousel-control-state': nextState,
1593
- 'data-target-slide-id': nextTarget?.id,
1594
- },
1595
- }),
1596
- playback: createUiDomPlan('button', 'carousel-playback', {
1597
- disabled: playbackDisabled,
1598
- attrs: {
1599
- type: 'button',
1600
- 'aria-label': playbackState === 'playing'
1601
- ? (options.pauseLabel ?? 'Pause carousel')
1602
- : (options.playLabel ?? 'Play carousel'),
1603
- 'aria-controls': trackId,
1604
- 'aria-pressed': playbackState === 'playing' ? 'true' : 'false',
1605
- 'data-carousel-control-state': playbackDisabled ? 'disabled' : 'ready',
1606
- 'data-playback-state': playbackState,
1607
- 'data-next-playback-state': playbackState === 'playing' ? 'paused' : 'playing',
1608
- },
1609
- }),
1610
- indicators: createUiDomPlan('div', 'carousel-indicators', {
1611
- attrs: { role: 'group', 'aria-label': 'Choose slide' },
1612
- }),
1613
- indicatorButtons: options.slides.map((slide, index) => {
1614
- const current = index === currentIndex;
1615
- return createUiDomPlan('button', 'carousel-indicator', {
1616
- disabled: current,
1617
- attrs: {
1618
- type: 'button',
1619
- 'aria-label': `Go to slide ${index + 1}: ${slide.label}`,
1620
- 'aria-controls': slide.id,
1621
- 'aria-current': current ? 'true' : undefined,
1622
- 'data-slide-id': slide.id,
1623
- 'data-slide-state': current ? 'current' : 'idle',
1624
- },
1625
- });
1626
- }),
1627
- };
1628
- }
1629
- export function createSplitWorkspacePlans(options) {
1630
- const fallback = options.tabletFallback ?? 'stack';
1631
- return {
1632
- root: createUiDomPlan('section', 'split-workspace', {
1633
- ...(options.className === undefined ? {} : { className: options.className }),
1634
- attrs: {
1635
- id: options.id,
1636
- ...options.attrs,
1637
- 'data-tablet-fallback': fallback,
1638
- 'data-areas': options.areas.length,
1639
- },
1640
- }),
1641
- areas: options.areas.map((area) => createUiDomPlan('section', 'split-workspace-area', {
1642
- attrs: {
1643
- id: area.id,
1644
- 'data-area-role': area.role,
1645
- 'data-fallback': area.fallback ?? fallback,
1646
- style: area.minWidth === undefined ? undefined : `--kui-area-min: ${area.minWidth};`,
1647
- },
1648
- })),
1649
- };
1650
- }
1651
- export function createSkeletonScreenPlans(options = {}) {
1652
- const rowCount = Math.max(1, Math.trunc(options.rows ?? 4));
1653
- return {
1654
- root: createUiDomPlan('section', 'skeleton-screen', {
1655
- ...(options.className === undefined ? {} : { className: options.className }),
1656
- ...(options.density === undefined ? {} : { density: options.density }),
1657
- loading: true,
1658
- attrs: {
1659
- id: options.id,
1660
- role: 'status',
1661
- 'aria-label': options.label ?? 'Loading content',
1662
- ...options.attrs,
1663
- 'data-row-count': rowCount,
1664
- 'data-animated': options.animated === false ? 'false' : 'true',
1665
- },
1666
- }),
1667
- rows: Array.from({ length: rowCount }, (_, index) => createSkeletonPrimitive({
1668
- className: 'kui-skeleton-screen-row',
1669
- attrs: {
1670
- 'data-index': index,
1671
- style: `--kui-skeleton-width: ${skeletonRowWidth(index)}%;`,
1672
- },
1673
- })),
1674
- };
1675
- }
1676
- export function createSpinOverlayPlans(options = {}) {
1677
- const open = options.open !== false;
1678
- const mode = options.mode ?? 'blocking';
1679
- return {
1680
- root: createUiDomPlan('div', 'spin-overlay', {
1681
- ...(options.className === undefined ? {} : { className: options.className }),
1682
- loading: open,
1683
- state: open ? 'open' : 'closed',
1684
- attrs: {
1685
- id: options.id,
1686
- role: 'status',
1687
- 'aria-label': options.label ?? 'Loading',
1688
- 'aria-live': 'polite',
1689
- 'aria-controls': options.targetId,
1690
- hidden: open ? undefined : true,
1691
- ...options.attrs,
1692
- 'data-mode': mode,
1693
- 'data-target-id': options.targetId,
1694
- },
1695
- }),
1696
- spinner: createSpinPrimitive({
1697
- label: options.label ?? 'Loading',
1698
- className: 'kui-spin-overlay-spinner',
1699
- }),
1700
- };
1701
- }
1702
- export function createButtonLoadingPlans(options) {
1703
- const button = createButtonPrimitive({
1704
- label: options.label,
1705
- ...(options.loading === undefined ? {} : { loading: options.loading }),
1706
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
1707
- ...(options.variant === undefined ? {} : { variant: options.variant }),
1708
- ...(options.size === undefined ? {} : { size: options.size }),
1709
- className: classNames('kui-button-loading', options.className),
1710
- attrs: {
1711
- ...(options.id === undefined ? {} : { id: options.id }),
1712
- ...options.attrs,
1713
- 'data-label': options.label,
1714
- },
1715
- });
1716
- return {
1717
- root: button,
1718
- ...(options.loading === true
1719
- ? {
1720
- indicator: createSpinPrimitive({
1721
- label: `${options.label} loading`,
1722
- className: 'kui-button-loading-indicator',
1723
- attrs: {
1724
- id: options.id === undefined ? undefined : `${options.id}-loading`,
1725
- },
1726
- }),
1727
- }
1728
- : {}),
1729
- label: createUiDomPlan('span', 'button-loading-label', {
1730
- attrs: { 'data-label': options.label },
1731
- }),
1732
- };
1733
- }
1734
- function styleSuffix(value) {
1735
- if (typeof value !== 'string' || value.trim() === '')
1736
- return '';
1737
- return value.trim().endsWith(';') ? value.trim() : `${value.trim()};`;
1738
- }
1739
- function ariaSort(column) {
1740
- if (column.sortable !== true)
1741
- return undefined;
1742
- if (column.sortDirection === 'ascending')
1743
- return 'ascending';
1744
- if (column.sortDirection === 'descending')
1745
- return 'descending';
1746
- return 'none';
1747
- }
1748
- function tableColumnStyle(column) {
1749
- const parts = [];
1750
- if (column.width !== undefined)
1751
- parts.push(`width: ${column.width};`);
1752
- if (column.minWidth !== undefined)
1753
- parts.push(`min-width: ${column.minWidth};`);
1754
- return parts.length === 0 ? undefined : parts.join('');
1755
- }
1756
- function filterChipLabel(filter) {
1757
- if (filter.value === undefined || filter.value === '')
1758
- return filter.label;
1759
- return `${filter.label}: ${filter.value}`;
1760
- }
1761
- function tagLabel(options) {
1762
- const parts = [options.label];
1763
- if (options.value !== undefined && options.value !== '')
1764
- parts.push(options.value);
1765
- if (options.count !== undefined)
1766
- parts.push(`${options.count}`);
1767
- return parts.join(': ');
1768
- }
1769
- function typographyTag(variant) {
1770
- if (variant === 'heading-lg')
1771
- return 'h1';
1772
- if (variant === 'heading-md')
1773
- return 'h2';
1774
- if (variant === 'heading-sm')
1775
- return 'h3';
1776
- if (variant === 'code')
1777
- return 'code';
1778
- if (variant === 'link')
1779
- return 'a';
1780
- return 'p';
1781
- }
1782
- function dividerInset(value) {
1783
- if (value === true)
1784
- return 'both';
1785
- if (value === false || value === undefined)
1786
- return undefined;
1787
- return value;
1788
- }
1789
- function initialsFromName(name) {
1790
- const parts = name
1791
- .trim()
1792
- .split(/\s+/)
1793
- .filter((part) => part.length > 0);
1794
- if (parts.length === 0)
1795
- return '?';
1796
- if (parts.length === 1)
1797
- return parts[0]?.slice(0, 2) ?? '?';
1798
- return `${parts[0]?.[0] ?? ''}${parts[parts.length - 1]?.[0] ?? ''}`;
1799
- }
1800
- function normalizeInitials(value) {
1801
- const normalized = value.replace(/\s+/g, '').slice(0, 2).toUpperCase();
1802
- return normalized === '' ? '?' : normalized;
1803
- }
1804
- function avatarImageState(options) {
1805
- if (options.imageError !== undefined && options.imageError.length > 0)
1806
- return 'error';
1807
- return options.src === undefined || options.src === '' ? 'fallback' : 'loaded';
1808
- }
1809
- function formatShortcutLabel(keys) {
1810
- return keys.join(' + ');
1811
- }
1812
- function getPaginationPageCount(options) {
1813
- const explicit = normalizeCount(options.pageCount);
1814
- if (explicit !== undefined && explicit > 0)
1815
- return explicit;
1816
- const total = normalizeCount(options.totalItems);
1817
- const pageSize = normalizeCount(options.pageSize);
1818
- if (total !== undefined && pageSize !== undefined && pageSize > 0) {
1819
- return Math.max(1, Math.ceil(total / pageSize));
1820
- }
1821
- return 1;
1822
- }
1823
- function clampPage(page, pageCount) {
1824
- if (!Number.isFinite(page))
1825
- return 1;
1826
- return Math.min(Math.max(1, Math.trunc(page)), Math.max(1, pageCount));
1827
- }
1828
- function createPaginationControl(control, page, disabled, label) {
1829
- return createUiDomPlan('button', `pagination-${control}`, {
1830
- disabled,
1831
- state: disabled ? 'disabled' : 'idle',
1832
- attrs: {
1833
- type: 'button',
1834
- 'aria-label': label,
1835
- 'data-page': page,
1836
- 'data-control': control,
1837
- },
1838
- });
1839
- }
1840
- function createPaginationItemPlan(item, index) {
1841
- if (item.kind === 'ellipsis') {
1842
- return createUiDomPlan('span', 'pagination-ellipsis', {
1843
- attrs: {
1844
- 'aria-hidden': 'true',
1845
- 'data-kind': 'ellipsis',
1846
- 'data-index': index,
1847
- },
1848
- });
1849
- }
1850
- const page = item.page ?? 1;
1851
- return createUiDomPlan('button', 'pagination-page', {
1852
- state: item.current === true ? 'current' : 'idle',
1853
- attrs: {
1854
- type: 'button',
1855
- 'aria-label': `Page ${page}`,
1856
- 'aria-current': item.current === true ? 'page' : undefined,
1857
- 'data-kind': 'page',
1858
- 'data-page': page,
1859
- },
1860
- });
1861
- }
1862
- function createNoteLikePlans(kind, options) {
1863
- const tone = options.tone ?? 'info';
1864
- return {
1865
- root: createUiDomPlan('aside', kind, {
1866
- ...(options.className === undefined ? {} : { className: options.className }),
1867
- state: tone,
1868
- attrs: {
1869
- id: options.id,
1870
- role: tone === 'error' || tone === 'warning' ? 'alert' : 'note',
1871
- 'aria-labelledby': options.titleId,
1872
- 'aria-describedby': options.descriptionId,
1873
- ...options.attrs,
1874
- 'data-tone': tone,
1875
- 'data-dismissible': options.closeLabel === undefined ? undefined : '',
1876
- 'data-actionable': options.action === undefined ? undefined : '',
1877
- },
1878
- }),
1879
- ...(options.icon === false
1880
- ? {}
1881
- : {
1882
- icon: createUiDomPlan('span', `${kind}-icon`, {
1883
- attrs: { 'aria-hidden': 'true', 'data-tone': tone },
1884
- }),
1885
- }),
1886
- content: createUiDomPlan('div', `${kind}-content`),
1887
- ...(options.titleId === undefined
1888
- ? {}
1889
- : { title: createUiDomPlan('strong', `${kind}-title`, { attrs: { id: options.titleId } }) }),
1890
- ...(options.descriptionId === undefined
1891
- ? {}
1892
- : {
1893
- description: createUiDomPlan('p', `${kind}-description`, {
1894
- attrs: { id: options.descriptionId },
1895
- }),
1896
- }),
1897
- ...(options.action === undefined
1898
- ? {}
1899
- : { actionButton: createDataDisplayActionButton(kind, options.action) }),
1900
- ...(options.closeLabel === undefined
1901
- ? {}
1902
- : {
1903
- closeButton: createUiDomPlan('button', `${kind}-close`, {
1904
- attrs: {
1905
- type: 'button',
1906
- 'aria-label': options.closeLabel,
1907
- 'data-display-close': '',
1908
- },
1909
- }),
1910
- }),
1911
- };
1912
- }
1913
- function createPanelLikePlans(kind, options) {
1914
- const tone = options.tone ?? 'default';
1915
- const actions = (options.actions ?? []).map((action) => createDataDisplayActionButton(kind, action));
1916
- return {
1917
- root: createUiDomPlan(kind === 'card' ? 'article' : 'section', kind, {
1918
- ...(options.className === undefined ? {} : { className: options.className }),
1919
- state: options.selected === true ? 'selected' : 'idle',
1920
- attrs: {
1921
- id: options.id,
1922
- role: options.interactive === true ? 'button' : undefined,
1923
- tabindex: options.interactive === true ? 0 : undefined,
1924
- 'aria-labelledby': options.titleId,
1925
- 'aria-describedby': options.descriptionId,
1926
- 'aria-selected': options.selected === undefined ? undefined : String(options.selected),
1927
- ...options.attrs,
1928
- 'data-tone': tone,
1929
- 'data-interactive': options.interactive === true ? 'true' : undefined,
1930
- 'data-action-count': actions.length,
1931
- },
1932
- }),
1933
- ...(options.titleId === undefined && options.descriptionId === undefined && actions.length === 0
1934
- ? {}
1935
- : { header: createUiDomPlan('header', `${kind}-header`) }),
1936
- ...(options.titleId === undefined
1937
- ? {}
1938
- : { title: createUiDomPlan('h3', `${kind}-title`, { attrs: { id: options.titleId } }) }),
1939
- ...(options.descriptionId === undefined
1940
- ? {}
1941
- : {
1942
- description: createUiDomPlan('p', `${kind}-description`, {
1943
- attrs: { id: options.descriptionId },
1944
- }),
1945
- }),
1946
- body: createUiDomPlan('div', `${kind}-body`),
1947
- ...(actions.length === 0 ? {} : { footer: createUiDomPlan('footer', `${kind}-footer`) }),
1948
- actions,
1949
- };
1950
- }
1951
- function createDataDisplayActionButton(component, action) {
1952
- return createUiDomPlan('button', `${component}-action`, {
1953
- ...(action.disabled === undefined ? {} : { disabled: action.disabled }),
1954
- state: action.disabled === true ? 'disabled' : 'idle',
1955
- className: classNames(action.tone === undefined ? undefined : `kui-${component}-action--${action.tone}`),
1956
- attrs: {
1957
- type: 'button',
1958
- 'aria-label': action.label,
1959
- 'data-action-id': action.id,
1960
- 'data-tone': action.tone ?? 'default',
1961
- 'data-label': action.label,
1962
- },
1963
- });
1964
- }
1965
- function tableCellState(options) {
1966
- if (options.loading === true)
1967
- return 'loading';
1968
- if (options.error === true)
1969
- return 'error';
1970
- if (options.disabled === true)
1971
- return 'disabled';
1972
- if (options.interactive === true || options.intent !== undefined)
1973
- return 'interactive';
1974
- return 'readonly';
1975
- }
1976
- function tableSelectionSummaryState(selectedCount, eligibleCount) {
1977
- if (selectedCount === 0)
1978
- return 'empty';
1979
- if (eligibleCount !== undefined && eligibleCount > 0 && selectedCount >= eligibleCount)
1980
- return 'all';
1981
- return 'partial';
1982
- }
1983
- function formatTableSelectionSummary(options) {
1984
- const parts = [`${options.selectedCount} selected`];
1985
- if (options.eligibleCount !== undefined)
1986
- parts.push(`${options.eligibleCount} eligible`);
1987
- if (options.totalCount !== undefined && options.totalCount !== options.eligibleCount) {
1988
- parts.push(`${options.totalCount} rows`);
1989
- }
1990
- return parts.join(' · ');
1991
- }
1992
- function tableBatchActionContract(action, selectedCount) {
1993
- const state = tableBatchActionState(action, selectedCount);
1994
- const reason = action.reason ??
1995
- (action.requiresSelection === true && selectedCount === 0 ? 'Select rows first' : '');
1996
- return {
1997
- id: action.id,
1998
- label: action.label,
1999
- state,
2000
- reason,
2001
- attrs: {
2002
- 'data-action-id': action.id,
2003
- 'data-label': action.label,
2004
- 'data-tone': action.tone ?? 'secondary',
2005
- 'data-action-state': state,
2006
- 'data-requires-selection': action.requiresSelection === true ? 'true' : undefined,
2007
- 'data-disabled-reason': reason === '' ? undefined : reason,
2008
- 'aria-label': action.label,
2009
- 'aria-disabled': state === 'disabled' ? 'true' : undefined,
2010
- 'aria-busy': state === 'pending' ? 'true' : undefined,
2011
- hidden: state === 'hidden' ? true : undefined,
2012
- },
2013
- };
2014
- }
2015
- function tableBatchActionState(action, selectedCount) {
2016
- if (action.hidden === true)
2017
- return 'hidden';
2018
- if (action.pending === true)
2019
- return 'pending';
2020
- if (action.disabled === true || action.reason !== undefined)
2021
- return 'disabled';
2022
- if (action.requiresSelection === true && selectedCount === 0)
2023
- return 'disabled';
2024
- return 'available';
2025
- }
2026
- function normalizeCount(value) {
2027
- if (value === undefined || !Number.isFinite(value))
2028
- return undefined;
2029
- return Math.max(0, Math.trunc(value));
2030
- }
2031
- function badgeCountOverflowLabel(options) {
2032
- if (options.dot)
2033
- return 'Badge dot';
2034
- if (options.count <= 0)
2035
- return 'No badge count';
2036
- return options.overflow ? `${options.displayValue} items` : `${options.count} items`;
2037
- }
2038
- function normalizeSelectedIds(ids) {
2039
- if (ids === undefined)
2040
- return [];
2041
- const values = Array.isArray(ids) ? ids : [...ids];
2042
- return [...new Set(values.filter((id) => id.length > 0))];
2043
- }
2044
- function normalizeColumnPreferenceIds(ids, knownIds) {
2045
- if (ids === undefined)
2046
- return [];
2047
- const result = [];
2048
- for (const id of ids) {
2049
- if (!knownIds.has(id) || result.includes(id))
2050
- continue;
2051
- result.push(id);
2052
- }
2053
- return result;
2054
- }
2055
- function orderTableColumns(columns, orderIds) {
2056
- if (orderIds.length === 0)
2057
- return [...columns];
2058
- const columnById = new Map(columns.map((column) => [column.id, column]));
2059
- const ordered = orderIds
2060
- .map((id) => columnById.get(id))
2061
- .filter((column) => column !== undefined);
2062
- const remaining = columns.filter((column) => !orderIds.includes(column.id));
2063
- return [...ordered, ...remaining];
2064
- }
2065
- function tableColumnPreferenceSticky(column, stickyStartIds, stickyEndIds) {
2066
- if (stickyStartIds === undefined && stickyEndIds === undefined)
2067
- return column.sticky;
2068
- if (stickyStartIds?.has(column.id) === true)
2069
- return 'start';
2070
- if (stickyEndIds?.has(column.id) === true)
2071
- return 'end';
2072
- return undefined;
2073
- }
2074
- function tableColumnPreferenceState(options) {
2075
- if (options.sourceColumns.length === 0)
2076
- return 'empty';
2077
- const sourceOrder = options.sourceColumns.map((column) => column.id).join(',');
2078
- const nextOrder = options.columns.map((column) => column.id).join(',');
2079
- const densityChanged = options.defaultDensity !== undefined && options.density !== options.defaultDensity;
2080
- if (densityChanged ||
2081
- options.hiddenIds.length > 0 ||
2082
- options.visibleIds !== undefined ||
2083
- options.orderIds.length > 0 ||
2084
- sourceOrder !== nextOrder ||
2085
- options.stickyStartIds !== undefined ||
2086
- options.stickyEndIds !== undefined) {
2087
- return 'customized';
2088
- }
2089
- return 'default';
2090
- }
2091
- function tableToolbarState(options) {
2092
- const visibleCount = options.filteredCount ?? options.totalCount;
2093
- if (options.loading)
2094
- return 'loading';
2095
- if (visibleCount === 0)
2096
- return 'empty';
2097
- if (options.selectedCount > 0)
2098
- return 'selected';
2099
- if (options.query !== '' || options.activeFilterCount > 0 || options.sortDirection !== 'none') {
2100
- return 'filtered';
2101
- }
2102
- return 'ready';
2103
- }
2104
- function normalizeEmptyOptions(options) {
2105
- if (options === undefined || options === false)
2106
- return undefined;
2107
- if (options === true)
2108
- return {};
2109
- return options;
2110
- }
2111
- function skeletonRowWidth(index) {
2112
- const widths = [92, 84, 78, 88, 72, 80];
2113
- return widths[index % widths.length] ?? 80;
2114
- }
2115
- export function createDescriptionsPlans(options) {
2116
- const layout = options.layout ?? 'horizontal';
2117
- const items = options.items.map((item) => ({
2118
- itemId: item.id,
2119
- label: createUiDomPlan('dt', 'descriptions-label', {
2120
- attrs: { id: `${options.id}-${item.id}-label`, 'data-label': item.label },
2121
- }),
2122
- value: createUiDomPlan('dd', 'descriptions-value', {
2123
- attrs: {
2124
- ...(item.span === undefined ? {} : { 'data-span': String(item.span) }),
2125
- ...(item.value === undefined ? {} : { 'data-value': item.value }),
2126
- },
2127
- }),
2128
- }));
2129
- return {
2130
- root: createUiDomPlan('dl', 'descriptions', {
2131
- state: layout === 'vertical' ? 'vertical' : 'horizontal',
2132
- ...(options.className === undefined ? {} : { className: options.className }),
2133
- attrs: {
2134
- id: `${options.id}-descriptions`,
2135
- ...(options.bordered === undefined
2136
- ? {}
2137
- : { 'data-bordered': options.bordered ? 'true' : 'false' }),
2138
- ...(options.column === undefined ? {} : { 'data-column': String(options.column) }),
2139
- ...options.attrs,
2140
- },
2141
- }),
2142
- items,
2143
- };
2144
- }
2145
- export function createTimelinePlans(options) {
2146
- const items = options.items.map((item) => {
2147
- const state = item.state ?? 'default';
2148
- return {
2149
- itemId: item.id,
2150
- state,
2151
- title: createUiDomPlan('div', 'timeline-title', {
2152
- attrs: { id: `${options.id}-${item.id}-title`, 'data-label': item.title },
2153
- }),
2154
- description: createUiDomPlan('div', 'timeline-description', {
2155
- attrs: {
2156
- id: `${options.id}-${item.id}-desc`,
2157
- ...(item.description === undefined ? {} : { 'data-label': item.description }),
2158
- },
2159
- }),
2160
- time: createUiDomPlan('div', 'timeline-time', {
2161
- attrs: {
2162
- id: `${options.id}-${item.id}-time`,
2163
- ...(item.time === undefined ? {} : { 'data-label': item.time }),
2164
- },
2165
- }),
2166
- };
2167
- });
2168
- // Timeline items carry state metadata (the root <li> elements are rendered by the app from items)
2169
- return {
2170
- root: createUiDomPlan('ul', 'timeline', {
2171
- ...(options.className === undefined ? {} : { className: options.className }),
2172
- attrs: { id: `${options.id}-timeline`, ...options.attrs },
2173
- }),
2174
- items,
2175
- };
2176
- }
2177
- export function createImagePlans(options) {
2178
- return {
2179
- root: createUiDomPlan('figure', 'image', {
2180
- state: options.preview === true ? 'preview' : 'plain',
2181
- ...(options.className === undefined ? {} : { className: options.className }),
2182
- attrs: { id: `${options.id}-image`, ...options.attrs },
2183
- }),
2184
- image: createUiDomPlan('img', 'image-figure', {
2185
- attrs: {
2186
- id: `${options.id}-img`,
2187
- ...(options.src === undefined ? {} : { src: options.src }),
2188
- ...(options.alt === undefined ? {} : { alt: options.alt }),
2189
- ...(options.preview === true ? { 'data-preview': 'true' } : {}),
2190
- },
2191
- }),
2192
- overlay: createUiDomPlan('div', 'image-overlay', {
2193
- state: 'hidden',
2194
- attrs: { id: `${options.id}-overlay`, role: 'dialog', 'aria-hidden': 'true' },
2195
- }),
2196
- close: createUiDomPlan('button', 'image-overlay-close', {
2197
- attrs: { id: `${options.id}-close`, type: 'button', 'aria-label': 'Close preview' },
2198
- }),
2199
- caption: createUiDomPlan('figcaption', 'image-caption', {
2200
- attrs: {
2201
- id: `${options.id}-caption`,
2202
- ...(options.caption === undefined ? {} : { 'data-label': options.caption }),
2203
- },
2204
- }),
2205
- };
2206
- }
2207
- export function createBackTopPlans(options) {
2208
- return {
2209
- root: createUiDomPlan('button', 'back-top', {
2210
- state: options.visible === true ? 'visible' : 'hidden',
2211
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
2212
- attrs: {
2213
- id: `${options.id}-backtop`,
2214
- type: 'button',
2215
- 'aria-label': 'Back to top',
2216
- ...(options.visible === true ? {} : { hidden: 'true' }),
2217
- ...options.attrs,
2218
- },
2219
- }),
2220
- };
2221
- }
2222
- function escapeRegex(value) {
2223
- return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2224
- }
2225
- /**
2226
- * Splits text into highlight/plain segments by query.
2227
- * - Case-insensitive; supports multiple keywords (array); empty query/text returns a single plain segment.
2228
- * - Overlapping hits are resolved by the earliest position (no nesting).
2229
- */
2230
- export function splitHighlightSegments(text, query) {
2231
- const keywords = (Array.isArray(query) ? query : [query])
2232
- .map((value) => value.trim())
2233
- .filter((value) => value !== '');
2234
- if (text === '' || keywords.length === 0)
2235
- return [{ text, highlighted: false }];
2236
- const pattern = new RegExp(`(${keywords.map(escapeRegex).join('|')})`, 'gi');
2237
- const segments = [];
2238
- let lastIndex = 0;
2239
- for (const match of text.matchAll(pattern)) {
2240
- const index = match.index ?? 0;
2241
- if (index > lastIndex) {
2242
- segments.push({ text: text.slice(lastIndex, index), highlighted: false });
2243
- }
2244
- segments.push({ text: match[0], highlighted: true });
2245
- lastIndex = index + match[0].length;
2246
- }
2247
- if (lastIndex < text.length) {
2248
- segments.push({ text: text.slice(lastIndex), highlighted: false });
2249
- }
2250
- return segments.length === 0 ? [{ text, highlighted: false }] : segments;
2251
- }
2252
- /** Highlight-mark plan (used by the app for highlighted segments at render time). */
2253
- export function createHighlightMark(text, id) {
2254
- return createUiDomPlan('mark', 'highlight', {
2255
- attrs: { ...(id === undefined ? {} : { id }), 'data-label': text },
2256
- });
2257
- }
2258
- export function createCodeBlockPlans(options) {
2259
- const code = createUiDomPlan('pre', 'code-block-code', {
2260
- attrs: {
2261
- id: `${options.id}-code`,
2262
- ...(options.code === undefined ? {} : { 'data-code': options.code }),
2263
- },
2264
- });
2265
- return {
2266
- root: createUiDomPlan('figure', 'code-block', {
2267
- ...(options.className === undefined ? {} : { className: options.className }),
2268
- attrs: { id: `${options.id}-codeblock`, ...options.attrs },
2269
- }),
2270
- header: createUiDomPlan('div', 'code-block-header', {
2271
- attrs: { id: `${options.id}-header` },
2272
- }),
2273
- language: createUiDomPlan('span', 'code-block-language', {
2274
- attrs: {
2275
- id: `${options.id}-lang`,
2276
- ...(options.language === undefined
2277
- ? {}
2278
- : { 'data-label': options.language, 'data-language': options.language }),
2279
- },
2280
- }),
2281
- copy: createUiDomPlan('button', 'code-block-copy', {
2282
- attrs: {
2283
- id: `${options.id}-copy`,
2284
- type: 'button',
2285
- 'aria-label': 'Copy code',
2286
- ...(options.copyable === false ? { hidden: 'true' } : {}),
2287
- },
2288
- }),
2289
- code,
2290
- ...(options.showLineNumbers === true
2291
- ? {
2292
- lineNumbers: createUiDomPlan('div', 'code-block-line-numbers', {
2293
- attrs: { id: `${options.id}-lines` },
2294
- }),
2295
- }
2296
- : {}),
2297
- };
2298
- }