@vobs/ui 0.3.0 → 1.1.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 (212) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -0
  3. package/package.json +52 -110
  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 +118 -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 -30
  165. package/dist/app-shell-dom.js +0 -913
  166. package/dist/app-shell.d.ts +0 -235
  167. package/dist/app-shell.js +0 -415
  168. package/dist/base.css +0 -51
  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 -866
  175. package/dist/components.css +0 -4756
  176. package/dist/data-display.d.ts +0 -1094
  177. package/dist/data-display.js +0 -2341
  178. package/dist/dialog.d.ts +0 -26
  179. package/dist/dialog.js +0 -94
  180. package/dist/feedback.d.ts +0 -88
  181. package/dist/feedback.js +0 -281
  182. package/dist/forms.d.ts +0 -378
  183. package/dist/forms.js +0 -1019
  184. package/dist/icons-config.d.ts +0 -25
  185. package/dist/icons-config.js +0 -14
  186. package/dist/icons.d.ts +0 -41
  187. package/dist/icons.js +0 -185
  188. package/dist/index.d.ts +0 -31
  189. package/dist/index.js +0 -40
  190. package/dist/locale/en-US.d.ts +0 -31
  191. package/dist/locale/en-US.js +0 -20
  192. package/dist/locale/zh-CN.d.ts +0 -31
  193. package/dist/locale/zh-CN.js +0 -20
  194. package/dist/navigation.d.ts +0 -609
  195. package/dist/navigation.js +0 -1707
  196. package/dist/overlay.d.ts +0 -308
  197. package/dist/overlay.js +0 -974
  198. package/dist/page-state.d.ts +0 -23
  199. package/dist/page-state.js +0 -96
  200. package/dist/primitives.d.ts +0 -268
  201. package/dist/primitives.js +0 -764
  202. package/dist/theme-data.d.ts +0 -394
  203. package/dist/theme-data.js +0 -417
  204. package/dist/theme.d.ts +0 -97
  205. package/dist/theme.js +0 -301
  206. package/dist/tokens.css +0 -570
  207. package/dist/tree.d.ts +0 -144
  208. package/dist/tree.js +0 -475
  209. package/dist/virtual-list.d.ts +0 -190
  210. package/dist/virtual-list.js +0 -538
  211. package/dist/workbench.d.ts +0 -136
  212. package/dist/workbench.js +0 -156
package/dist/forms.js DELETED
@@ -1,1019 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { createForm, createFormRenderer, createFormView, } from '@vobs/forms';
6
- import { classNames, createChoicePrimitive, createInputPrimitive, createSelectPrimitive, createTextareaPrimitive, createUiDomPlan, } from './primitives.js';
7
- export function createFormDrawerSubmissionContract(options) {
8
- const mode = options.mode ?? 'edit';
9
- const open = options.open === true;
10
- const dirty = options.dirty === true;
11
- const disabled = options.disabled === true;
12
- const submitting = options.submitting === true || options.view.submitting.value;
13
- const serverErrorCount = normalizeCount(options.serverErrorCount);
14
- const errorCount = options.view.errorSummary.value.length;
15
- const invalid = errorCount > 0;
16
- const success = options.success === true && !invalid && !submitting;
17
- const state = formDrawerSubmissionState({
18
- dirty,
19
- disabled,
20
- invalid,
21
- serverErrorCount,
22
- submitting,
23
- success,
24
- });
25
- const canSubmit = !disabled && !submitting && !invalid;
26
- const focusTarget = readFirstErrorFocusTarget(options.view) ?? `#${options.drawerId}`;
27
- return {
28
- drawerId: options.drawerId,
29
- mode,
30
- state,
31
- open,
32
- dirty,
33
- disabled,
34
- submitting,
35
- invalid,
36
- success,
37
- errorCount,
38
- serverErrorCount,
39
- canSubmit,
40
- focusTarget,
41
- attrs: {
42
- 'data-form-drawer-contract': 'true',
43
- 'data-drawer-id': options.drawerId,
44
- 'data-mode': mode,
45
- 'data-state': state,
46
- 'data-open': open ? 'true' : 'false',
47
- 'data-dirty': dirty ? 'true' : 'false',
48
- 'data-disabled': disabled ? 'true' : undefined,
49
- 'data-submitting': submitting ? 'true' : 'false',
50
- 'data-invalid': invalid ? 'true' : 'false',
51
- 'data-success': success ? 'true' : 'false',
52
- 'data-error-count': errorCount,
53
- 'data-server-error-count': serverErrorCount,
54
- 'data-can-submit': canSubmit ? 'true' : 'false',
55
- 'data-focus-target': focusTarget,
56
- 'aria-busy': submitting ? 'true' : undefined,
57
- },
58
- submitAttrs: {
59
- type: 'submit',
60
- disabled: !canSubmit,
61
- 'aria-disabled': canSubmit ? undefined : 'true',
62
- 'aria-busy': submitting ? 'true' : undefined,
63
- 'data-submit-intent': options.submitIntent ?? 'submit',
64
- 'data-state': submitting ? 'submitting' : canSubmit ? 'ready' : 'blocked',
65
- 'data-can-submit': canSubmit ? 'true' : 'false',
66
- },
67
- cancelAttrs: {
68
- type: 'button',
69
- 'data-cancel-intent': options.cancelIntent ?? 'cancel',
70
- 'data-state': submitting ? 'blocked' : 'ready',
71
- disabled: submitting ? true : undefined,
72
- 'aria-disabled': submitting ? 'true' : undefined,
73
- },
74
- };
75
- }
76
- export function createSliderPlans(options) {
77
- const min = finiteOr(options.min, 0);
78
- const max = Math.max(min, finiteOr(options.max, 100));
79
- const step = Math.max(0, finiteOr(options.step, 1));
80
- const values = sliderValues(options.value, min, max);
81
- const mode = isSliderRangeValue(options.value) ? 'range' : 'single';
82
- const thumbValues = mode === 'single' ? [values[0]] : values;
83
- const startPercent = sliderPercent(values[0], min, max);
84
- const endPercent = sliderPercent(values[1], min, max);
85
- return {
86
- root: createUiDomPlan('div', 'slider', {
87
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
88
- ...(options.readonly === undefined ? {} : { readonly: options.readonly }),
89
- state: options.disabled === true ? 'disabled' : options.readonly === true ? 'readonly' : mode,
90
- ...(options.className === undefined ? {} : { className: options.className }),
91
- attrs: {
92
- id: options.id,
93
- role: 'group',
94
- 'aria-label': options.label,
95
- ...options.attrs,
96
- 'data-mode': mode,
97
- 'data-orientation': options.orientation ?? 'horizontal',
98
- 'data-min': min,
99
- 'data-max': max,
100
- 'data-step': step,
101
- 'data-value-start': values[0],
102
- 'data-value-end': values[1],
103
- style: `--kui-slider-start: ${startPercent}%; --kui-slider-end: ${endPercent}%;${styleSuffix(options.attrs?.style)}`,
104
- },
105
- }),
106
- track: createUiDomPlan('div', 'slider-track', { attrs: { 'aria-hidden': 'true' } }),
107
- range: createUiDomPlan('span', 'slider-range', { attrs: { 'aria-hidden': 'true' } }),
108
- thumbs: thumbValues.map((value, index) => createUiDomPlan('input', 'slider-thumb', {
109
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
110
- ...(options.readonly === undefined ? {} : { readonly: options.readonly }),
111
- attrs: {
112
- id: `${options.id}-${mode === 'range' ? (index === 0 ? 'start' : 'end') : 'thumb'}`,
113
- type: 'range',
114
- name: sliderThumbName(options.name, mode, index),
115
- min,
116
- max,
117
- step,
118
- value,
119
- 'aria-label': sliderThumbLabel(options.label, mode, index),
120
- 'aria-valuetext': sliderValueText(options.valueText, value, index),
121
- },
122
- })),
123
- marks: (options.marks ?? []).map((mark) => createUiDomPlan('span', 'slider-mark', {
124
- attrs: {
125
- 'aria-hidden': mark.label === undefined ? 'true' : undefined,
126
- 'data-value': clampNumber(mark.value, min, max),
127
- 'data-label': mark.label,
128
- style: `--kui-slider-mark: ${sliderPercent(mark.value, min, max)}%;`,
129
- },
130
- })),
131
- };
132
- }
133
- export function getNextSliderValue(value, key, options = {}) {
134
- const min = finiteOr(options.min, 0);
135
- const max = Math.max(min, finiteOr(options.max, 100));
136
- const step = Math.max(1, finiteOr(options.step, 1));
137
- if (key === 'Home')
138
- return min;
139
- if (key === 'End')
140
- return max;
141
- if (key === 'PageUp')
142
- return clampNumber(value + step * 10, min, max);
143
- if (key === 'PageDown')
144
- return clampNumber(value - step * 10, min, max);
145
- if (key === 'ArrowRight' || key === 'ArrowUp')
146
- return clampNumber(value + step, min, max);
147
- if (key === 'ArrowLeft' || key === 'ArrowDown')
148
- return clampNumber(value - step, min, max);
149
- return clampNumber(value, min, max);
150
- }
151
- export function createSliderTooltipFormatterStateContract(options) {
152
- const min = finiteOr(options.min, 0);
153
- const max = Math.max(min, finiteOr(options.max, 100));
154
- const values = sliderValues(options.value, min, max);
155
- const mode = isSliderRangeValue(options.value) ? 'range' : 'single';
156
- const thumbValues = mode === 'single' ? [values[0]] : values;
157
- const formattedValues = sliderContractFormattedValues(options.formattedValue, values);
158
- const formatState = sliderHasFormattedValue(options.formattedValue)
159
- ? 'formatted'
160
- : 'raw';
161
- const tooltipVisibility = sliderContractTooltipVisibility(options.tooltipVisible, thumbValues.length);
162
- const tooltipVisibleCount = tooltipVisibility.filter(Boolean).length;
163
- const tooltipState = tooltipVisibleCount === 0
164
- ? 'hidden'
165
- : tooltipVisibleCount === thumbValues.length
166
- ? 'visible'
167
- : 'partial';
168
- const filterSummary = options.filterSummary?.trim() ?? '';
169
- const filterSummaryState = filterSummary === '' ? 'empty' : 'ready';
170
- const thumbs = thumbValues.map((value, index) => {
171
- const id = `${options.id}-${mode === 'range' ? (index === 0 ? 'start' : 'end') : 'thumb'}`;
172
- const tooltipId = `${id}-tooltip`;
173
- const formattedValue = formattedValues[index] ?? String(value);
174
- const tooltipVisible = tooltipVisibility[index] === true;
175
- return {
176
- id,
177
- value,
178
- formattedValue,
179
- tooltipVisible,
180
- attrs: {
181
- 'data-tooltip-visible': tooltipVisible ? 'true' : undefined,
182
- 'data-tooltip-id': tooltipId,
183
- 'data-formatted-value': formattedValue,
184
- 'aria-valuetext': formattedValue,
185
- 'aria-describedby': tooltipVisible ? tooltipId : undefined,
186
- },
187
- tooltipAttrs: {
188
- id: tooltipId,
189
- role: 'tooltip',
190
- hidden: tooltipVisible ? undefined : true,
191
- 'data-tooltip-visible': tooltipVisible ? 'true' : undefined,
192
- 'data-formatted-value': formattedValue,
193
- },
194
- };
195
- });
196
- return {
197
- mode,
198
- tooltipState,
199
- formatState,
200
- filterSummaryState,
201
- tooltipVisibleCount,
202
- formattedValues,
203
- valueText: mode === 'single' ? formattedValues[0] : formattedValues,
204
- filterSummary,
205
- attrs: {
206
- 'data-slider-tooltip-formatter-contract': 'true',
207
- 'data-slider-tooltip-state': tooltipState,
208
- 'data-slider-format-state': formatState,
209
- 'data-slider-filter-summary-state': filterSummaryState,
210
- 'data-tooltip-visible-count': tooltipVisibleCount,
211
- 'data-formatted-value-start': formattedValues[0],
212
- 'data-formatted-value-end': formattedValues[1],
213
- 'data-filter-summary': filterSummary === '' ? undefined : filterSummary,
214
- },
215
- thumbs,
216
- };
217
- }
218
- export function createSegmentedPlans(options) {
219
- const enabled = options.options.filter((option) => option.disabled !== true);
220
- const value = enabled.some((option) => option.value === options.value)
221
- ? options.value
222
- : enabled[0]?.value;
223
- return {
224
- root: createUiDomPlan('div', 'segmented', {
225
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
226
- ...(options.className === undefined ? {} : { className: options.className }),
227
- attrs: {
228
- id: options.id,
229
- role: 'radiogroup',
230
- 'aria-label': options.label,
231
- ...options.attrs,
232
- 'data-orientation': options.orientation ?? 'horizontal',
233
- 'data-value': value,
234
- 'data-count': options.options.length,
235
- },
236
- }),
237
- items: options.options.map((option) => {
238
- const checked = option.value === value;
239
- const disabled = options.disabled === true || option.disabled === true;
240
- return createUiDomPlan('button', 'segmented-item', {
241
- disabled,
242
- state: disabled ? 'disabled' : checked ? 'checked' : 'idle',
243
- attrs: {
244
- id: `${options.id}-${option.id}`,
245
- type: 'button',
246
- role: 'radio',
247
- tabindex: checked && !disabled ? 0 : -1,
248
- 'aria-checked': checked ? 'true' : 'false',
249
- 'data-option-id': option.id,
250
- 'data-value': option.value,
251
- 'data-label': option.label,
252
- 'data-icon': option.icon === true ? 'true' : undefined,
253
- },
254
- });
255
- }),
256
- icons: options.options.map((option) => option.icon === true
257
- ? createUiDomPlan('span', 'segmented-icon', {
258
- attrs: { 'aria-hidden': 'true', 'data-option-id': option.id },
259
- })
260
- : undefined),
261
- };
262
- }
263
- export function getNextSegmentedValue(options, value, key, orientation = 'horizontal') {
264
- return getNextSelectionValue(options, value, key, orientation);
265
- }
266
- export function createSegmentedResponsiveStateContract(options) {
267
- const ids = options.options.map((option) => option.id);
268
- const knownIds = new Set(ids);
269
- const optionIconIds = options.options
270
- .filter((option) => option.icon === true)
271
- .map((option) => option.id);
272
- const iconIds = normalizeKnownIds([...optionIconIds, ...(options.iconIds ?? [])], knownIds);
273
- const overflowIds = normalizeKnownIds(options.overflowIds, knownIds);
274
- const overflowSet = new Set(overflowIds);
275
- const visibleInputIds = options.visibleIds === undefined ? ids : normalizeKnownIds(options.visibleIds, knownIds);
276
- const visibleIds = visibleInputIds.filter((id) => !overflowSet.has(id));
277
- const iconSet = new Set(iconIds);
278
- const visibleSet = new Set(visibleIds);
279
- const wrapState = options.wrapped === true ? 'wrapped' : 'nowrap';
280
- const overflowState = options.options.length === 0 ? 'empty' : overflowIds.length > 0 ? 'overflow' : 'expanded';
281
- const items = options.options.map((option) => {
282
- const icon = iconSet.has(option.id);
283
- const overflow = overflowSet.has(option.id);
284
- const visible = visibleSet.has(option.id);
285
- return {
286
- id: option.id,
287
- option: icon === option.icon ? option : { ...option, icon },
288
- icon,
289
- visible,
290
- overflow,
291
- attrs: {
292
- 'data-option-id': option.id,
293
- 'data-icon': icon ? 'true' : undefined,
294
- 'data-visible': visible ? 'true' : undefined,
295
- 'data-overflow': overflow ? 'true' : undefined,
296
- hidden: visible ? undefined : true,
297
- 'aria-hidden': visible ? undefined : 'true',
298
- },
299
- };
300
- });
301
- return {
302
- overflowState,
303
- wrapState,
304
- itemCount: options.options.length,
305
- iconCount: iconIds.length,
306
- visibleCount: visibleIds.length,
307
- overflowCount: overflowIds.length,
308
- iconIds,
309
- visibleIds,
310
- overflowIds,
311
- attrs: {
312
- 'data-segmented-responsive-state-contract': 'true',
313
- 'data-segmented-overflow-state': overflowState,
314
- 'data-segmented-wrap-state': wrapState,
315
- 'data-icon-count': iconIds.length,
316
- 'data-visible-count': visibleIds.length,
317
- 'data-overflow-count': overflowIds.length,
318
- 'data-icon-ids': iconIds.join(','),
319
- 'data-visible-ids': visibleIds.join(','),
320
- 'data-overflow-ids': overflowIds.join(','),
321
- },
322
- items,
323
- };
324
- }
325
- export function createRadioGroupPlans(options) {
326
- return createChoiceGroupPlans('radio', options);
327
- }
328
- export function createCheckboxGroupPlans(options) {
329
- return createChoiceGroupPlans('checkbox', options);
330
- }
331
- export function getNextRadioGroupValue(options, value, key, orientation = 'vertical') {
332
- return getNextSelectionValue(options, value, key, orientation);
333
- }
334
- export function toggleCheckboxGroupValue(values, value) {
335
- const next = new Set(values);
336
- if (next.has(value)) {
337
- next.delete(value);
338
- }
339
- else {
340
- next.add(value);
341
- }
342
- return next;
343
- }
344
- export function createChoiceGroupCardOptionStateContract(options) {
345
- const ids = options.options.map((option) => option.id);
346
- const knownIds = new Set(ids);
347
- const selectedValues = options.type === 'radio'
348
- ? new Set(options.value === undefined ? [] : [options.value])
349
- : valueSet(options.values);
350
- const selectedIds = options.options
351
- .filter((option) => selectedValues.has(option.value))
352
- .map((option) => option.id);
353
- const disabledIds = options.options
354
- .filter((option) => option.disabled === true)
355
- .map((option) => option.id);
356
- const selectedSet = new Set(selectedIds);
357
- const disabledSet = new Set(disabledIds);
358
- const cardIds = normalizeKnownIds(options.cardIds, knownIds);
359
- const selectedEmphasisIds = normalizeKnownIds(options.selectedEmphasisIds ?? selectedIds, knownIds).filter((id) => selectedSet.has(id));
360
- const disabledEmphasisIds = normalizeKnownIds(options.disabledEmphasisIds ?? disabledIds, knownIds).filter((id) => disabledSet.has(id));
361
- const descriptionIds = normalizeKnownIds(options.descriptionIds ??
362
- options.options
363
- .filter((option) => option.description !== undefined)
364
- .map((option) => option.id), knownIds);
365
- const optionById = new Map(options.options.map((option) => [option.id, option]));
366
- const filteredDescriptionIds = descriptionIds.filter((id) => optionById.get(id)?.description !== undefined);
367
- const actionIds = normalizeKnownIds(options.actionIds, knownIds);
368
- const actionSet = new Set(actionIds);
369
- const actionDisabledIds = normalizeKnownIds(options.actionDisabledIds, knownIds).filter((id) => actionSet.has(id));
370
- const cardSet = new Set(cardIds);
371
- const selectedEmphasisSet = new Set(selectedEmphasisIds);
372
- const disabledEmphasisSet = new Set(disabledEmphasisIds);
373
- const descriptionSet = new Set(filteredDescriptionIds);
374
- const actionDisabledSet = new Set(actionDisabledIds);
375
- const state = options.options.length === 0
376
- ? 'empty'
377
- : cardIds.length === 0
378
- ? 'plain'
379
- : cardIds.length === options.options.length
380
- ? 'cards'
381
- : 'partial';
382
- const items = options.options.map((option) => {
383
- const card = cardSet.has(option.id);
384
- const selected = selectedSet.has(option.id);
385
- const disabled = disabledSet.has(option.id);
386
- const selectedEmphasis = selectedEmphasisSet.has(option.id);
387
- const disabledEmphasis = disabledEmphasisSet.has(option.id);
388
- const descriptionState = descriptionSet.has(option.id)
389
- ? 'visible'
390
- : 'hidden';
391
- const actionState = !actionSet.has(option.id)
392
- ? 'hidden'
393
- : actionDisabledSet.has(option.id) || disabled
394
- ? 'disabled'
395
- : 'ready';
396
- const optionState = disabled
397
- ? 'disabled'
398
- : selected
399
- ? 'selected'
400
- : 'idle';
401
- return {
402
- id: option.id,
403
- option,
404
- state: optionState,
405
- actionState,
406
- descriptionState,
407
- card,
408
- selectedEmphasis,
409
- disabledEmphasis,
410
- attrs: {
411
- 'data-card-option': card ? 'true' : undefined,
412
- 'data-card-option-state': optionState,
413
- 'data-selected-emphasis': selectedEmphasis ? 'true' : undefined,
414
- 'data-disabled-emphasis': disabledEmphasis ? 'true' : undefined,
415
- 'data-description-state': descriptionState,
416
- 'data-action-state': actionState,
417
- 'aria-disabled': disabled ? 'true' : undefined,
418
- },
419
- actionAttrs: {
420
- type: 'button',
421
- 'data-option-id': option.id,
422
- 'data-action-state': actionState,
423
- hidden: actionState === 'hidden' ? true : undefined,
424
- disabled: actionState === 'disabled' ? true : undefined,
425
- 'aria-disabled': actionState === 'disabled' ? 'true' : undefined,
426
- },
427
- };
428
- });
429
- return {
430
- type: options.type,
431
- state,
432
- optionCount: options.options.length,
433
- cardCount: cardIds.length,
434
- selectedEmphasisCount: selectedEmphasisIds.length,
435
- disabledEmphasisCount: disabledEmphasisIds.length,
436
- descriptionCount: filteredDescriptionIds.length,
437
- actionCount: actionIds.length,
438
- actionDisabledCount: actionDisabledIds.length,
439
- cardIds,
440
- selectedEmphasisIds,
441
- disabledEmphasisIds,
442
- descriptionIds: filteredDescriptionIds,
443
- actionIds,
444
- actionDisabledIds,
445
- attrs: {
446
- 'data-choice-group-card-option-contract': 'true',
447
- 'data-choice-type': options.type,
448
- 'data-card-state': state,
449
- 'data-card-count': cardIds.length,
450
- 'data-selected-emphasis-count': selectedEmphasisIds.length,
451
- 'data-disabled-emphasis-count': disabledEmphasisIds.length,
452
- 'data-description-count': descriptionIds.length,
453
- 'data-action-count': actionIds.length,
454
- 'data-action-disabled-count': actionDisabledIds.length,
455
- 'data-card-ids': cardIds.join(','),
456
- 'data-selected-emphasis-ids': selectedEmphasisIds.join(','),
457
- 'data-disabled-emphasis-ids': disabledEmphasisIds.join(','),
458
- 'data-description-ids': filteredDescriptionIds.join(','),
459
- 'data-action-ids': actionIds.join(','),
460
- 'data-action-disabled-ids': actionDisabledIds.join(','),
461
- },
462
- items,
463
- };
464
- }
465
- export function createChoiceGroupBulkSelectionContract(options) {
466
- const disabled = options.disabled === true;
467
- const disabledReason = options.disabledReason?.trim() ?? '';
468
- const selectedValues = options.type === 'radio'
469
- ? new Set(options.value === undefined ? [] : [options.value])
470
- : valueSet(options.values);
471
- const indeterminateValues = valueSet(options.indeterminateValues);
472
- const enabledOptions = options.options.filter((option) => option.disabled !== true);
473
- const enabledValues = new Set(enabledOptions.map((option) => option.value));
474
- const checkedOptions = options.options.filter((option) => selectedValues.has(option.value));
475
- const indeterminateOptions = options.options.filter((option) => indeterminateValues.has(option.value));
476
- const checkedIds = checkedOptions.map((option) => option.id);
477
- const indeterminateIds = indeterminateOptions.map((option) => option.id);
478
- const checkedEnabledCount = checkedOptions.filter((option) => enabledValues.has(option.value)).length;
479
- const indeterminateEnabledCount = indeterminateOptions.filter((option) => enabledValues.has(option.value)).length;
480
- const state = choiceGroupBulkSelectionState({
481
- checkedCount: checkedEnabledCount,
482
- disabled,
483
- enabledCount: enabledOptions.length,
484
- indeterminateCount: indeterminateEnabledCount,
485
- totalCount: options.options.length,
486
- });
487
- const selectAllState = choiceGroupBulkSelectAllState({
488
- state,
489
- type: options.type,
490
- });
491
- const canSelectAll = options.type === 'checkbox' && !disabled && enabledOptions.length > 0;
492
- const nextCheckedIds = canSelectAll && selectAllState !== 'checked' ? enabledOptions.map((option) => option.id) : [];
493
- const attrs = {
494
- 'data-choice-group-bulk-selection-contract': 'true',
495
- 'data-choice-type': options.type,
496
- 'data-selection-state': state,
497
- 'data-select-all-state': selectAllState,
498
- 'data-total-count': options.options.length,
499
- 'data-enabled-count': enabledOptions.length,
500
- 'data-checked-count': checkedIds.length,
501
- 'data-indeterminate-count': indeterminateIds.length,
502
- 'data-checked-ids': checkedIds.join(','),
503
- 'data-indeterminate-ids': indeterminateIds.join(','),
504
- 'data-next-checked-ids': nextCheckedIds.join(','),
505
- 'data-disabled-reason': disabledReason === '' ? undefined : disabledReason,
506
- 'data-can-select-all': canSelectAll ? 'true' : undefined,
507
- 'aria-disabled': disabled ? 'true' : undefined,
508
- };
509
- return {
510
- type: options.type,
511
- state,
512
- selectAllState,
513
- totalCount: options.options.length,
514
- enabledCount: enabledOptions.length,
515
- checkedCount: checkedIds.length,
516
- indeterminateCount: indeterminateIds.length,
517
- checkedIds,
518
- indeterminateIds,
519
- nextCheckedIds,
520
- disabledReason,
521
- canSelectAll,
522
- attrs,
523
- selectAllAttrs: {
524
- id: `${options.id}-select-all`,
525
- type: 'checkbox',
526
- role: 'checkbox',
527
- 'aria-label': options.selectAllLabel ?? 'Select all options',
528
- 'aria-checked': selectAllState === 'mixed' ? 'mixed' : selectAllState === 'checked' ? 'true' : 'false',
529
- disabled: canSelectAll ? undefined : true,
530
- 'aria-disabled': canSelectAll ? undefined : 'true',
531
- 'data-select-all-state': selectAllState,
532
- 'data-next-checked-ids': nextCheckedIds.join(','),
533
- },
534
- optionContracts: options.options.map((option) => {
535
- const checked = selectedValues.has(option.value);
536
- const indeterminate = options.type === 'checkbox' && indeterminateValues.has(option.value);
537
- const optionDisabled = disabled || option.disabled === true;
538
- return {
539
- id: option.id,
540
- value: option.value,
541
- checked,
542
- indeterminate,
543
- disabled: optionDisabled,
544
- attrs: {
545
- 'data-option-id': option.id,
546
- 'data-value': option.value,
547
- 'data-checked': checked ? 'true' : undefined,
548
- 'data-indeterminate': indeterminate ? 'true' : undefined,
549
- 'data-disabled-reason': optionDisabled && disabledReason !== '' ? disabledReason : undefined,
550
- 'aria-checked': indeterminate ? 'mixed' : checked ? 'true' : 'false',
551
- 'aria-disabled': optionDisabled ? 'true' : undefined,
552
- },
553
- };
554
- }),
555
- };
556
- }
557
- export function createFormUi(schema, options) {
558
- const form = createForm(schema, options.initialValues);
559
- const renderer = createFormRenderer(form, options.renderer);
560
- const view = createFormView(renderer, { id: options.id });
561
- return createFormUiFromView(view, options);
562
- }
563
- export function createFormUiFromView(view, options) {
564
- return {
565
- form: view.renderer.form,
566
- renderer: view.renderer,
567
- view,
568
- root: createFormRootPlan(view),
569
- fields: view.items.map((item) => createFormFieldPlan(item, options.descriptions?.[item.name])),
570
- errorSummary: createErrorSummaryPlan(view),
571
- submit: (callback) => view.submit(callback),
572
- };
573
- }
574
- export function applyFormServerErrors(view, errors) {
575
- for (const item of view.items) {
576
- const value = errors[item.name];
577
- if (value === undefined)
578
- continue;
579
- item.errors.value = Array.isArray(value) ? value : [value];
580
- item.touched.value = true;
581
- }
582
- }
583
- export function readFirstErrorFocusTarget(view) {
584
- return view.errorSummary.value[0]?.href;
585
- }
586
- export function createFormRootPlan(view) {
587
- return createUiDomPlan('form', 'form', {
588
- loading: view.submitting.value,
589
- state: view.submitting.value ? 'submitting' : 'idle',
590
- ...(view.submitting.value ? { className: 'kui-form--submitting' } : {}),
591
- attrs: { id: view.id, novalidate: true },
592
- });
593
- }
594
- export function createErrorSummaryPlan(view) {
595
- const items = view.errorSummary.value;
596
- return {
597
- root: createUiDomPlan('div', 'error-summary', {
598
- invalid: items.length > 0,
599
- state: items.length > 0 ? 'invalid' : 'idle',
600
- attrs: {
601
- id: view.errorSummaryId,
602
- role: 'alert',
603
- 'aria-labelledby': view.errorSummaryLabelId,
604
- hidden: items.length === 0 ? true : undefined,
605
- },
606
- }),
607
- title: createUiDomPlan('h2', 'error-summary-title', {
608
- attrs: { id: view.errorSummaryLabelId },
609
- }),
610
- items,
611
- };
612
- }
613
- export function createFormFieldPlan(item, description) {
614
- const descriptionId = description === undefined ? undefined : `${item.controlId}-description`;
615
- const invalid = item.errors.value.length > 0;
616
- const disabled = item.disabled.value;
617
- const describedBy = joinIds(descriptionId, item.ariaDescribedBy.value);
618
- return {
619
- name: item.name,
620
- label: item.labelText.value,
621
- control: item.control,
622
- root: createUiDomPlan('div', 'form-field', {
623
- invalid,
624
- disabled,
625
- state: invalid ? 'invalid' : disabled ? 'disabled' : 'idle',
626
- attrs: { id: item.id },
627
- }),
628
- labelPlan: createUiDomPlan('label', 'form-label', {
629
- attrs: {
630
- id: item.labelId,
631
- for: item.control === 'custom' ? undefined : item.controlId,
632
- },
633
- }),
634
- ...(description === undefined
635
- ? {}
636
- : {
637
- descriptionText: description,
638
- description: createUiDomPlan('p', 'form-description', {
639
- attrs: { id: descriptionId },
640
- }),
641
- }),
642
- controlPlan: createControlPlan(item, describedBy),
643
- error: createUiDomPlan('p', 'form-error', {
644
- invalid,
645
- state: invalid ? 'invalid' : 'idle',
646
- attrs: {
647
- id: item.errorId,
648
- role: invalid ? 'alert' : undefined,
649
- hidden: invalid ? undefined : true,
650
- },
651
- }),
652
- errorText: item.errorText.value,
653
- };
654
- }
655
- function createControlPlan(item, describedBy) {
656
- const common = {
657
- name: item.name,
658
- required: item.required.value,
659
- disabled: item.disabled.value,
660
- invalid: item.errors.value.length > 0,
661
- attrs: controlAttrs(item, describedBy),
662
- };
663
- switch (item.control) {
664
- case 'checkbox':
665
- return createChoicePrimitive({
666
- type: 'checkbox',
667
- checked: item.model.value === true,
668
- value: 'true',
669
- ...common,
670
- });
671
- case 'select':
672
- // value carries the current selection (schema defaultValue / form model) so templates can render option selected
673
- return createSelectPrimitive({
674
- ...common,
675
- value: item.model.value,
676
- });
677
- case 'textarea':
678
- return createTextareaPrimitive({
679
- ...common,
680
- ...textValueOption(item.model.value),
681
- });
682
- case 'custom':
683
- return createUiDomPlan('div', 'custom-field', {
684
- invalid: common.invalid,
685
- disabled: common.disabled,
686
- state: common.invalid ? 'invalid' : common.disabled ? 'disabled' : 'idle',
687
- attrs: {
688
- id: item.controlId,
689
- role: 'group',
690
- 'aria-labelledby': item.labelId,
691
- 'aria-describedby': describedBy,
692
- },
693
- });
694
- default:
695
- return createInputPrimitive({
696
- type: normalizeInputType(item.inputType),
697
- ...common,
698
- ...textValueOption(item.model.value),
699
- });
700
- }
701
- }
702
- function controlAttrs(item, describedBy) {
703
- return {
704
- id: item.controlId,
705
- 'aria-labelledby': item.labelId,
706
- 'aria-describedby': describedBy,
707
- 'aria-required': item.ariaRequired.value,
708
- 'aria-invalid': item.ariaInvalid.value,
709
- 'aria-disabled': item.ariaDisabled.value,
710
- };
711
- }
712
- function normalizeInputType(value) {
713
- switch (value) {
714
- case 'date':
715
- case 'email':
716
- case 'number':
717
- case 'password':
718
- case 'search':
719
- case 'url':
720
- return value;
721
- default:
722
- return 'text';
723
- }
724
- }
725
- function textValueOption(value) {
726
- return typeof value === 'number' || typeof value === 'string' ? { value } : {};
727
- }
728
- function joinIds(...ids) {
729
- return classNames(...ids);
730
- }
731
- function createChoiceGroupPlans(type, options) {
732
- const selectedValues = type === 'radio'
733
- ? new Set(options.value === undefined ? [] : [options.value])
734
- : valueSet(options.values);
735
- const indeterminateValues = valueSet(options.indeterminateValues);
736
- return {
737
- root: createUiDomPlan('fieldset', `${type}-group`, {
738
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
739
- ...(options.className === undefined ? {} : { className: options.className }),
740
- attrs: {
741
- id: options.id,
742
- ...options.attrs,
743
- 'data-orientation': options.orientation ?? 'vertical',
744
- 'data-count': options.options.length,
745
- 'data-selected-count': selectedValues.size,
746
- },
747
- }),
748
- legend: createUiDomPlan('legend', `${type}-group-legend`, {
749
- attrs: { id: `${options.id}-legend`, 'data-label': options.legend },
750
- }),
751
- options: options.options.map((option) => {
752
- const disabled = options.disabled === true || option.disabled === true;
753
- const checked = selectedValues.has(option.value);
754
- return createUiDomPlan('label', `${type}-group-option`, {
755
- disabled,
756
- state: disabled ? 'disabled' : checked ? 'checked' : 'idle',
757
- attrs: {
758
- id: `${options.id}-${option.id}-option`,
759
- for: `${options.id}-${option.id}`,
760
- 'data-option-id': option.id,
761
- 'data-value': option.value,
762
- 'data-label': option.label,
763
- 'data-description': option.description,
764
- 'data-indeterminate': type === 'checkbox' && indeterminateValues.has(option.value) ? 'true' : undefined,
765
- },
766
- });
767
- }),
768
- inputs: options.options.map((option) => {
769
- const disabled = options.disabled === true || option.disabled === true;
770
- const checked = selectedValues.has(option.value);
771
- return createChoicePrimitive({
772
- type,
773
- name: options.name,
774
- value: option.value,
775
- checked,
776
- disabled,
777
- attrs: {
778
- id: `${options.id}-${option.id}`,
779
- 'aria-describedby': option.description === undefined ? undefined : `${options.id}-${option.id}-description`,
780
- 'aria-checked': type === 'checkbox' && indeterminateValues.has(option.value)
781
- ? 'mixed'
782
- : checked
783
- ? 'true'
784
- : 'false',
785
- 'data-option-id': option.id,
786
- 'data-label': option.label,
787
- },
788
- });
789
- }),
790
- descriptions: options.options.map((option) => option.description === undefined
791
- ? undefined
792
- : createUiDomPlan('span', `${type}-group-description`, {
793
- attrs: {
794
- id: `${options.id}-${option.id}-description`,
795
- 'data-description': option.description,
796
- },
797
- })),
798
- };
799
- }
800
- function sliderValues(value, min, max) {
801
- if (isSliderRangeValue(value)) {
802
- const start = clampNumber(value[0] ?? min, min, max);
803
- const end = clampNumber(value[1] ?? max, min, max);
804
- return start <= end ? [start, end] : [end, start];
805
- }
806
- const current = clampNumber(value, min, max);
807
- return [current, current];
808
- }
809
- function sliderPercent(value, min, max) {
810
- if (max <= min)
811
- return 0;
812
- return Math.round(((clampNumber(value, min, max) - min) / (max - min)) * 10000) / 100;
813
- }
814
- function sliderThumbName(name, mode, index) {
815
- if (name === undefined)
816
- return undefined;
817
- if (mode === 'single')
818
- return name;
819
- return `${name}-${index === 0 ? 'start' : 'end'}`;
820
- }
821
- function sliderThumbLabel(label, mode, index) {
822
- if (mode === 'single')
823
- return label ?? 'Slider value';
824
- return `${label ?? 'Slider'} ${index === 0 ? 'start' : 'end'}`;
825
- }
826
- function sliderValueText(valueText, value, index) {
827
- if (isSliderValueTextRange(valueText))
828
- return valueText[index];
829
- return valueText ?? String(value);
830
- }
831
- function sliderContractFormattedValues(value, values) {
832
- if (value !== undefined && typeof value !== 'string') {
833
- return [value[0].trim() || String(values[0]), value[1].trim() || String(values[1])];
834
- }
835
- const formatted = typeof value === 'string' ? value.trim() : '';
836
- return [formatted || String(values[0]), formatted || String(values[1])];
837
- }
838
- function sliderContractTooltipVisibility(value, count) {
839
- if (value !== undefined && typeof value !== 'boolean')
840
- return value.slice(0, count);
841
- return Array.from({ length: count }, () => value === true);
842
- }
843
- function sliderHasFormattedValue(value) {
844
- if (value === undefined)
845
- return false;
846
- return typeof value === 'string' ? value.trim() !== '' : value.some((item) => item.trim() !== '');
847
- }
848
- function isSliderRangeValue(value) {
849
- return Array.isArray(value);
850
- }
851
- function isSliderValueTextRange(value) {
852
- return Array.isArray(value);
853
- }
854
- function getNextSelectionValue(options, value, key, orientation) {
855
- const enabled = options.filter((option) => option.disabled !== true);
856
- if (enabled.length === 0)
857
- return value;
858
- const currentIndex = Math.max(0, enabled.findIndex((option) => option.value === value));
859
- const previousKey = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
860
- const nextKey = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
861
- if (key === 'Home')
862
- return enabled[0]?.value;
863
- if (key === 'End')
864
- return enabled.at(-1)?.value;
865
- if (key === previousKey)
866
- return enabled.at(currentIndex - 1)?.value ?? enabled.at(-1)?.value;
867
- if (key === nextKey)
868
- return enabled[(currentIndex + 1) % enabled.length]?.value;
869
- return value;
870
- }
871
- function valueSet(value) {
872
- if (value === undefined)
873
- return new Set();
874
- return value instanceof Set ? value : new Set(value);
875
- }
876
- function normalizeKnownIds(value, knownIds) {
877
- if (value === undefined)
878
- return [];
879
- const result = [];
880
- const seen = new Set();
881
- for (const id of value) {
882
- if (!knownIds.has(id) || seen.has(id))
883
- continue;
884
- seen.add(id);
885
- result.push(id);
886
- }
887
- return result;
888
- }
889
- function choiceGroupBulkSelectionState(options) {
890
- if (options.disabled)
891
- return 'disabled';
892
- if (options.totalCount === 0)
893
- return 'empty';
894
- if (options.enabledCount > 0 &&
895
- options.checkedCount >= options.enabledCount &&
896
- options.indeterminateCount === 0) {
897
- return 'all';
898
- }
899
- if (options.checkedCount > 0 || options.indeterminateCount > 0)
900
- return 'partial';
901
- return 'none';
902
- }
903
- function choiceGroupBulkSelectAllState(options) {
904
- if (options.type === 'radio' || options.state === 'disabled' || options.state === 'empty') {
905
- return 'disabled';
906
- }
907
- if (options.state === 'all')
908
- return 'checked';
909
- if (options.state === 'partial')
910
- return 'mixed';
911
- return 'unchecked';
912
- }
913
- function finiteOr(value, fallback) {
914
- return value === undefined || !Number.isFinite(value) ? fallback : value;
915
- }
916
- function normalizeCount(value) {
917
- if (value === undefined || !Number.isFinite(value))
918
- return 0;
919
- return Math.max(0, Math.trunc(value));
920
- }
921
- function formDrawerSubmissionState(options) {
922
- if (options.submitting)
923
- return 'submitting';
924
- if (options.disabled)
925
- return 'disabled';
926
- if (options.serverErrorCount > 0)
927
- return 'server-error';
928
- if (options.invalid)
929
- return 'invalid';
930
- if (options.success)
931
- return 'success';
932
- if (options.dirty)
933
- return 'dirty';
934
- return 'idle';
935
- }
936
- function clampNumber(value, min, max) {
937
- if (!Number.isFinite(value))
938
- return min;
939
- return Math.min(Math.max(value, min), max);
940
- }
941
- function styleSuffix(value) {
942
- if (typeof value !== 'string' || value.trim() === '')
943
- return '';
944
- return value.trim().endsWith(';') ? value.trim() : `${value.trim()};`;
945
- }
946
- export function createSettingRowPlans(options) {
947
- const kind = options.control ?? 'select';
948
- const control = createUiDomPlan('button', 'setting-row-control', {
949
- state: options.danger === true ? 'danger' : kind === 'icon-button' ? 'icon' : 'idle',
950
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
951
- attrs: {
952
- id: `${options.id}-control`,
953
- type: 'button',
954
- ...(kind === 'select' ? { 'data-kind': 'select' } : {}),
955
- ...(options.controlLabel === undefined ? {} : { 'data-label': options.controlLabel }),
956
- },
957
- });
958
- return {
959
- root: createUiDomPlan('div', 'setting-row', {
960
- ...(options.className === undefined ? {} : { className: options.className }),
961
- attrs: { id: `${options.id}-row`, ...options.attrs },
962
- }),
963
- main: createUiDomPlan('div', 'setting-row-main', {
964
- attrs: { id: `${options.id}-main` },
965
- }),
966
- title: createUiDomPlan('span', 'setting-row-title', {
967
- attrs: { id: `${options.id}-title`, 'data-label': options.title },
968
- }),
969
- description: createUiDomPlan('span', 'setting-row-description', {
970
- attrs: {
971
- id: `${options.id}-desc`,
972
- ...(options.description === undefined ? {} : { 'data-label': options.description }),
973
- },
974
- }),
975
- control,
976
- };
977
- }
978
- export function createSettingRowGroupPlans(options) {
979
- return {
980
- ...createUiDomPlan('div', 'setting-row-group', {
981
- attrs: { id: `${options.id}-group` },
982
- }),
983
- groupId: options.id,
984
- groupLabel: createUiDomPlan('span', 'setting-row-group-label', {
985
- attrs: { id: `${options.id}-group-label`, 'data-label': options.label },
986
- }),
987
- panel: createUiDomPlan('div', 'setting-row-panel', {
988
- attrs: { id: `${options.id}-panel` },
989
- }),
990
- rows: options.rows.map((row) => createSettingRowPlans(row)),
991
- };
992
- }
993
- export function createRatePlans(options) {
994
- const max = options.max ?? 5;
995
- const value = Math.max(0, Math.min(max, options.value ?? 0));
996
- const stars = [];
997
- for (let index = 1; index <= max; index += 1) {
998
- stars.push(createUiDomPlan('button', 'rate-star', {
999
- state: index <= value ? 'active' : 'idle',
1000
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
1001
- attrs: {
1002
- id: `${options.id}-star-${index}`,
1003
- type: 'button',
1004
- 'data-index': String(index),
1005
- 'aria-label': `Rate ${index} of ${max}`,
1006
- 'aria-checked': index <= value ? 'true' : 'false',
1007
- role: 'radio',
1008
- },
1009
- }));
1010
- }
1011
- return {
1012
- root: createUiDomPlan('div', 'rate', {
1013
- state: options.readonly === true ? 'readonly' : 'idle',
1014
- ...(options.className === undefined ? {} : { className: options.className }),
1015
- attrs: { id: `${options.id}-rate`, role: 'radiogroup', ...options.attrs },
1016
- }),
1017
- stars,
1018
- };
1019
- }