@sectile/vue 0.2.1

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 (287) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/dist/accordion.d.ts +250 -0
  4. package/dist/accordion.d.ts.map +1 -0
  5. package/dist/accordion.js +242 -0
  6. package/dist/accordion.js.map +1 -0
  7. package/dist/alert-dialog.d.ts +16 -0
  8. package/dist/alert-dialog.d.ts.map +1 -0
  9. package/dist/alert-dialog.js +18 -0
  10. package/dist/alert-dialog.js.map +1 -0
  11. package/dist/calendar.d.ts +232 -0
  12. package/dist/calendar.d.ts.map +1 -0
  13. package/dist/calendar.js +109 -0
  14. package/dist/calendar.js.map +1 -0
  15. package/dist/carousel.d.ts +365 -0
  16. package/dist/carousel.d.ts.map +1 -0
  17. package/dist/carousel.js +140 -0
  18. package/dist/carousel.js.map +1 -0
  19. package/dist/cascade-select.d.ts +442 -0
  20. package/dist/cascade-select.d.ts.map +1 -0
  21. package/dist/cascade-select.js +111 -0
  22. package/dist/cascade-select.js.map +1 -0
  23. package/dist/checkbox-group.d.ts +206 -0
  24. package/dist/checkbox-group.d.ts.map +1 -0
  25. package/dist/checkbox-group.js +98 -0
  26. package/dist/checkbox-group.js.map +1 -0
  27. package/dist/checkbox.d.ts +152 -0
  28. package/dist/checkbox.d.ts.map +1 -0
  29. package/dist/checkbox.js +164 -0
  30. package/dist/checkbox.js.map +1 -0
  31. package/dist/color-picker.d.ts +602 -0
  32. package/dist/color-picker.d.ts.map +1 -0
  33. package/dist/color-picker.js +129 -0
  34. package/dist/color-picker.js.map +1 -0
  35. package/dist/combobox.d.ts +320 -0
  36. package/dist/combobox.d.ts.map +1 -0
  37. package/dist/combobox.js +176 -0
  38. package/dist/combobox.js.map +1 -0
  39. package/dist/date-field.d.ts +15 -0
  40. package/dist/date-field.d.ts.map +1 -0
  41. package/dist/date-field.js +19 -0
  42. package/dist/date-field.js.map +1 -0
  43. package/dist/date-picker.d.ts +554 -0
  44. package/dist/date-picker.d.ts.map +1 -0
  45. package/dist/date-picker.js +18 -0
  46. package/dist/date-picker.js.map +1 -0
  47. package/dist/date-range-field.d.ts +129 -0
  48. package/dist/date-range-field.d.ts.map +1 -0
  49. package/dist/date-range-field.js +134 -0
  50. package/dist/date-range-field.js.map +1 -0
  51. package/dist/date-range-picker.d.ts +594 -0
  52. package/dist/date-range-picker.d.ts.map +1 -0
  53. package/dist/date-range-picker.js +19 -0
  54. package/dist/date-range-picker.js.map +1 -0
  55. package/dist/date-time-field.d.ts +15 -0
  56. package/dist/date-time-field.d.ts.map +1 -0
  57. package/dist/date-time-field.js +19 -0
  58. package/dist/date-time-field.js.map +1 -0
  59. package/dist/date-time-picker.d.ts +635 -0
  60. package/dist/date-time-picker.d.ts.map +1 -0
  61. package/dist/date-time-picker.js +20 -0
  62. package/dist/date-time-picker.js.map +1 -0
  63. package/dist/date-time-range-picker.d.ts +755 -0
  64. package/dist/date-time-range-picker.d.ts.map +1 -0
  65. package/dist/date-time-range-picker.js +23 -0
  66. package/dist/date-time-range-picker.js.map +1 -0
  67. package/dist/dialog.d.ts +16 -0
  68. package/dist/dialog.d.ts.map +1 -0
  69. package/dist/dialog.js +15 -0
  70. package/dist/dialog.js.map +1 -0
  71. package/dist/disclosure.d.ts +147 -0
  72. package/dist/disclosure.d.ts.map +1 -0
  73. package/dist/disclosure.js +129 -0
  74. package/dist/disclosure.js.map +1 -0
  75. package/dist/editable.d.ts +300 -0
  76. package/dist/editable.d.ts.map +1 -0
  77. package/dist/editable.js +181 -0
  78. package/dist/editable.js.map +1 -0
  79. package/dist/feed.d.ts +216 -0
  80. package/dist/feed.d.ts.map +1 -0
  81. package/dist/feed.js +85 -0
  82. package/dist/feed.js.map +1 -0
  83. package/dist/form.d.ts +356 -0
  84. package/dist/form.d.ts.map +1 -0
  85. package/dist/form.js +752 -0
  86. package/dist/form.js.map +1 -0
  87. package/dist/grid.d.ts +254 -0
  88. package/dist/grid.d.ts.map +1 -0
  89. package/dist/grid.js +95 -0
  90. package/dist/grid.js.map +1 -0
  91. package/dist/host-provider.d.ts +46 -0
  92. package/dist/host-provider.d.ts.map +1 -0
  93. package/dist/host-provider.js +39 -0
  94. package/dist/host-provider.js.map +1 -0
  95. package/dist/index.d.ts +124 -0
  96. package/dist/index.d.ts.map +1 -0
  97. package/dist/index.js +61 -0
  98. package/dist/index.js.map +1 -0
  99. package/dist/internal/date-picker.d.ts +539 -0
  100. package/dist/internal/date-picker.d.ts.map +1 -0
  101. package/dist/internal/date-picker.js +522 -0
  102. package/dist/internal/date-picker.js.map +1 -0
  103. package/dist/internal/form-control-inventory.d.ts +154 -0
  104. package/dist/internal/form-control-inventory.d.ts.map +1 -0
  105. package/dist/internal/form-control-inventory.js +43 -0
  106. package/dist/internal/form-control-inventory.js.map +1 -0
  107. package/dist/internal/form-control.d.ts +41 -0
  108. package/dist/internal/form-control.d.ts.map +1 -0
  109. package/dist/internal/form-control.js +52 -0
  110. package/dist/internal/form-control.js.map +1 -0
  111. package/dist/internal/native-field.d.ts +49 -0
  112. package/dist/internal/native-field.d.ts.map +1 -0
  113. package/dist/internal/native-field.js +81 -0
  114. package/dist/internal/native-field.js.map +1 -0
  115. package/dist/internal/native-input.d.ts +9 -0
  116. package/dist/internal/native-input.d.ts.map +1 -0
  117. package/dist/internal/native-input.js +9 -0
  118. package/dist/internal/native-input.js.map +1 -0
  119. package/dist/internal/part-contract.d.ts +9 -0
  120. package/dist/internal/part-contract.d.ts.map +1 -0
  121. package/dist/internal/part-contract.js +12 -0
  122. package/dist/internal/part-contract.js.map +1 -0
  123. package/dist/internal/popup.d.ts +99 -0
  124. package/dist/internal/popup.d.ts.map +1 -0
  125. package/dist/internal/popup.js +238 -0
  126. package/dist/internal/popup.js.map +1 -0
  127. package/dist/internal/tabs-context.d.ts +25 -0
  128. package/dist/internal/tabs-context.d.ts.map +1 -0
  129. package/dist/internal/tabs-context.js +9 -0
  130. package/dist/internal/tabs-context.js.map +1 -0
  131. package/dist/listbox.d.ts +275 -0
  132. package/dist/listbox.d.ts.map +1 -0
  133. package/dist/listbox.js +258 -0
  134. package/dist/listbox.js.map +1 -0
  135. package/dist/menu-button.d.ts +2 -0
  136. package/dist/menu-button.d.ts.map +1 -0
  137. package/dist/menu-button.js +2 -0
  138. package/dist/menu-button.js.map +1 -0
  139. package/dist/menu.d.ts +638 -0
  140. package/dist/menu.d.ts.map +1 -0
  141. package/dist/menu.js +167 -0
  142. package/dist/menu.js.map +1 -0
  143. package/dist/menubar.d.ts +2 -0
  144. package/dist/menubar.d.ts.map +1 -0
  145. package/dist/menubar.js +2 -0
  146. package/dist/menubar.js.map +1 -0
  147. package/dist/month-picker.d.ts +353 -0
  148. package/dist/month-picker.d.ts.map +1 -0
  149. package/dist/month-picker.js +10 -0
  150. package/dist/month-picker.js.map +1 -0
  151. package/dist/month-range-picker.d.ts +393 -0
  152. package/dist/month-range-picker.d.ts.map +1 -0
  153. package/dist/month-range-picker.js +11 -0
  154. package/dist/month-range-picker.js.map +1 -0
  155. package/dist/multi-thumb-slider.d.ts +299 -0
  156. package/dist/multi-thumb-slider.d.ts.map +1 -0
  157. package/dist/multi-thumb-slider.js +204 -0
  158. package/dist/multi-thumb-slider.js.map +1 -0
  159. package/dist/navigation-menu.d.ts +114 -0
  160. package/dist/navigation-menu.d.ts.map +1 -0
  161. package/dist/navigation-menu.js +36 -0
  162. package/dist/navigation-menu.js.map +1 -0
  163. package/dist/number-field.d.ts +12 -0
  164. package/dist/number-field.d.ts.map +1 -0
  165. package/dist/number-field.js +21 -0
  166. package/dist/number-field.js.map +1 -0
  167. package/dist/pagination.d.ts +294 -0
  168. package/dist/pagination.d.ts.map +1 -0
  169. package/dist/pagination.js +145 -0
  170. package/dist/pagination.js.map +1 -0
  171. package/dist/pin-input.d.ts +207 -0
  172. package/dist/pin-input.d.ts.map +1 -0
  173. package/dist/pin-input.js +129 -0
  174. package/dist/pin-input.js.map +1 -0
  175. package/dist/popover.d.ts +17 -0
  176. package/dist/popover.d.ts.map +1 -0
  177. package/dist/popover.js +16 -0
  178. package/dist/popover.js.map +1 -0
  179. package/dist/primitive.d.ts +41 -0
  180. package/dist/primitive.d.ts.map +1 -0
  181. package/dist/primitive.js +27 -0
  182. package/dist/primitive.js.map +1 -0
  183. package/dist/quantity-field.d.ts +249 -0
  184. package/dist/quantity-field.d.ts.map +1 -0
  185. package/dist/quantity-field.js +131 -0
  186. package/dist/quantity-field.js.map +1 -0
  187. package/dist/radio-group.d.ts +225 -0
  188. package/dist/radio-group.d.ts.map +1 -0
  189. package/dist/radio-group.js +198 -0
  190. package/dist/radio-group.js.map +1 -0
  191. package/dist/range-calendar.d.ts +336 -0
  192. package/dist/range-calendar.d.ts.map +1 -0
  193. package/dist/range-calendar.js +15 -0
  194. package/dist/range-calendar.js.map +1 -0
  195. package/dist/rating.d.ts +228 -0
  196. package/dist/rating.d.ts.map +1 -0
  197. package/dist/rating.js +118 -0
  198. package/dist/rating.js.map +1 -0
  199. package/dist/reorder.d.ts +209 -0
  200. package/dist/reorder.d.ts.map +1 -0
  201. package/dist/reorder.js +186 -0
  202. package/dist/reorder.js.map +1 -0
  203. package/dist/select.d.ts +352 -0
  204. package/dist/select.d.ts.map +1 -0
  205. package/dist/select.js +191 -0
  206. package/dist/select.js.map +1 -0
  207. package/dist/slider.d.ts +269 -0
  208. package/dist/slider.d.ts.map +1 -0
  209. package/dist/slider.js +197 -0
  210. package/dist/slider.js.map +1 -0
  211. package/dist/spin-button.d.ts +272 -0
  212. package/dist/spin-button.d.ts.map +1 -0
  213. package/dist/spin-button.js +190 -0
  214. package/dist/spin-button.js.map +1 -0
  215. package/dist/stepper.d.ts +310 -0
  216. package/dist/stepper.d.ts.map +1 -0
  217. package/dist/stepper.js +87 -0
  218. package/dist/stepper.js.map +1 -0
  219. package/dist/switch.d.ts +149 -0
  220. package/dist/switch.d.ts.map +1 -0
  221. package/dist/switch.js +140 -0
  222. package/dist/switch.js.map +1 -0
  223. package/dist/tabs.d.ts +290 -0
  224. package/dist/tabs.d.ts.map +1 -0
  225. package/dist/tabs.js +258 -0
  226. package/dist/tabs.js.map +1 -0
  227. package/dist/tags-input.d.ts +297 -0
  228. package/dist/tags-input.d.ts.map +1 -0
  229. package/dist/tags-input.js +196 -0
  230. package/dist/tags-input.js.map +1 -0
  231. package/dist/text.d.ts +138 -0
  232. package/dist/text.d.ts.map +1 -0
  233. package/dist/text.js +125 -0
  234. package/dist/text.js.map +1 -0
  235. package/dist/time-field.d.ts +15 -0
  236. package/dist/time-field.d.ts.map +1 -0
  237. package/dist/time-field.js +19 -0
  238. package/dist/time-field.js.map +1 -0
  239. package/dist/time-range-field.d.ts +128 -0
  240. package/dist/time-range-field.d.ts.map +1 -0
  241. package/dist/time-range-field.js +44 -0
  242. package/dist/time-range-field.js.map +1 -0
  243. package/dist/timer.d.ts +232 -0
  244. package/dist/timer.d.ts.map +1 -0
  245. package/dist/timer.js +51 -0
  246. package/dist/timer.js.map +1 -0
  247. package/dist/toast.d.ts +189 -0
  248. package/dist/toast.d.ts.map +1 -0
  249. package/dist/toast.js +78 -0
  250. package/dist/toast.js.map +1 -0
  251. package/dist/toggle-button.d.ts +81 -0
  252. package/dist/toggle-button.d.ts.map +1 -0
  253. package/dist/toggle-button.js +75 -0
  254. package/dist/toggle-button.js.map +1 -0
  255. package/dist/toggle-group.d.ts +228 -0
  256. package/dist/toggle-group.d.ts.map +1 -0
  257. package/dist/toggle-group.js +143 -0
  258. package/dist/toggle-group.js.map +1 -0
  259. package/dist/toolbar.d.ts +202 -0
  260. package/dist/toolbar.d.ts.map +1 -0
  261. package/dist/toolbar.js +82 -0
  262. package/dist/toolbar.js.map +1 -0
  263. package/dist/tooltip.d.ts +13 -0
  264. package/dist/tooltip.d.ts.map +1 -0
  265. package/dist/tooltip.js +15 -0
  266. package/dist/tooltip.js.map +1 -0
  267. package/dist/tree-grid.d.ts +359 -0
  268. package/dist/tree-grid.d.ts.map +1 -0
  269. package/dist/tree-grid.js +125 -0
  270. package/dist/tree-grid.js.map +1 -0
  271. package/dist/tree-view.d.ts +304 -0
  272. package/dist/tree-view.d.ts.map +1 -0
  273. package/dist/tree-view.js +131 -0
  274. package/dist/tree-view.js.map +1 -0
  275. package/dist/window-splitter.d.ts +204 -0
  276. package/dist/window-splitter.d.ts.map +1 -0
  277. package/dist/window-splitter.js +82 -0
  278. package/dist/window-splitter.js.map +1 -0
  279. package/dist/year-picker.d.ts +353 -0
  280. package/dist/year-picker.d.ts.map +1 -0
  281. package/dist/year-picker.js +10 -0
  282. package/dist/year-picker.js.map +1 -0
  283. package/dist/year-range-picker.d.ts +393 -0
  284. package/dist/year-range-picker.d.ts.map +1 -0
  285. package/dist/year-range-picker.js +11 -0
  286. package/dist/year-range-picker.js.map +1 -0
  287. package/package.json +356 -0
package/dist/form.js ADDED
@@ -0,0 +1,752 @@
1
+ import { computed, defineComponent, getCurrentInstance, h, inject, mergeProps, nextTick, onBeforeUnmount, onMounted, provide, ref, shallowRef, watch, } from 'vue';
2
+ import { appendFormFieldPath, createForm, encodeFormFieldPath, } from '@sectile/dom/form';
3
+ import { Primitive } from './primitive.js';
4
+ import { useHostId } from './host-provider.js';
5
+ const formContextKey = Symbol('SectileForm');
6
+ const formFieldContextKey = Symbol('SectileFormField');
7
+ const formControlOwnerKey = Symbol('SectileFormControlOwner');
8
+ const emptyState = Object.freeze({
9
+ validationGeneration: 0,
10
+ validationStatus: 'idle',
11
+ validationTrigger: null,
12
+ validationIntent: null,
13
+ submissionGeneration: 0,
14
+ submissionStatus: 'idle',
15
+ submitCount: 0,
16
+ submitted: false,
17
+ touched: false,
18
+ dirty: false,
19
+ valid: true,
20
+ fields: Object.freeze([]),
21
+ issues: Object.freeze([]),
22
+ });
23
+ const partProps = {
24
+ as: { type: [String, Object, Function], default: 'div' },
25
+ asChild: { type: Boolean, default: false },
26
+ };
27
+ const FormRootImpl = defineComponent({
28
+ name: 'SectileFormRoot',
29
+ inheritAttrs: false,
30
+ props: {
31
+ issues: { type: Array, default: () => [] },
32
+ schema: { type: Object, default: undefined },
33
+ validate: { type: Function, default: undefined },
34
+ validateOn: {
35
+ type: Array,
36
+ default: () => [],
37
+ },
38
+ revalidateOn: {
39
+ type: Array,
40
+ default: () => ['input'],
41
+ },
42
+ onSubmit: { type: Function, default: undefined },
43
+ },
44
+ emits: {
45
+ reset: () => true,
46
+ 'state-change': (_state) => true,
47
+ },
48
+ slots: Object,
49
+ setup(props, { attrs, emit, expose, slots }) {
50
+ const root = ref(null);
51
+ const summary = ref(null);
52
+ const state = shallowRef(emptyState);
53
+ const connection = shallowRef(null);
54
+ const participants = new Map();
55
+ const sync = () => {
56
+ if (connection.value !== null)
57
+ state.value = connection.value.getSnapshot().state;
58
+ };
59
+ const register = (participant) => {
60
+ const existing = participants.get(participant.id);
61
+ existing?.unregister?.();
62
+ const registered = { participant };
63
+ participants.set(participant.id, registered);
64
+ if (connection.value !== null)
65
+ registered.unregister = connection.value.registerParticipant(participant);
66
+ return () => {
67
+ if (participants.get(participant.id) !== registered)
68
+ return;
69
+ registered.unregister?.();
70
+ participants.delete(participant.id);
71
+ sync();
72
+ };
73
+ };
74
+ const settleSubmission = (target, generation, result) => {
75
+ if (connection.value !== target)
76
+ return;
77
+ if (typeof result === 'object' && result !== null && result.ok === false) {
78
+ target.submitFailed(generation, toServerIssues(result.issues));
79
+ return;
80
+ }
81
+ target.submitSucceeded(generation);
82
+ };
83
+ const rejectSubmission = (target, generation, reason) => {
84
+ if (connection.value !== target)
85
+ return;
86
+ target.submitFailed(generation, [submissionErrorIssue(reason)]);
87
+ };
88
+ const submit = (payload) => {
89
+ const handler = props.onSubmit;
90
+ if (handler === undefined)
91
+ return;
92
+ const target = connection.value;
93
+ if (target === null)
94
+ return;
95
+ payload.event.preventDefault();
96
+ if (target.getSnapshot().state.submissionStatus === 'submitting') {
97
+ return;
98
+ }
99
+ const generation = target.submitStarted();
100
+ if (generation === null)
101
+ return;
102
+ let result;
103
+ try {
104
+ result = handler(toFormSubmitEvent(payload));
105
+ }
106
+ catch (error) {
107
+ rejectSubmission(target, generation, error);
108
+ return;
109
+ }
110
+ if (isPromiseLike(result)) {
111
+ void Promise.resolve(result).then((resolved) => settleSubmission(target, generation, resolved), (error) => rejectSubmission(target, generation, error));
112
+ return;
113
+ }
114
+ settleSubmission(target, generation, result);
115
+ };
116
+ const mount = () => {
117
+ if (root.value === null)
118
+ return;
119
+ connection.value?.destroy();
120
+ connection.value = createForm({
121
+ form: root.value,
122
+ ...(summary.value === null ? {} : { summary: summary.value }),
123
+ issues: props.issues,
124
+ ...(props.schema === undefined ? {} : { schema: props.schema }),
125
+ ...(props.validate === undefined ? {} : { validate: props.validate }),
126
+ validateOn: props.validateOn,
127
+ revalidateOn: props.revalidateOn,
128
+ onSubmit: submit,
129
+ onReset: () => emit('reset'),
130
+ onStateChange: (next) => {
131
+ state.value = next;
132
+ emit('state-change', next);
133
+ },
134
+ });
135
+ for (const registered of participants.values()) {
136
+ registered.unregister = connection.value.registerParticipant(registered.participant);
137
+ }
138
+ sync();
139
+ };
140
+ onMounted(() => { void nextTick(mount); });
141
+ onBeforeUnmount(() => connection.value?.destroy());
142
+ watch([
143
+ () => props.schema,
144
+ () => props.validate,
145
+ () => props.validateOn,
146
+ () => props.revalidateOn,
147
+ ], () => { void nextTick(mount); });
148
+ const actions = {
149
+ submitStarted: () => connection.value?.submitStarted() ?? null,
150
+ submitSucceeded: (generation) => connection.value?.submitSucceeded(generation) ?? false,
151
+ submitFailed: (generation, issues = []) => connection.value?.submitFailed(generation, issues) ?? false,
152
+ replaceIssues: (source, issues) => connection.value?.replaceIssues(source, issues) ?? false,
153
+ reset: () => connection.value?.reset(),
154
+ };
155
+ const slotProps = computed(() => Object.freeze({
156
+ state: state.value,
157
+ validationStatus: state.value.validationStatus,
158
+ validationTrigger: state.value.validationTrigger,
159
+ validationIntent: state.value.validationIntent,
160
+ submissionStatus: state.value.submissionStatus,
161
+ valid: state.value.valid,
162
+ touched: state.value.touched,
163
+ dirty: state.value.dirty,
164
+ submitted: state.value.submitted,
165
+ submitCount: state.value.submitCount,
166
+ ...actions,
167
+ }));
168
+ provide(formContextKey, { state, summary, register, connection });
169
+ expose(actions);
170
+ return () => h('form', mergeProps(attrs, {
171
+ ref: (element) => { root.value = element; },
172
+ 'data-scope': 'form',
173
+ 'data-part': 'root',
174
+ 'data-validation-status': state.value.validationStatus,
175
+ 'data-submission-status': state.value.submissionStatus,
176
+ }), slots['default']?.(slotProps.value) ?? []);
177
+ },
178
+ });
179
+ export const FormRoot = FormRootImpl;
180
+ function toFormSubmitEvent(payload) {
181
+ return Object.freeze({
182
+ formData: payload.formData,
183
+ values: payload.values,
184
+ submitter: payload.submitter,
185
+ state: payload.state,
186
+ nativeEvent: payload.event,
187
+ get defaultPrevented() { return payload.event.defaultPrevented; },
188
+ preventDefault: () => payload.event.preventDefault(),
189
+ stopPropagation: () => payload.event.stopPropagation(),
190
+ stopImmediatePropagation: () => payload.event.stopImmediatePropagation(),
191
+ });
192
+ }
193
+ function isPromiseLike(value) {
194
+ return typeof value === 'object'
195
+ && value !== null
196
+ && 'then' in value
197
+ && typeof value.then === 'function';
198
+ }
199
+ function toServerIssues(input) {
200
+ if (input === undefined || input.length === 0)
201
+ return [submissionErrorIssue()];
202
+ return Object.freeze(input.map((issue) => Object.freeze({ ...issue, source: 'server' })));
203
+ }
204
+ function submissionErrorIssue(reason) {
205
+ const message = reason instanceof Error
206
+ ? reason.message.trim()
207
+ : typeof reason === 'string' ? reason.trim() : '';
208
+ return Object.freeze({
209
+ id: 'form-submit-error',
210
+ message: message || 'Form submission failed.',
211
+ source: 'server',
212
+ });
213
+ }
214
+ export const FormField = defineComponent({
215
+ name: 'SectileFormField',
216
+ inheritAttrs: false,
217
+ props: {
218
+ id: { type: String, default: undefined },
219
+ name: { type: [String, Array], default: undefined },
220
+ form: { type: String, default: undefined },
221
+ required: { type: Boolean, default: undefined },
222
+ disabled: { type: Boolean, default: undefined },
223
+ readonly: { type: Boolean, default: undefined },
224
+ ...partProps,
225
+ },
226
+ slots: Object,
227
+ setup(props, { attrs, slots }) {
228
+ const formContext = useFormContext('FormField');
229
+ const generatedId = useHostId();
230
+ const id = computed(() => props.id ?? `form-field-${generatedId}`);
231
+ const nameKey = computed(() => (props.name === undefined ? undefined : encodeFormFieldPath(props.name)));
232
+ const root = ref(null);
233
+ const controls = shallowRef([]);
234
+ const fallback = shallowRef(null);
235
+ const appliedAttributes = new Map();
236
+ let observer;
237
+ let unregister;
238
+ const activeControl = computed(() => (controls.value.find((registration) => resolveElement(registration.element) !== null)
239
+ ?? fallback.value));
240
+ const labelMode = computed(() => activeControl.value?.labelMode ?? 'for');
241
+ const semanticControl = () => {
242
+ const registration = activeControl.value;
243
+ if (registration === undefined || registration === null)
244
+ return null;
245
+ return resolveElement(registration.semanticControl ?? registration.element);
246
+ };
247
+ const effectiveControlId = computed(() => (semanticControl()?.getAttribute('id')?.trim() || `${id.value}-control`));
248
+ const fieldState = computed(() => formContext.state.value.fields.find((field) => field.id === id.value));
249
+ const slotProps = computed(() => {
250
+ const current = fieldState.value;
251
+ const descriptionId = `${id.value}-description`;
252
+ const messageId = `${id.value}-message`;
253
+ const describedBy = `${descriptionId} ${messageId}`;
254
+ const valid = current?.valid ?? true;
255
+ return Object.freeze({
256
+ id: id.value,
257
+ controlId: effectiveControlId.value,
258
+ labelId: `${id.value}-label`,
259
+ descriptionId,
260
+ messageId,
261
+ describedBy,
262
+ valid,
263
+ touched: current?.touched ?? false,
264
+ dirty: current?.dirty ?? false,
265
+ issues: current?.issues ?? [],
266
+ });
267
+ });
268
+ const attributesFor = (registration) => {
269
+ const explicit = new Set(registration.explicit ?? []);
270
+ const capabilities = registration.capabilities ?? {};
271
+ const attributes = {};
272
+ const assign = (attribute, value) => {
273
+ if (!explicit.has(attribute) && value !== undefined)
274
+ attributes[attribute] = value;
275
+ };
276
+ if (capabilities.id === true)
277
+ assign('id', slotProps.value.controlId);
278
+ if (capabilities.describedBy === true) {
279
+ assign('aria-describedby', slotProps.value.describedBy);
280
+ assign('aria-errormessage', slotProps.value.messageId);
281
+ }
282
+ if (capabilities.invalid === true && !slotProps.value.valid)
283
+ assign('aria-invalid', 'true');
284
+ if (capabilities.labelledBy === true && labelMode.value === 'labelledby') {
285
+ assign('aria-labelledby', slotProps.value.labelId);
286
+ }
287
+ if (capabilities.required === true && props.required === true)
288
+ assign('required', true);
289
+ else if (labelMode.value !== 'for' && props.required === true)
290
+ assign('aria-required', 'true');
291
+ if (capabilities.disabled === true && props.disabled === true)
292
+ assign('disabled', true);
293
+ else if (labelMode.value !== 'for' && props.disabled === true)
294
+ assign('aria-disabled', 'true');
295
+ if (capabilities.readonly === true && props.readonly === true)
296
+ assign('readonly', true);
297
+ else if (labelMode.value !== 'for' && props.readonly === true)
298
+ assign('aria-readonly', 'true');
299
+ return Object.freeze(attributes);
300
+ };
301
+ const restoreControlAttributes = () => {
302
+ for (const [element, attributes] of appliedAttributes) {
303
+ for (const [name, previous] of attributes) {
304
+ if (previous === null)
305
+ element.removeAttribute(name);
306
+ else
307
+ element.setAttribute(name, previous);
308
+ }
309
+ }
310
+ appliedAttributes.clear();
311
+ };
312
+ const applyControlAttributes = () => {
313
+ restoreControlAttributes();
314
+ const registration = activeControl.value;
315
+ if (registration === undefined || registration === null)
316
+ return;
317
+ const semantic = resolveElement(registration.semanticControl ?? registration.element);
318
+ if (semantic !== null) {
319
+ applyMetadata(semantic, attributesFor(registration), registration.explicit, appliedAttributes);
320
+ }
321
+ for (const submission of resolveSubmissionRegistrations(registration)) {
322
+ const element = resolveElement(submission.element);
323
+ if (element === null)
324
+ continue;
325
+ const capabilities = submission.capabilities ?? nativeSubmissionCapabilities(element);
326
+ const explicit = submission.explicit ?? registration.explicit;
327
+ const attributes = {};
328
+ if (capabilities.name === true && props.name !== undefined) {
329
+ attributes['name'] = encodeSubmissionName(props.name, submission.relativeName);
330
+ }
331
+ if (capabilities.form === true && props.form !== undefined)
332
+ attributes['form'] = props.form;
333
+ if (capabilities.required === true && props.required === true)
334
+ attributes['required'] = true;
335
+ if (capabilities.disabled === true && props.disabled === true)
336
+ attributes['disabled'] = true;
337
+ if (capabilities.readonly === true && props.readonly === true)
338
+ attributes['readonly'] = true;
339
+ applyMetadata(element, attributes, explicit, appliedAttributes);
340
+ }
341
+ };
342
+ const discoverNativeFallback = () => {
343
+ if (controls.value.length > 0 || root.value === null) {
344
+ fallback.value = null;
345
+ return;
346
+ }
347
+ const candidates = nativeCandidates(root.value);
348
+ const semantic = nativeSemanticControl(candidates);
349
+ if (semantic === undefined) {
350
+ fallback.value = null;
351
+ return;
352
+ }
353
+ const submissions = candidates.filter(isFormSubmissionElement).map((element) => ({
354
+ element: () => element,
355
+ capabilities: nativeSubmissionCapabilities(element),
356
+ }));
357
+ const current = fallback.value;
358
+ if (current !== null
359
+ && resolveElement(current.semanticControl ?? current.element) === semantic
360
+ && sameSubmissionElements(resolveSubmissionRegistrations(current), submissions))
361
+ return;
362
+ fallback.value = {
363
+ element: () => semantic,
364
+ semanticControl: () => semantic,
365
+ focusTarget: () => semantic,
366
+ submissions,
367
+ labelMode: nativeLabelMode(semantic),
368
+ capabilities: nativeControlCapabilities(semantic),
369
+ };
370
+ };
371
+ const registerControl = (registration) => {
372
+ controls.value = [...controls.value, registration];
373
+ fallback.value = null;
374
+ void nextTick(applyControlAttributes);
375
+ return () => {
376
+ controls.value = controls.value.filter((candidate) => candidate !== registration);
377
+ discoverNativeFallback();
378
+ void nextTick(applyControlAttributes);
379
+ };
380
+ };
381
+ const mount = () => {
382
+ unregister?.();
383
+ unregister = undefined;
384
+ if (root.value === null)
385
+ return;
386
+ discoverNativeFallback();
387
+ observer?.disconnect();
388
+ observer = new MutationObserver(() => {
389
+ discoverNativeFallback();
390
+ applyControlAttributes();
391
+ });
392
+ observer.observe(root.value, { childList: true, subtree: true });
393
+ applyControlAttributes();
394
+ const participant = {
395
+ id: id.value,
396
+ get element() { return root.value; },
397
+ get semanticControl() { return semanticControl() ?? root.value; },
398
+ get focusTarget() {
399
+ const registration = activeControl.value;
400
+ return registration === undefined || registration === null
401
+ ? root.value
402
+ : resolveElement(registration.focusTarget ?? registration.semanticControl ?? registration.element)
403
+ ?? root.value;
404
+ },
405
+ get submissionElements() {
406
+ const registration = activeControl.value;
407
+ if (registration === undefined || registration === null)
408
+ return [];
409
+ return resolveSubmissionRegistrations(registration)
410
+ .map((submission) => resolveElement(submission.element))
411
+ .filter((element) => element !== null);
412
+ },
413
+ focus: () => {
414
+ const registration = activeControl.value;
415
+ const control = registration === undefined || registration === null
416
+ ? null
417
+ : resolveElement(registration.focusTarget ?? registration.semanticControl ?? registration.element);
418
+ control?.focus();
419
+ return control !== null && document.activeElement === control;
420
+ },
421
+ ...(props.name === undefined ? {} : { name: props.name }),
422
+ };
423
+ unregister = formContext.register(participant);
424
+ };
425
+ onMounted(() => { void nextTick(mount); });
426
+ onBeforeUnmount(() => {
427
+ observer?.disconnect();
428
+ restoreControlAttributes();
429
+ unregister?.();
430
+ });
431
+ watch([id, nameKey], () => { void nextTick(mount); });
432
+ watch([
433
+ activeControl,
434
+ effectiveControlId,
435
+ labelMode,
436
+ () => props.name,
437
+ () => props.form,
438
+ () => props.required,
439
+ () => props.disabled,
440
+ () => props.readonly,
441
+ () => slotProps.value.valid,
442
+ ], applyControlAttributes, { flush: 'post' });
443
+ provide(formFieldContextKey, {
444
+ slotProps,
445
+ labelMode,
446
+ registerControl,
447
+ attributesFor,
448
+ });
449
+ return () => h(Primitive, mergeProps(attrs, {
450
+ as: props.as,
451
+ asChild: props.asChild,
452
+ elementRef: (element) => { root.value = element; },
453
+ 'data-scope': 'form',
454
+ 'data-part': 'field',
455
+ 'data-valid': slotProps.value.valid ? '' : undefined,
456
+ 'data-invalid': slotProps.value.valid ? undefined : '',
457
+ 'data-touched': slotProps.value.touched ? '' : undefined,
458
+ 'data-dirty': slotProps.value.dirty ? '' : undefined,
459
+ }), { default: () => slots['default']?.(slotProps.value) });
460
+ },
461
+ });
462
+ export const FormLabel = defineComponent({
463
+ name: 'SectileFormLabel', inheritAttrs: false,
464
+ props: {
465
+ as: { type: [String, Object, Function], default: undefined },
466
+ asChild: { type: Boolean, default: false },
467
+ },
468
+ setup(props, { attrs, slots }) {
469
+ const field = useFormFieldContext('FormLabel');
470
+ return () => {
471
+ const mode = field.labelMode.value;
472
+ return renderFieldPart({
473
+ as: props.as ?? (mode === 'legend' ? 'legend' : mode === 'for' ? 'label' : 'span'),
474
+ asChild: props.asChild,
475
+ }, attrs, slots, 'label', {
476
+ id: field.slotProps.value.labelId,
477
+ ...(mode === 'for' ? { for: field.slotProps.value.controlId } : {}),
478
+ });
479
+ };
480
+ },
481
+ });
482
+ export const FormDescription = defineComponent({
483
+ name: 'SectileFormDescription', inheritAttrs: false,
484
+ props: { ...partProps, as: { ...partProps.as, default: 'p' } },
485
+ setup(props, { attrs, slots }) {
486
+ const field = useFormFieldContext('FormDescription');
487
+ return () => renderFieldPart(props, attrs, slots, 'description', {
488
+ id: field.slotProps.value.descriptionId,
489
+ });
490
+ },
491
+ });
492
+ export const FormMessage = defineComponent({
493
+ name: 'SectileFormMessage', inheritAttrs: false,
494
+ props: { ...partProps, as: { ...partProps.as, default: 'p' } },
495
+ slots: Object,
496
+ setup(props, { attrs, slots }) {
497
+ const field = useFormFieldContext('FormMessage');
498
+ return () => h(Primitive, mergeProps(attrs, {
499
+ as: props.as,
500
+ asChild: props.asChild,
501
+ id: field.slotProps.value.messageId,
502
+ hidden: field.slotProps.value.valid,
503
+ role: 'alert',
504
+ 'aria-live': 'polite',
505
+ 'data-scope': 'form',
506
+ 'data-part': 'message',
507
+ }), {
508
+ default: () => slots['default']?.(field.slotProps.value)
509
+ ?? field.slotProps.value.issues.map((issue) => issue.message).join(' '),
510
+ });
511
+ },
512
+ });
513
+ export const FormSummary = defineComponent({
514
+ name: 'SectileFormSummary', inheritAttrs: false,
515
+ props: partProps,
516
+ slots: Object,
517
+ setup(props, { attrs, slots }) {
518
+ const form = useFormContext('FormSummary');
519
+ const slotProps = useRootSlotProps(form);
520
+ return () => h(Primitive, mergeProps(attrs, {
521
+ as: props.as,
522
+ asChild: props.asChild,
523
+ elementRef: (element) => { form.summary.value = element; },
524
+ role: 'alert',
525
+ 'aria-live': 'polite',
526
+ tabindex: -1,
527
+ hidden: form.state.value.valid,
528
+ 'data-scope': 'form',
529
+ 'data-part': 'summary',
530
+ }), { default: () => slots['default']?.(slotProps.value) });
531
+ },
532
+ });
533
+ export const FormReset = defineComponent({
534
+ name: 'SectileFormReset', inheritAttrs: false,
535
+ props: { ...partProps, as: { ...partProps.as, default: 'button' } },
536
+ setup(props, { attrs, slots }) {
537
+ const form = useFormContext('FormReset');
538
+ const slotProps = useRootSlotProps(form);
539
+ return () => h(Primitive, mergeProps(attrs, {
540
+ as: props.as,
541
+ asChild: props.asChild,
542
+ ...(props.as === 'button' && !props.asChild ? { type: 'reset' } : {}),
543
+ 'data-scope': 'form',
544
+ 'data-part': 'reset',
545
+ 'data-validation-status': form.state.value.validationStatus,
546
+ 'data-submission-status': form.state.value.submissionStatus,
547
+ }), { default: () => slots['default']?.(slotProps.value) });
548
+ },
549
+ });
550
+ export const FormSubmit = defineComponent({
551
+ name: 'SectileFormSubmit', inheritAttrs: false,
552
+ props: { ...partProps, as: { ...partProps.as, default: 'button' } },
553
+ setup(props, { attrs, slots }) {
554
+ const form = useFormContext('FormSubmit');
555
+ const slotProps = useRootSlotProps(form);
556
+ return () => h(Primitive, mergeProps(attrs, {
557
+ as: props.as,
558
+ asChild: props.asChild,
559
+ ...(props.as === 'button' && !props.asChild ? { type: 'submit' } : {}),
560
+ 'data-scope': 'form',
561
+ 'data-part': 'submit',
562
+ 'data-validation-status': form.state.value.validationStatus,
563
+ 'data-submission-status': form.state.value.submissionStatus,
564
+ }), { default: () => slots['default']?.(slotProps.value) });
565
+ },
566
+ });
567
+ function useRootSlotProps(form) {
568
+ return computed(() => Object.freeze({
569
+ state: form.state.value,
570
+ validationStatus: form.state.value.validationStatus,
571
+ validationTrigger: form.state.value.validationTrigger,
572
+ validationIntent: form.state.value.validationIntent,
573
+ submissionStatus: form.state.value.submissionStatus,
574
+ valid: form.state.value.valid,
575
+ touched: form.state.value.touched,
576
+ dirty: form.state.value.dirty,
577
+ submitted: form.state.value.submitted,
578
+ submitCount: form.state.value.submitCount,
579
+ submitStarted: () => form.connection.value?.submitStarted() ?? null,
580
+ submitSucceeded: (generation) => form.connection.value?.submitSucceeded(generation) ?? false,
581
+ submitFailed: (generation, issues = []) => form.connection.value?.submitFailed(generation, issues) ?? false,
582
+ replaceIssues: (source, issues) => form.connection.value?.replaceIssues(source, issues) ?? false,
583
+ reset: () => form.connection.value?.reset(),
584
+ }));
585
+ }
586
+ function renderFieldPart(props, attrs, slots, part, attributes) {
587
+ return h(Primitive, mergeProps(attributes, attrs, {
588
+ as: props.as,
589
+ asChild: props.asChild,
590
+ 'data-scope': 'form',
591
+ 'data-part': part,
592
+ }), { default: () => slots.default?.() });
593
+ }
594
+ export function useFormControl(registration) {
595
+ const field = inject(formFieldContextKey, null);
596
+ const owned = inject(formControlOwnerKey, false);
597
+ const instance = getCurrentInstance();
598
+ const explicit = Object.freeze([
599
+ ...new Set([
600
+ ...(registration.explicit ?? []),
601
+ ...explicitMetadataAttributes(instance?.vnode.props ?? null),
602
+ ]),
603
+ ]);
604
+ const normalized = Object.freeze({ ...registration, explicit });
605
+ const controlProps = computed(() => (field !== null && !owned ? field.attributesFor(normalized) : Object.freeze({})));
606
+ let unregister;
607
+ if (field !== null && !owned) {
608
+ onMounted(() => { unregister = field.registerControl(normalized); });
609
+ onBeforeUnmount(() => unregister?.());
610
+ }
611
+ return Object.freeze({ participating: field !== null && !owned, controlProps });
612
+ }
613
+ export function provideFormControlOwner() {
614
+ provide(formControlOwnerKey, true);
615
+ }
616
+ function useFormContext(part) {
617
+ const context = inject(formContextKey, null);
618
+ if (context === null)
619
+ throw new TypeError(`${part} must be rendered inside FormRoot.`);
620
+ return context;
621
+ }
622
+ function useFormFieldContext(part) {
623
+ const context = inject(formFieldContextKey, null);
624
+ if (context === null)
625
+ throw new TypeError(`${part} must be rendered inside FormField.`);
626
+ return context;
627
+ }
628
+ function resolveElement(source) {
629
+ return typeof source === 'function' ? source() : source.value;
630
+ }
631
+ function resolveSubmissionRegistrations(registration) {
632
+ if (registration.submissions !== undefined) {
633
+ return typeof registration.submissions === 'function'
634
+ ? registration.submissions()
635
+ : registration.submissions;
636
+ }
637
+ const element = resolveElement(registration.element);
638
+ if (element === null || !isFormSubmissionElement(element))
639
+ return [];
640
+ return [{
641
+ element: () => element,
642
+ capabilities: nativeSubmissionCapabilities(element),
643
+ ...(registration.explicit === undefined ? {} : { explicit: registration.explicit }),
644
+ }];
645
+ }
646
+ function sameSubmissionElements(left, right) {
647
+ return left.length === right.length && left.every((submission, index) => (resolveElement(submission.element) === resolveElement(right[index].element)));
648
+ }
649
+ function nativeCandidates(root) {
650
+ const selector = 'button, fieldset, input, select, textarea';
651
+ const candidates = [
652
+ ...(root.matches(selector) ? [root] : []),
653
+ ...root.querySelectorAll(selector),
654
+ ];
655
+ return candidates.filter((candidate) => (candidate.closest('[data-scope="form"][data-part="field"]') === root));
656
+ }
657
+ function nativeSemanticControl(candidates) {
658
+ return candidates.find((candidate) => (candidate.tagName !== 'INPUT'
659
+ || candidate.getAttribute('type')?.toLowerCase() !== 'hidden')) ?? candidates[0];
660
+ }
661
+ function isFormSubmissionElement(element) {
662
+ return ['INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName);
663
+ }
664
+ function nativeLabelMode(element) {
665
+ if (element.tagName === 'FIELDSET')
666
+ return 'legend';
667
+ if (element.tagName === 'INPUT' && element.getAttribute('type')?.toLowerCase() === 'hidden') {
668
+ return 'labelledby';
669
+ }
670
+ return ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'].includes(element.tagName)
671
+ ? 'for'
672
+ : 'labelledby';
673
+ }
674
+ function nativeControlCapabilities(element) {
675
+ const tag = element.tagName;
676
+ const labelable = nativeLabelMode(element) === 'for';
677
+ return Object.freeze({
678
+ id: true,
679
+ describedBy: true,
680
+ invalid: true,
681
+ labelledBy: !labelable,
682
+ required: ['INPUT', 'SELECT', 'TEXTAREA'].includes(tag),
683
+ disabled: ['BUTTON', 'FIELDSET', 'INPUT', 'SELECT', 'TEXTAREA'].includes(tag),
684
+ readonly: tag === 'TEXTAREA' || (tag === 'INPUT' && supportsReadonly(element)),
685
+ });
686
+ }
687
+ function nativeSubmissionCapabilities(element) {
688
+ const tag = element.tagName;
689
+ return Object.freeze({
690
+ name: true,
691
+ form: true,
692
+ required: ['INPUT', 'SELECT', 'TEXTAREA'].includes(tag),
693
+ disabled: true,
694
+ readonly: tag === 'TEXTAREA' || (tag === 'INPUT' && supportsReadonly(element)),
695
+ });
696
+ }
697
+ function encodeSubmissionName(base, relative) {
698
+ if (relative === undefined)
699
+ return encodeFormFieldPath(base);
700
+ return encodeFormFieldPath(appendFormFieldPath(base, relative));
701
+ }
702
+ function applyMetadata(element, attributes, explicitAttributes, applied) {
703
+ const explicit = new Set(explicitAttributes ?? []);
704
+ for (const [name, value] of Object.entries(attributes)) {
705
+ if (value === undefined || value === false || explicit.has(name))
706
+ continue;
707
+ const previous = element.getAttribute(name);
708
+ const mergeTokens = name === 'aria-describedby' || name === 'aria-labelledby';
709
+ if (previous !== null && !mergeTokens)
710
+ continue;
711
+ let changes = applied.get(element);
712
+ if (changes === undefined) {
713
+ changes = new Map();
714
+ applied.set(element, changes);
715
+ }
716
+ if (!changes.has(name))
717
+ changes.set(name, previous);
718
+ const next = mergeTokens && previous !== null
719
+ ? [...new Set([...previous.split(/\s+/u), ...String(value).split(/\s+/u)])].join(' ')
720
+ : value === true ? '' : String(value);
721
+ element.setAttribute(name, next);
722
+ }
723
+ }
724
+ function explicitMetadataAttributes(vnodeProps) {
725
+ if (vnodeProps === null)
726
+ return [];
727
+ const aliases = {
728
+ id: ['id'],
729
+ name: ['name'],
730
+ form: ['form'],
731
+ required: ['required'],
732
+ disabled: ['disabled'],
733
+ readonly: ['readonly', 'readOnly'],
734
+ 'aria-describedby': ['aria-describedby', 'ariaDescribedby'],
735
+ 'aria-errormessage': ['aria-errormessage', 'ariaErrormessage'],
736
+ 'aria-invalid': ['aria-invalid', 'ariaInvalid'],
737
+ 'aria-labelledby': ['aria-labelledby', 'ariaLabelledby'],
738
+ 'aria-disabled': ['aria-disabled', 'ariaDisabled'],
739
+ 'aria-required': ['aria-required', 'ariaRequired'],
740
+ 'aria-readonly': ['aria-readonly', 'ariaReadonly'],
741
+ };
742
+ return Object.entries(aliases)
743
+ .filter(([, names]) => names.some((name) => Object.hasOwn(vnodeProps, name)))
744
+ .map(([attribute]) => attribute);
745
+ }
746
+ function supportsReadonly(element) {
747
+ return !new Set([
748
+ 'button', 'checkbox', 'color', 'file', 'hidden', 'image', 'radio', 'range',
749
+ 'reset', 'submit',
750
+ ]).has(element.type.toLowerCase());
751
+ }
752
+ //# sourceMappingURL=form.js.map