@stonecrop/aform 0.34.0 → 0.36.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 (42) hide show
  1. package/dist/aform.js +818 -454
  2. package/dist/aform.js.map +1 -1
  3. package/dist/assets/index.css +321 -247
  4. package/dist/src/components/AForm.vue.d.ts.map +1 -1
  5. package/dist/src/components/form/ACheckbox.vue.d.ts.map +1 -1
  6. package/dist/src/components/form/ACurrencyInput.vue.d.ts +3 -3
  7. package/dist/src/components/form/ACurrencyInput.vue.d.ts.map +1 -1
  8. package/dist/src/components/form/ADate.vue.d.ts +3 -3
  9. package/dist/src/components/form/ADate.vue.d.ts.map +1 -1
  10. package/dist/src/components/form/ADatePicker.vue.d.ts +10 -10
  11. package/dist/src/components/form/ADatePicker.vue.d.ts.map +1 -1
  12. package/dist/src/components/form/ADateRange.vue.d.ts +3 -3
  13. package/dist/src/components/form/ADateRange.vue.d.ts.map +1 -1
  14. package/dist/src/components/form/ADateSelection.vue.d.ts +8 -6
  15. package/dist/src/components/form/ADateSelection.vue.d.ts.map +1 -1
  16. package/dist/src/components/form/ADateTime.vue.d.ts +3 -3
  17. package/dist/src/components/form/ADateTime.vue.d.ts.map +1 -1
  18. package/dist/src/components/form/ADateTimeInput.vue.d.ts.map +1 -1
  19. package/dist/src/components/form/ADropdown.vue.d.ts.map +1 -1
  20. package/dist/src/components/form/ADuration.vue.d.ts +3 -3
  21. package/dist/src/components/form/ADuration.vue.d.ts.map +1 -1
  22. package/dist/src/components/form/AFileAttach.vue.d.ts.map +1 -1
  23. package/dist/src/components/form/AFormLink.vue.d.ts.map +1 -1
  24. package/dist/src/components/form/ANumericInput.vue.d.ts +3 -3
  25. package/dist/src/components/form/ANumericInput.vue.d.ts.map +1 -1
  26. package/dist/src/components/form/AQuantityInput.vue.d.ts +3 -3
  27. package/dist/src/components/form/AQuantityInput.vue.d.ts.map +1 -1
  28. package/dist/src/components/form/ASegmentedControl.vue.d.ts.map +1 -1
  29. package/dist/src/components/form/ATextInput.vue.d.ts.map +1 -1
  30. package/dist/src/components/form/ATextboxInput.vue.d.ts.map +1 -1
  31. package/dist/src/components/utilities/Login.vue.d.ts.map +1 -1
  32. package/dist/src/composables/fieldErrorA11y.d.ts +8 -0
  33. package/dist/src/composables/fieldErrorA11y.d.ts.map +1 -0
  34. package/dist/src/types/index.d.ts +8 -8
  35. package/dist/src/types/index.d.ts.map +1 -1
  36. package/dist/src/utils/emptiedBox.d.ts +11 -0
  37. package/dist/src/utils/emptiedBox.d.ts.map +1 -0
  38. package/dist/src/utils/fieldCalendar.d.ts +18 -0
  39. package/dist/src/utils/fieldCalendar.d.ts.map +1 -0
  40. package/dist/src/utils/typedDay.d.ts +14 -0
  41. package/dist/src/utils/typedDay.d.ts.map +1 -0
  42. package/package.json +8 -5
package/dist/aform.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { hasBadgeOptions, isBadgeDescriptor, lookupBadge, selectChoices } from "@stonecrop/schema";
2
2
  import { install as install$1 } from "@stonecrop/atable";
3
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, mergeModels, mergeProps, nextTick, normalizeClass, normalizeStyle, onMounted, openBlock, provide, reactive, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, useModel, useTemplateRef, vModelCheckbox, vModelRadio, vModelSelect, vModelText, vShow, watch, watchEffect, withCtx, withDirectives, withKeys, withModifiers } from "vue";
3
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, inject, mergeModels, mergeProps, nextTick, normalizeClass, normalizeStyle, onMounted, openBlock, provide, reactive, ref, renderList, renderSlot, resolveComponent, resolveDynamicComponent, toDisplayString, unref, useId, useModel, useTemplateRef, vModelCheckbox, vModelRadio, vModelSelect, vModelText, vShow, watch, watchEffect, withCtx, withDirectives, withKeys, withModifiers } from "vue";
4
4
  import { vOnClickOutside } from "@vueuse/components";
5
+ import { fromISODate } from "@stonecrop/utilities";
6
+ import { Temporal } from "temporal-polyfill";
5
7
  import { onClickOutside, useFileDialog } from "@vueuse/core";
6
8
  import './assets/index.css';//#region src/utils/deserialize.ts
7
9
  /**
@@ -115,18 +117,32 @@ function resolvedFieldsToColumns(fields) {
115
117
  return columns;
116
118
  }
117
119
  //#endregion
120
+ //#region src/composables/fieldErrorA11y.ts
121
+ function fieldErrorA11y(uuid, errorText) {
122
+ const errorId = computed(() => uuid ? `${uuid}-error` : void 0);
123
+ const hasError = computed(() => Boolean(errorText.value));
124
+ return {
125
+ errorId,
126
+ hasError,
127
+ describedBy: computed(() => hasError.value && errorId.value ? errorId.value : void 0),
128
+ invalid: computed(() => hasError.value ? true : void 0)
129
+ };
130
+ }
131
+ //#endregion
118
132
  //#region src/components/form/ACheckbox.vue?vue&type=script&setup=true&lang.ts
119
133
  var _hoisted_1$20 = { class: "aform_form-element" };
120
134
  var _hoisted_2$19 = { class: "aform_field-label" };
121
135
  var _hoisted_3$17 = { class: "aform_display-value" };
122
136
  var _hoisted_4$16 = ["for"];
123
137
  var _hoisted_5$16 = { class: "aform_checkbox-container aform_input-field" };
124
- var _hoisted_6$16 = [
138
+ var _hoisted_6$13 = [
125
139
  "id",
126
140
  "disabled",
127
- "required"
141
+ "required",
142
+ "aria-invalid",
143
+ "aria-describedby"
128
144
  ];
129
- var _hoisted_7$7 = ["innerHTML"];
145
+ var _hoisted_7$7 = ["id"];
130
146
  var ACheckbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
131
147
  __name: "ACheckbox",
132
148
  props: /*@__PURE__*/ mergeModels({
@@ -151,6 +167,7 @@ var ACheckbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
151
167
  emits: ["update:modelValue"],
152
168
  setup(__props) {
153
169
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
170
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
154
171
  const checkbox = useModel(__props, "modelValue");
155
172
  return (_ctx, _cache) => {
156
173
  return openBlock(), createElementBlock("div", _hoisted_1$20, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("label", _hoisted_2$19, toDisplayString(__props.label), 1), createElementVNode("span", _hoisted_3$17, toDisplayString(checkbox.value ? "✓" : "✗"), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
@@ -164,12 +181,15 @@ var ACheckbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
164
181
  type: "checkbox",
165
182
  class: "aform_checkbox",
166
183
  disabled: __props.mode === "read",
167
- required: __props.required
168
- }, null, 8, _hoisted_6$16), [[vModelCheckbox, checkbox.value]])]),
184
+ required: __props.required,
185
+ "aria-invalid": unref(invalid),
186
+ "aria-describedby": unref(describedBy)
187
+ }, null, 8, _hoisted_6$13), [[vModelCheckbox, checkbox.value]])]),
169
188
  withDirectives(createElementVNode("p", {
189
+ id: unref(errorId),
170
190
  class: "aform_error",
171
- innerHTML: errorText.value
172
- }, null, 8, _hoisted_7$7), [[vShow, errorText.value]])
191
+ role: "alert"
192
+ }, toDisplayString(errorText.value), 9, _hoisted_7$7), [[vShow, errorText.value]])
173
193
  ], 64))]);
174
194
  };
175
195
  }
@@ -183,17 +203,35 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
183
203
  };
184
204
  //#endregion
185
205
  //#region src/components/form/ACheckbox.vue
186
- var ACheckbox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(ACheckbox_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-ca06741d"]]);
206
+ var ACheckbox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(ACheckbox_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-f28e7db6"]]);
207
+ //#endregion
208
+ //#region src/utils/emptiedBox.ts
209
+ /**
210
+ * A date box's day as `v-model` hands it back, or null once any part of the box is empty, which the
211
+ * browser reports as `''`.
212
+ */
213
+ function dayFromBox(text) {
214
+ return text === "" ? null : text;
215
+ }
216
+ /**
217
+ * A number box's number as `v-model` hands it back, or null once the box is empty. `v-model` hands
218
+ * back the box's text when it holds no number, and the browser reports an empty box as `''`.
219
+ */
220
+ function numberFromBox(value) {
221
+ return value === "" ? null : value;
222
+ }
187
223
  //#endregion
188
224
  //#region src/components/form/AFormLink.vue?vue&type=script&setup=true&lang.ts
189
- var _hoisted_1$19 = {
225
+ var _hoisted_1$19 = { class: "aform_display-value" };
226
+ var _hoisted_2$18 = {
190
227
  key: 0,
191
- class: "aform_display-value"
228
+ class: "aform_field-label"
192
229
  };
193
- var _hoisted_2$18 = { class: "aform_form-link-wrapper" };
194
- var _hoisted_3$16 = { class: "input-group" };
195
- var _hoisted_4$15 = [
230
+ var _hoisted_3$16 = { class: "aform_form-link-wrapper" };
231
+ var _hoisted_4$15 = { class: "input-group" };
232
+ var _hoisted_5$15 = [
196
233
  "placeholder",
234
+ "size",
197
235
  "aria-label",
198
236
  "required",
199
237
  "aria-expanded",
@@ -202,18 +240,28 @@ var _hoisted_4$15 = [
202
240
  "disabled",
203
241
  "onKeydown"
204
242
  ];
205
- var _hoisted_5$15 = ["onKeydown"];
206
- var _hoisted_6$15 = ["aria-label"];
207
- var _hoisted_7$6 = {
243
+ var _hoisted_6$12 = ["onKeydown"];
244
+ var _hoisted_7$6 = ["data-icon"];
245
+ var _hoisted_8$6 = {
246
+ key: 0,
247
+ d: "M6 3.5 10.5 8 6 12.5"
248
+ };
249
+ var _hoisted_9$5 = {
250
+ key: 1,
251
+ d: "M2.5 8h11M9 3.5 13.5 8 9 12.5"
252
+ };
253
+ var _hoisted_10$4 = { class: "aform_form-btn-name" };
254
+ var _hoisted_11$4 = ["aria-label"];
255
+ var _hoisted_12$4 = {
208
256
  key: 0,
209
257
  class: "autocomplete-result loading"
210
258
  };
211
- var _hoisted_8$6 = [
259
+ var _hoisted_13$3 = [
212
260
  "id",
213
261
  "aria-selected",
214
262
  "onMousedown"
215
263
  ];
216
- var _hoisted_9$5 = {
264
+ var _hoisted_14$3 = {
217
265
  key: 0,
218
266
  class: "aform_field-label"
219
267
  };
@@ -274,6 +322,7 @@ var AFormLink_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
274
322
  return linkDisplayText(modelValue.value) ?? id;
275
323
  });
276
324
  const searchText = ref(hasValidId.value ? displayedText.value : "");
325
+ const embeddedSize = computed(() => __props.embedded ? Math.max((searchText.value || __props.placeholder || "").length, 1) : void 0);
277
326
  const dropdownOpen = ref(false);
278
327
  const loading = ref(false);
279
328
  const dropdownResults = ref([]);
@@ -373,7 +422,7 @@ var AFormLink_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
373
422
  if (activeIndex.value !== null && dropdownResults.value[activeIndex.value]) selectOption(dropdownResults.value[activeIndex.value]);
374
423
  };
375
424
  return (_ctx, _cache) => {
376
- return openBlock(), createElementBlock("div", { class: normalizeClass(["aform_form-element", { "aform_form-element--embedded": __props.embedded }]) }, [__props.mode === "display" ? (openBlock(), createElementBlock("span", _hoisted_1$19, toDisplayString(displayedText.value), 1)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [withDirectives((openBlock(), createElementBlock("div", _hoisted_2$18, [createElementVNode("div", _hoisted_3$16, [withDirectives(createElementVNode("input", {
425
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["aform_form-element", { "aform_form-element--embedded": __props.embedded }]) }, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_1$19, toDisplayString(displayedText.value), 1), __props.label && !__props.embedded ? (openBlock(), createElementBlock("label", _hoisted_2$18, toDisplayString(__props.label), 1)) : createCommentVNode("", true)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [withDirectives((openBlock(), createElementBlock("div", _hoisted_3$16, [createElementVNode("div", _hoisted_4$15, [withDirectives(createElementVNode("input", {
377
426
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchText.value = $event),
378
427
  type: "text",
379
428
  role: "combobox",
@@ -381,6 +430,7 @@ var AFormLink_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
381
430
  "aria-autocomplete": "list",
382
431
  class: normalizeClass(["aform_input-field", { "aform_input-field--embedded": __props.embedded }]),
383
432
  placeholder: __props.placeholder,
433
+ size: embeddedSize.value,
384
434
  "aria-label": __props.ariaLabel,
385
435
  required: __props.required,
386
436
  "aria-expanded": dropdownOpen.value,
@@ -396,19 +446,25 @@ var AFormLink_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
396
446
  withKeys(closeDropdown, ["esc"]),
397
447
  withKeys(closeDropdown, ["tab"])
398
448
  ]
399
- }, null, 42, _hoisted_4$15), [[vModelText, searchText.value]]), hasValidId.value && !__props.disabled && !__props.embedded ? (openBlock(), createElementBlock("button", {
449
+ }, null, 42, _hoisted_5$15), [[vModelText, searchText.value]]), hasValidId.value && !__props.disabled && !__props.embedded ? (openBlock(), createElementBlock("button", {
400
450
  key: 0,
401
451
  type: "button",
402
452
  class: "aform_form-btn",
403
453
  onClick: handleNavigate,
404
454
  onKeydown: withKeys(withModifiers(handleNavigate, ["prevent"]), ["enter"])
405
- }, [createElementVNode("span", null, toDisplayString(__props.icon === "chevron-right" ? "›" : "→"), 1)], 40, _hoisted_5$15)) : createCommentVNode("", true)]), dropdownOpen.value ? (openBlock(), createElementBlock("ul", {
455
+ }, [(openBlock(), createElementBlock("svg", {
456
+ class: "aform_form-btn-icon",
457
+ "data-icon": __props.icon,
458
+ viewBox: "1.75 2.75 12.5 10.5",
459
+ "aria-hidden": "true",
460
+ focusable: "false"
461
+ }, [__props.icon === "chevron-right" ? (openBlock(), createElementBlock("path", _hoisted_8$6)) : (openBlock(), createElementBlock("path", _hoisted_9$5))], 8, _hoisted_7$6)), createElementVNode("span", _hoisted_10$4, toDisplayString(__props.icon === "chevron-right" ? "›" : "→"), 1)], 40, _hoisted_6$12)) : createCommentVNode("", true)]), dropdownOpen.value ? (openBlock(), createElementBlock("ul", {
406
462
  key: 0,
407
463
  id: listboxId,
408
464
  class: "autocomplete-results",
409
465
  role: "listbox",
410
466
  "aria-label": __props.ariaLabel
411
- }, [loading.value ? (openBlock(), createElementBlock("li", _hoisted_7$6, "Loading…")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(dropdownResults.value, (option, i) => {
467
+ }, [loading.value ? (openBlock(), createElementBlock("li", _hoisted_12$4, "Loading…")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(dropdownResults.value, (option, i) => {
412
468
  return openBlock(), createElementBlock("li", {
413
469
  id: `${listboxId}-opt-${i}`,
414
470
  key: String(option.id),
@@ -416,11 +472,11 @@ var AFormLink_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
416
472
  "aria-selected": i === activeIndex.value,
417
473
  class: normalizeClass(["autocomplete-result", { "is-active": i === activeIndex.value }]),
418
474
  onMousedown: withModifiers(($event) => selectOption(option), ["prevent"])
419
- }, [renderSlot(_ctx.$slots, "option", { option }, () => [createTextVNode(toDisplayString(option.displayText ?? String(option.id)), 1)], true)], 42, _hoisted_8$6);
420
- }), 128))], 8, _hoisted_6$15)) : createCommentVNode("", true)])), [[unref(vOnClickOutside), onClickOutside]]), __props.label && !__props.embedded ? (openBlock(), createElementBlock("label", _hoisted_9$5, toDisplayString(__props.label), 1)) : createCommentVNode("", true)], 64))], 2);
475
+ }, [renderSlot(_ctx.$slots, "option", { option }, () => [createTextVNode(toDisplayString(option.displayText ?? String(option.id)), 1)], true)], 42, _hoisted_13$3);
476
+ }), 128))], 8, _hoisted_11$4)) : createCommentVNode("", true)])), [[unref(vOnClickOutside), onClickOutside]]), __props.label && !__props.embedded ? (openBlock(), createElementBlock("label", _hoisted_14$3, toDisplayString(__props.label), 1)) : createCommentVNode("", true)], 64))], 2);
421
477
  };
422
478
  }
423
- }), [["__scopeId", "data-v-91a761d0"]]);
479
+ }), [["__scopeId", "data-v-07d20e9b"]]);
424
480
  //#endregion
425
481
  //#region src/components/form/ACurrencyInput.vue?vue&type=script&setup=true&lang.ts
426
482
  var _hoisted_1$18 = { class: "aform_form-element acurrency" };
@@ -428,26 +484,28 @@ var _hoisted_2$17 = { class: "aform_display-value" };
428
484
  var _hoisted_3$15 = { class: "aform_field-label" };
429
485
  var _hoisted_4$14 = { class: "acurrency__row" };
430
486
  var _hoisted_5$14 = { class: "acurrency__field acurrency__field--amount" };
431
- var _hoisted_6$14 = { class: "acurrency__group" };
487
+ var _hoisted_6$11 = { class: "acurrency__group" };
432
488
  var _hoisted_7$5 = { class: "acurrency__currency" };
433
489
  var _hoisted_8$5 = { class: "acurrency__amount-wrap" };
434
490
  var _hoisted_9$4 = [
435
491
  "id",
436
492
  "disabled",
437
- "required"
493
+ "required",
494
+ "aria-invalid",
495
+ "aria-describedby"
438
496
  ];
439
497
  var _hoisted_10$3 = ["for"];
440
498
  var _hoisted_11$3 = { class: "acurrency__row acurrency__row--base" };
441
499
  var _hoisted_12$3 = { class: "acurrency__field acurrency__field--base-currency" };
442
- var _hoisted_13$1 = ["value"];
443
- var _hoisted_14$1 = { class: "aform_field-label" };
500
+ var _hoisted_13$2 = ["value"];
501
+ var _hoisted_14$2 = { class: "aform_field-label" };
444
502
  var _hoisted_15$1 = { class: "acurrency__field acurrency__field--base-amount" };
445
503
  var _hoisted_16$1 = ["value"];
446
504
  var _hoisted_17$1 = { class: "aform_field-label" };
447
505
  var _hoisted_18$1 = { class: "acurrency__field acurrency__field--exchange-rate" };
448
506
  var _hoisted_19$1 = ["value"];
449
507
  var _hoisted_20$1 = { class: "aform_field-label" };
450
- var _hoisted_21$1 = ["innerHTML"];
508
+ var _hoisted_21$1 = ["id"];
451
509
  var FLOAT_NOISE_DECIMALS = 6;
452
510
  //#endregion
453
511
  //#region src/components/form/ACurrencyInput.vue
@@ -461,7 +519,7 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
461
519
  required: { type: Boolean },
462
520
  mode: {},
463
521
  uuid: {},
464
- validation: { default: () => ({ errorMessage: " " }) },
522
+ validation: { default: () => ({ errorMessage: "" }) },
465
523
  errors: {},
466
524
  options: { default: () => ({}) },
467
525
  currencyLabel: { default: "Currency" },
@@ -470,9 +528,9 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
470
528
  exchangeRateLabel: { default: "Exchange Rate" }
471
529
  }, {
472
530
  "modelValue": { default: () => ({
473
- amount: 0,
531
+ amount: null,
474
532
  currency: { id: "" },
475
- baseAmount: 0,
533
+ baseAmount: null,
476
534
  baseCurrency: { id: "" },
477
535
  exchangeRate: 1
478
536
  }) },
@@ -480,6 +538,8 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
480
538
  }),
481
539
  emits: ["update:modelValue"],
482
540
  setup(__props) {
541
+ const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
542
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
483
543
  const currencySymbol = (value) => value.symbol ?? value.displayText ?? String(value.id);
484
544
  const modelValue = useModel(__props, "modelValue");
485
545
  const normalizedBaseCurrency = computed(() => {
@@ -528,16 +588,16 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
528
588
  currency: currencyValue,
529
589
  exchangeRate,
530
590
  baseCurrency: resolvedBaseCurrency.value,
531
- baseAmount: roundAmount(amount * exchangeRate)
591
+ baseAmount: amount === null ? null : roundAmount(amount * exchangeRate)
532
592
  };
533
593
  };
534
594
  const amount = computed({
535
- get: () => modelValue.value?.amount ?? 0,
536
- set: (value) => recompute(value, modelValue.value?.currency ?? { id: "" })
595
+ get: () => modelValue.value?.amount ?? null,
596
+ set: (value) => recompute(numberFromBox(value), modelValue.value?.currency ?? { id: "" })
537
597
  });
538
598
  const currency = computed({
539
599
  get: () => modelValue.value?.currency ?? { id: "" },
540
- set: (value) => recompute(modelValue.value?.amount ?? 0, value)
600
+ set: (value) => recompute(modelValue.value?.amount ?? null, value)
541
601
  });
542
602
  const amountNavigationKeys = /* @__PURE__ */ new Set([
543
603
  "Backspace",
@@ -571,7 +631,7 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
571
631
  });
572
632
  const displayText = computed(() => {
573
633
  const v = modelValue.value;
574
- if (!v || !v.currency?.id) return "—";
634
+ if (!v || !v.currency?.id || v.amount === null) return "—";
575
635
  const currencyText = v.currency.displayText ?? String(v.currency.id);
576
636
  const base = `${v.amount} ${currencyText}`;
577
637
  if (!showBase.value) return base;
@@ -580,7 +640,7 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
580
640
  });
581
641
  return (_ctx, _cache) => {
582
642
  return openBlock(), createElementBlock("div", _hoisted_1$18, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$17, toDisplayString(displayText.value), 1), createElementVNode("label", _hoisted_3$15, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
583
- createElementVNode("div", _hoisted_4$14, [createElementVNode("div", _hoisted_5$14, [createElementVNode("div", _hoisted_6$14, [createElementVNode("div", _hoisted_7$5, [createVNode(AFormLink_default, {
643
+ createElementVNode("div", _hoisted_4$14, [createElementVNode("div", _hoisted_5$14, [createElementVNode("div", _hoisted_6$11, [createElementVNode("div", _hoisted_7$5, [createVNode(AFormLink_default, {
584
644
  modelValue: currency.value,
585
645
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => currency.value = $event),
586
646
  mode: __props.mode,
@@ -611,6 +671,8 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
611
671
  type: "number",
612
672
  disabled: __props.mode === "read",
613
673
  required: __props.required,
674
+ "aria-invalid": unref(invalid),
675
+ "aria-describedby": unref(describedBy),
614
676
  onKeydown: onAmountKeydown,
615
677
  onPaste: onAmountPaste
616
678
  }, null, 40, _hoisted_9$4), [[
@@ -628,53 +690,96 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
628
690
  class: "aform_input-field acurrency__base-field",
629
691
  type: "text",
630
692
  disabled: ""
631
- }, null, 8, _hoisted_13$1), createElementVNode("label", _hoisted_14$1, toDisplayString(__props.baseCurrencyLabel), 1)]),
693
+ }, null, 8, _hoisted_13$2), createElementVNode("label", _hoisted_14$2, toDisplayString(__props.baseCurrencyLabel), 1)]),
632
694
  createElementVNode("div", _hoisted_15$1, [createElementVNode("input", {
633
- value: modelValue.value.baseAmount,
695
+ value: modelValue.value?.baseAmount,
634
696
  class: "aform_input-field acurrency__base-field",
635
697
  type: "number",
636
698
  disabled: ""
637
699
  }, null, 8, _hoisted_16$1), createElementVNode("label", _hoisted_17$1, toDisplayString(__props.baseAmountLabel), 1)]),
638
700
  createElementVNode("div", _hoisted_18$1, [createElementVNode("input", {
639
- value: modelValue.value.exchangeRate,
701
+ value: modelValue.value?.exchangeRate,
640
702
  class: "aform_input-field acurrency__base-field",
641
703
  type: "number",
642
704
  disabled: ""
643
705
  }, null, 8, _hoisted_19$1), createElementVNode("label", _hoisted_20$1, toDisplayString(__props.exchangeRateLabel), 1)])
644
706
  ]),
645
707
  withDirectives(createElementVNode("p", {
708
+ id: unref(errorId),
646
709
  class: "aform_error",
647
- innerHTML: __props.validation.errorMessage
648
- }, null, 8, _hoisted_21$1), [[vShow, __props.validation.errorMessage]])
710
+ role: "alert"
711
+ }, toDisplayString(errorText.value), 9, _hoisted_21$1), [[vShow, errorText.value]])
649
712
  ], 64))]);
650
713
  };
651
714
  }
652
- }), [["__scopeId", "data-v-77852830"]]);
715
+ }), [["__scopeId", "data-v-7c503cec"]]);
716
+ //#endregion
717
+ //#region src/utils/typedDay.ts
718
+ var TYPED_DAY_FORMAT = {
719
+ calendar: "gregory",
720
+ numberingSystem: "latn"
721
+ };
722
+ /**
723
+ * A day written as the locale writes it in numbers, for a box a user types days into.
724
+ * @param locale - The browser's own when omitted
725
+ */
726
+ function writeTypedDay(day, locale) {
727
+ return day.toLocaleString(locale, TYPED_DAY_FORMAT);
728
+ }
729
+ /**
730
+ * Reads a day typed as `writeTypedDay` writes it, or as `YYYY-MM-DD`. Any other text, or a day that does
731
+ * not exist, reads as no day. Not `new Date(text)`, which reads `YYYY-MM-DD` as UTC midnight and rolls an
732
+ * impossible day over.
733
+ * @param locale - The browser's own when omitted
734
+ */
735
+ function readTypedDay(text, locale) {
736
+ const isoDay = fromISODate(text.trim());
737
+ if (isoDay) return isoDay;
738
+ const numbers = text.match(/\d+/g);
739
+ if (numbers?.length !== 3) return void 0;
740
+ const order = new Intl.DateTimeFormat(locale, TYPED_DAY_FORMAT).formatToParts(0).map((part) => part.type).filter((type) => type === "year" || type === "month" || type === "day");
741
+ const typed = Object.fromEntries(order.map((type, index) => [type, numbers[index]]));
742
+ if (typed.year?.length !== 4) return void 0;
743
+ try {
744
+ return Temporal.PlainDate.from({
745
+ year: Number(typed.year),
746
+ month: Number(typed.month),
747
+ day: Number(typed.day)
748
+ }, { overflow: "reject" });
749
+ } catch {
750
+ return;
751
+ }
752
+ }
653
753
  //#endregion
654
754
  //#region src/components/form/ADatePicker.vue?vue&type=script&setup=true&lang.ts
655
755
  var _hoisted_1$17 = { class: "aform_display-value" };
656
756
  var _hoisted_2$16 = { key: 0 };
657
- var _hoisted_3$14 = ["innerHTML"];
658
- var _hoisted_4$13 = {
659
- ref: "datepicker",
660
- class: "adatepicker",
661
- tabindex: "0"
662
- };
663
- var _hoisted_5$13 = {
757
+ var _hoisted_3$14 = ["id"];
758
+ var _hoisted_4$13 = { class: "adatepicker" };
759
+ var _hoisted_5$13 = ["aria-label"];
760
+ var _hoisted_6$10 = {
664
761
  colspan: "5",
665
762
  tabindex: -1
666
763
  };
667
- var _hoisted_6$13 = { key: 0 };
668
- var _hoisted_7$4 = { colspan: "7" };
669
- var _hoisted_8$4 = { class: "date-input" };
670
- var _hoisted_9$3 = ["value"];
764
+ var _hoisted_7$4 = { key: 0 };
765
+ var _hoisted_8$4 = {
766
+ colspan: "7",
767
+ class: "range-inputs"
768
+ };
769
+ var _hoisted_9$3 = { class: "date-input" };
671
770
  var _hoisted_10$2 = ["value"];
672
- var _hoisted_11$2 = [
771
+ var _hoisted_11$2 = ["value"];
772
+ var _hoisted_12$2 = { class: "days-header" };
773
+ var _hoisted_13$1 = [
774
+ "tabindex",
775
+ "aria-label",
776
+ "aria-selected",
777
+ "aria-current",
673
778
  "onClick",
674
779
  "onKeydown",
675
780
  "onMouseover"
676
781
  ];
677
- var _hoisted_12$2 = ["innerHTML"];
782
+ var _hoisted_14$1 = ["id"];
678
783
  var numberOfRows = 6;
679
784
  var numberOfColumns = 7;
680
785
  //#endregion
@@ -695,18 +800,21 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
695
800
  validation: { default: () => ({ errorMessage: "" }) },
696
801
  errors: {}
697
802
  }, {
698
- "modelValue": { default: () => /* @__PURE__ */ new Date() },
803
+ "modelValue": {},
699
804
  "modelModifiers": {}
700
805
  }),
701
806
  emits: /*@__PURE__*/ mergeModels(["get-date"], ["update:modelValue"]),
702
807
  setup(__props, { expose: __expose, emit: __emit }) {
703
808
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
809
+ const { errorId } = fieldErrorA11y(__props.uuid, errorText);
704
810
  const date = useModel(__props, "modelValue");
705
- const selectedDate = ref(new Date(date.value));
706
- const currentMonth = ref(selectedDate.value.getMonth());
707
- const currentYear = ref(selectedDate.value.getFullYear());
811
+ const selectedDate = ref(fromISODate(date.value ?? "") ?? Temporal.Now.plainDateISO());
812
+ const currentMonth = ref(selectedDate.value.month - 1);
813
+ const currentYear = ref(selectedDate.value.year);
708
814
  const currentDates = ref([]);
709
- const hoveredDate = ref(new Date(date.value));
815
+ const focusedDay = ref(selectedDate.value);
816
+ const grid = useTemplateRef("grid");
817
+ const hoveredDate = ref(selectedDate.value);
710
818
  const start_date = ref(null);
711
819
  const end_date = ref(null);
712
820
  const startDateInput = useTemplateRef("start-date-input");
@@ -718,69 +826,65 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
718
826
  /*******************
719
827
  Computed
720
828
  *******************/
721
- const monthAndYear = computed(() => {
722
- return new Date(currentYear.value, currentMonth.value, 1).toLocaleDateString(void 0, {
723
- year: "numeric",
724
- month: "long"
725
- });
726
- });
727
- const getStartDate = computed(() => {
728
- return start_date.value != null ? parseDateToString(start_date.value) : "";
729
- });
730
- const getEndDate = computed(() => {
731
- return end_date.value != null ? parseDateToString(end_date.value) : "";
732
- });
829
+ const displayValue = computed(() => date.value ? fromISODate(date.value)?.toLocaleString() ?? "Invalid Date" : "");
830
+ const firstOfMonth = computed(() => Temporal.PlainDate.from({
831
+ year: currentYear.value,
832
+ month: currentMonth.value + 1,
833
+ day: 1
834
+ }));
835
+ const monthAndYear = computed(() => firstOfMonth.value.toLocaleString(void 0, {
836
+ year: "numeric",
837
+ month: "long"
838
+ }));
839
+ const weekdayLetters = computed(() => currentDates.value.slice(0, numberOfColumns).map((day) => day.toLocaleString(void 0, { weekday: "narrow" })));
840
+ const getStartDate = computed(() => boxText(start_date.value));
841
+ const getEndDate = computed(() => boxText(end_date.value));
733
842
  /*******************
734
843
  Functions
735
844
  *******************/
736
- const parseDateToString = (dateValue) => {
737
- if (!validateDate(dateValue)) return "";
738
- return dateValue.getMonth() + 1 + "/" + dateValue.getDate() + "/" + dateValue.getFullYear();
739
- };
845
+ const boxText = (day) => day ? writeTypedDay(day) : "";
740
846
  const isTodaysDate = (day) => {
741
- const todaysDate = /* @__PURE__ */ new Date();
742
- if (currentMonth.value !== todaysDate.getMonth()) return false;
743
- return todaysDate.toDateString() === new Date(day).toDateString();
847
+ const today = Temporal.Now.plainDateISO();
848
+ if (currentMonth.value !== today.month - 1) return false;
849
+ return day.equals(today);
744
850
  };
745
851
  const isSelectedDate = (day) => {
746
- return new Date(day).toDateString() === new Date(selectedDate.value).toDateString();
852
+ return day.equals(selectedDate.value);
747
853
  };
748
854
  const isStartDate = (day) => {
749
- const start = start_date.value;
750
- if (!validateDate(start)) return false;
751
- return new Date(day).toDateString() === start.toDateString();
855
+ return start_date.value !== null && day.equals(start_date.value);
752
856
  };
753
857
  const isEndDate = (day) => {
754
- const end = end_date.value;
755
- if (!validateDate(end)) return false;
756
- return new Date(day).toDateString() === end.toDateString();
757
- };
858
+ return end_date.value !== null && day.equals(end_date.value);
859
+ };
860
+ const isPicked = (day) => isSelectedDate(day) || __props.selectRange && (isStartDate(day) || isEndDate(day));
861
+ const fullDate = (day) => day.toLocaleString(void 0, {
862
+ weekday: "long",
863
+ year: "numeric",
864
+ month: "long",
865
+ day: "numeric"
866
+ });
758
867
  const getCurrentCell = (rowNo, colNo) => {
759
- return (rowNo - 1) * numberOfColumns + colNo;
868
+ return (rowNo - 1) * numberOfColumns + colNo - 1;
760
869
  };
761
870
  const isInDateRange = (day) => {
762
871
  const start = start_date.value;
763
- if (!validateDate(start)) return false;
764
- const this_date = new Date(day);
765
- const end = end_date.value;
766
- const temp_end_date = validateDate(end) ? end : new Date(hoveredDate.value);
767
- return this_date.getTime() > start.getTime() && this_date.getTime() < temp_end_date.getTime();
872
+ if (!start) return false;
873
+ const end = end_date.value ?? hoveredDate.value;
874
+ return Temporal.PlainDate.compare(day, start) > 0 && Temporal.PlainDate.compare(day, end) < 0;
768
875
  };
769
876
  const getCurrentDate = (rowNo, colNo) => {
770
877
  return currentDates.value[getCurrentCell(rowNo, colNo)];
771
878
  };
772
879
  const hoverDate = (currentIndex) => {
773
- hoveredDate.value = new Date(currentDates.value[currentIndex]);
880
+ hoveredDate.value = currentDates.value[currentIndex];
774
881
  };
775
882
  const preventCellSelection = (event) => {
776
883
  if (event.target?.tagName !== "INPUT") event.preventDefault();
777
884
  };
778
885
  const populateMonth = () => {
779
- currentDates.value = [];
780
- const firstOfMonth = new Date(currentYear.value, currentMonth.value, 1);
781
- const monthStartWeekday = firstOfMonth.getDay();
782
- const calendarStartDay = firstOfMonth.setDate(firstOfMonth.getDate() - monthStartWeekday);
783
- for (const dayIndex of Array(43).keys()) currentDates.value.push(calendarStartDay + dayIndex * 864e5);
886
+ const gridStart = firstOfMonth.value.subtract({ days: firstOfMonth.value.dayOfWeek - 1 });
887
+ currentDates.value = Array.from({ length: 42 }, (_, cellIndex) => gridStart.add({ days: cellIndex }));
784
888
  };
785
889
  const previousYear = () => currentYear.value -= 1;
786
890
  const nextYear = () => currentYear.value += 1;
@@ -799,102 +903,141 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
799
903
  const enterDate = (event) => {
800
904
  if (event.key === "Enter") enterInputDate();
801
905
  };
906
+ /** The day a key moves focus to from `day`, or undefined for a key that moves nothing. */
907
+ const dayAfterKey = (event, day) => {
908
+ switch (event.key) {
909
+ case "ArrowLeft": return day.subtract({ days: 1 });
910
+ case "ArrowRight": return day.add({ days: 1 });
911
+ case "ArrowUp": return day.subtract({ weeks: 1 });
912
+ case "ArrowDown": return day.add({ weeks: 1 });
913
+ case "PageUp": return day.subtract(event.shiftKey ? { years: 1 } : { months: 1 });
914
+ case "PageDown": return day.add(event.shiftKey ? { years: 1 } : { months: 1 });
915
+ case "Home": return day.subtract({ days: day.dayOfWeek - 1 });
916
+ case "End": return day.add({ days: 7 - day.dayOfWeek });
917
+ }
918
+ };
919
+ const focusTabStop = async () => {
920
+ await nextTick();
921
+ grid.value?.querySelector("[tabindex=\"0\"]")?.focus();
922
+ };
923
+ const onDayKeydown = (event, currentIndex) => {
924
+ if (event.key === "Enter" || event.key === " ") {
925
+ event.preventDefault();
926
+ selectDate(currentIndex);
927
+ return;
928
+ }
929
+ const next = dayAfterKey(event, focusedDay.value);
930
+ if (!next) return;
931
+ event.preventDefault();
932
+ focusedDay.value = next;
933
+ currentMonth.value = next.month - 1;
934
+ currentYear.value = next.year;
935
+ focusTabStop();
936
+ };
802
937
  const selectDate = (currentIndex) => {
803
- date.value = selectedDate.value = new Date(currentDates.value[currentIndex]);
938
+ const picked = currentDates.value[currentIndex];
939
+ selectedDate.value = picked;
940
+ focusedDay.value = picked;
941
+ if (document.activeElement instanceof HTMLTableCellElement && grid.value?.contains(document.activeElement)) focusTabStop();
942
+ date.value = picked.toString();
804
943
  if (__props.selectRange) {
805
944
  const start = start_date.value;
806
945
  if (start == null || end_date.value != null) {
807
- start_date.value = date.value;
946
+ start_date.value = picked;
808
947
  end_date.value = null;
809
- } else if (validateDate(start) && selectedDate.value.getTime() < start.getTime()) {
948
+ } else if (Temporal.PlainDate.compare(picked, start) < 0) {
810
949
  end_date.value = null;
811
- start_date.value = date.value;
812
- } else end_date.value = date.value;
813
- if (startDateInput.value) startDateInput.value.value = parseDateToString(start_date.value) ?? "";
814
- if (endDateInput.value) endDateInput.value.value = parseDateToString(end_date.value) ?? "";
950
+ start_date.value = picked;
951
+ } else end_date.value = picked;
952
+ if (startDateInput.value) startDateInput.value.value = boxText(start_date.value);
953
+ if (endDateInput.value) endDateInput.value.value = boxText(end_date.value);
815
954
  }
816
955
  emitData();
817
956
  };
818
957
  const testDateOrder = () => {
819
958
  const start = start_date.value;
820
959
  const end = end_date.value;
821
- if (validateDate(end) && validateDate(start) && end.getTime() < start.getTime()) [start_date.value, end_date.value] = [end, start];
822
- };
823
- const validateDate = (dateValue) => {
824
- return dateValue instanceof Date && !isNaN(dateValue.getTime());
960
+ if (start && end && Temporal.PlainDate.compare(end, start) < 0) [start_date.value, end_date.value] = [end, start];
825
961
  };
826
962
  const enterInputDate = () => {
827
963
  if (startDateInput.value?.value == "") start_date.value = null;
828
- else if (startDateInput.value) {
829
- const start = new Date(startDateInput.value.value);
830
- start_date.value = validateDate(start) ? start : null;
831
- }
964
+ else if (startDateInput.value) start_date.value = readTypedDay(startDateInput.value.value) ?? null;
832
965
  if (endDateInput.value?.value == "") end_date.value = null;
833
- else if (endDateInput.value) {
834
- const end = new Date(endDateInput.value.value);
835
- end_date.value = validateDate(end) ? end : null;
836
- }
837
- if (validateDate(start_date.value)) {
838
- if (validateDate(end_date.value)) testDateOrder();
966
+ else if (endDateInput.value) end_date.value = readTypedDay(endDateInput.value.value) ?? null;
967
+ if (start_date.value) {
968
+ if (end_date.value) testDateOrder();
839
969
  selectedDate.value = start_date.value;
840
970
  }
841
971
  emitData();
842
972
  };
843
973
  const emitData = () => {
844
974
  emit("get-date", {
845
- start: __props.selectRange ? start_date.value : null,
846
- end: __props.selectRange ? end_date.value : null,
847
- selected: selectedDate.value
975
+ start: __props.selectRange ? start_date.value?.toString() ?? null : null,
976
+ end: __props.selectRange ? end_date.value?.toString() ?? null : null,
977
+ selected: selectedDate.value.toString()
848
978
  });
849
979
  };
850
980
  /*******************
851
981
  Hooks
852
982
  *******************/
853
- onMounted(async () => {
854
- populateMonth();
855
- await nextTick();
856
- const $selectedDate = document.getElementsByClassName("selectedDate");
857
- if ($selectedDate.length > 0) $selectedDate[0].focus();
858
- else {
859
- const $todaysDate = document.getElementsByClassName("todaysDate");
860
- if ($todaysDate.length > 0) $todaysDate[0].focus();
861
- }
862
- });
983
+ populateMonth();
863
984
  /*******************
864
985
  Watchers
865
986
  *******************/
866
- watch([currentMonth, currentYear], populateMonth);
987
+ watch([currentMonth, currentYear], () => {
988
+ populateMonth();
989
+ const month = currentMonth.value + 1;
990
+ if (focusedDay.value.month !== month || focusedDay.value.year !== currentYear.value) focusedDay.value = focusedDay.value.with({
991
+ year: currentYear.value,
992
+ month
993
+ });
994
+ });
995
+ watch(date, (value) => {
996
+ const day = fromISODate(value ?? "");
997
+ if (!day || day.equals(selectedDate.value)) return;
998
+ selectedDate.value = day;
999
+ focusedDay.value = day;
1000
+ currentMonth.value = day.month - 1;
1001
+ currentYear.value = day.year;
1002
+ });
867
1003
  /*******************
868
1004
  Expose
869
1005
  *******************/
870
1006
  __expose({
871
1007
  currentMonth,
872
1008
  currentYear,
873
- selectedDate
1009
+ selectedDate: computed(() => selectedDate.value.toString())
874
1010
  });
875
1011
  return (_ctx, _cache) => {
876
1012
  return __props.mode === "display" || __props.mode === "read" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
877
- createElementVNode("span", _hoisted_1$17, toDisplayString(date.value ? new Date(date.value).toLocaleDateString() : ""), 1),
1013
+ createElementVNode("span", _hoisted_1$17, toDisplayString(displayValue.value), 1),
878
1014
  __props.label ? (openBlock(), createElementBlock("label", _hoisted_2$16, toDisplayString(__props.label), 1)) : createCommentVNode("", true),
879
1015
  withDirectives(createElementVNode("p", {
1016
+ id: unref(errorId),
880
1017
  class: "aform_error",
881
- innerHTML: errorText.value
882
- }, null, 8, _hoisted_3$14), [[vShow, errorText.value]])
883
- ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("div", _hoisted_4$13, [createElementVNode("table", { onMousedown: preventCellSelection }, [createElementVNode("tbody", null, [
1018
+ role: "alert"
1019
+ }, toDisplayString(errorText.value), 9, _hoisted_3$14), [[vShow, errorText.value]])
1020
+ ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("div", _hoisted_4$13, [createElementVNode("table", {
1021
+ ref_key: "grid",
1022
+ ref: grid,
1023
+ role: "grid",
1024
+ "aria-label": monthAndYear.value,
1025
+ onMousedown: preventCellSelection
1026
+ }, [createElementVNode("tbody", null, [
884
1027
  createElementVNode("tr", null, [
885
1028
  createElementVNode("td", {
886
1029
  id: "previous-month-btn",
887
1030
  tabindex: -1,
888
1031
  onClick: previousMonth
889
1032
  }, "<"),
890
- createElementVNode("th", _hoisted_5$13, toDisplayString(monthAndYear.value), 1),
1033
+ createElementVNode("th", _hoisted_6$10, toDisplayString(monthAndYear.value), 1),
891
1034
  createElementVNode("td", {
892
1035
  id: "next-month-btn",
893
1036
  tabindex: -1,
894
1037
  onClick: nextMonth
895
1038
  }, ">")
896
1039
  ]),
897
- __props.selectRange ? (openBlock(), createElementBlock("tr", _hoisted_6$13, [createElementVNode("td", _hoisted_7$4, [createElementVNode("div", _hoisted_8$4, [
1040
+ __props.selectRange ? (openBlock(), createElementBlock("tr", _hoisted_7$4, [createElementVNode("td", _hoisted_8$4, [createElementVNode("div", _hoisted_9$3, [
898
1041
  createElementVNode("input", {
899
1042
  ref: "start-date-input",
900
1043
  value: getStartDate.value,
@@ -903,7 +1046,7 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
903
1046
  placeholder: "start date",
904
1047
  onBlur: _cache[0] || (_cache[0] = ($event) => enterInputDate()),
905
1048
  onKeydown: enterDate
906
- }, null, 40, _hoisted_9$3),
1049
+ }, null, 40, _hoisted_10$2),
907
1050
  _cache[2] || (_cache[2] = createElementVNode("div", null, "-", -1)),
908
1051
  createElementVNode("input", {
909
1052
  ref: "end-date-input",
@@ -913,17 +1056,11 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
913
1056
  placeholder: "end date",
914
1057
  onBlur: _cache[1] || (_cache[1] = ($event) => enterInputDate()),
915
1058
  onKeydown: enterDate
916
- }, null, 40, _hoisted_10$2)
1059
+ }, null, 40, _hoisted_11$2)
917
1060
  ])])])) : createCommentVNode("", true),
918
- _cache[3] || (_cache[3] = createElementVNode("tr", { class: "days-header" }, [
919
- createElementVNode("td", null, "M"),
920
- createElementVNode("td", null, "T"),
921
- createElementVNode("td", null, "W"),
922
- createElementVNode("td", null, "T"),
923
- createElementVNode("td", null, "F"),
924
- createElementVNode("td", null, "S"),
925
- createElementVNode("td", null, "S")
926
- ], -1)),
1061
+ createElementVNode("tr", _hoisted_12$2, [(openBlock(true), createElementBlock(Fragment, null, renderList(weekdayLetters.value, (letter, weekdayIndex) => {
1062
+ return openBlock(), createElementBlock("td", { key: weekdayIndex }, toDisplayString(letter), 1);
1063
+ }), 128))]),
927
1064
  (openBlock(), createElementBlock(Fragment, null, renderList(numberOfRows, (rowNo) => {
928
1065
  return createElementVNode("tr", { key: rowNo }, [(openBlock(), createElementBlock(Fragment, null, renderList(numberOfColumns, (colNo) => {
929
1066
  return createElementVNode("td", {
@@ -939,20 +1076,24 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
939
1076
  }]),
940
1077
  contenteditable: false,
941
1078
  spellcheck: false,
942
- tabindex: 0,
1079
+ tabindex: getCurrentDate(rowNo, colNo).equals(focusedDay.value) ? 0 : -1,
1080
+ "aria-label": fullDate(getCurrentDate(rowNo, colNo)),
1081
+ "aria-selected": isPicked(getCurrentDate(rowNo, colNo)),
1082
+ "aria-current": isTodaysDate(getCurrentDate(rowNo, colNo)) ? "date" : void 0,
943
1083
  onClick: withModifiers(($event) => selectDate(getCurrentCell(rowNo, colNo)), ["prevent", "stop"]),
944
- onKeydown: withKeys(($event) => selectDate(getCurrentCell(rowNo, colNo)), ["enter"]),
1084
+ onKeydown: ($event) => onDayKeydown($event, getCurrentCell(rowNo, colNo)),
945
1085
  onMouseover: ($event) => hoverDate(getCurrentCell(rowNo, colNo))
946
- }, toDisplayString(new Date(getCurrentDate(rowNo, colNo)).getDate()), 43, _hoisted_11$2);
1086
+ }, toDisplayString(getCurrentDate(rowNo, colNo).day), 43, _hoisted_13$1);
947
1087
  }), 64))]);
948
1088
  }), 64))
949
- ])], 32)], 512), withDirectives(createElementVNode("p", {
1089
+ ])], 40, _hoisted_5$13)]), withDirectives(createElementVNode("p", {
1090
+ id: unref(errorId),
950
1091
  class: "aform_error",
951
- innerHTML: errorText.value
952
- }, null, 8, _hoisted_12$2), [[vShow, errorText.value]])], 64));
1092
+ role: "alert"
1093
+ }, toDisplayString(errorText.value), 9, _hoisted_14$1), [[vShow, errorText.value]])], 64));
953
1094
  };
954
1095
  }
955
- }), [["__scopeId", "data-v-ef698187"]]);
1096
+ }), [["__scopeId", "data-v-d0133599"]]);
956
1097
  //#endregion
957
1098
  //#region src/components/form/ADateTimeInput.vue?vue&type=script&setup=true&lang.ts
958
1099
  var _hoisted_1$16 = { class: "adate_time" };
@@ -963,7 +1104,7 @@ var _hoisted_5$12 = {
963
1104
  key: 0,
964
1105
  class: "colon"
965
1106
  };
966
- var _hoisted_6$12 = ["onKeydown"];
1107
+ var _hoisted_6$9 = ["onKeydown"];
967
1108
  //#endregion
968
1109
  //#region src/components/form/ADateTimeInput.vue
969
1110
  var ADateTimeInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1137,7 +1278,7 @@ var ADateTimeInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1137
1278
  _cache[8] || (_cache[8] = withKeys(withModifiers(($event) => tick("seconds"), ["prevent"]), ["up"])),
1138
1279
  _cache[9] || (_cache[9] = withKeys(withModifiers(($event) => tick("seconds", -1), ["prevent"]), ["down"]))
1139
1280
  ]
1140
- }, null, 40, _hoisted_6$12)), [[vModelText, timeData.seconds]]) : createCommentVNode("", true),
1281
+ }, null, 40, _hoisted_6$9)), [[vModelText, timeData.seconds]]) : createCommentVNode("", true),
1141
1282
  !__props.allowMilitaryTime ? withDirectives((openBlock(), createElementBlock("select", {
1142
1283
  key: 2,
1143
1284
  ref: "meridiem-selector",
@@ -1148,7 +1289,7 @@ var ADateTimeInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1148
1289
  ])]);
1149
1290
  };
1150
1291
  }
1151
- }), [["__scopeId", "data-v-95ad0aa4"]]);
1292
+ }), [["__scopeId", "data-v-3a6e48db"]]);
1152
1293
  //#endregion
1153
1294
  //#region src/components/form/ADateSelection.vue?vue&type=script&setup=true&lang.ts
1154
1295
  var _hoisted_1$15 = { class: "adate-selection" };
@@ -1181,6 +1322,7 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1181
1322
  type: Boolean,
1182
1323
  default: false
1183
1324
  },
1325
+ defaultDate: {},
1184
1326
  defaultHours: { default: 12 },
1185
1327
  defaultMinutes: { default: 0 },
1186
1328
  defaultSeconds: { default: 0 },
@@ -1198,15 +1340,14 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1198
1340
  setup(__props, { emit: __emit }) {
1199
1341
  const emit = __emit;
1200
1342
  provide("select-range", __props.selectRange);
1201
- const today = /* @__PURE__ */ new Date();
1343
+ const today = Temporal.Now.plainDateISO();
1202
1344
  const pickerStart = ref(today);
1203
1345
  const pickerEnd = ref(today);
1204
1346
  const startTimeMs = ref(0);
1205
1347
  const endTimeMs = ref(0);
1206
- const mergeDateTime = (date, timeMs) => {
1207
- const d = new Date(date);
1208
- d.setHours(0, 0, 0, 0);
1209
- return new Date(d.getTime() + timeMs);
1348
+ const mergeDateTime = (day, timeMs) => {
1349
+ const midnight = day.toZonedDateTime(Temporal.Now.timeZoneId());
1350
+ return new Date(midnight.epochMilliseconds + timeMs);
1210
1351
  };
1211
1352
  const timePayloadToMs = (payload) => {
1212
1353
  let h;
@@ -1228,8 +1369,8 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1228
1369
  const handleDate = (data) => {
1229
1370
  emit("get-date", data);
1230
1371
  if (__props.selectRange) {
1231
- pickerStart.value = data.start ?? data.selected;
1232
- pickerEnd.value = data.end ?? data.selected;
1372
+ pickerStart.value = fromISODate(data.start ?? data.selected) ?? today;
1373
+ pickerEnd.value = fromISODate(data.end ?? data.selected) ?? today;
1233
1374
  tryEmitRange("user");
1234
1375
  }
1235
1376
  };
@@ -1246,9 +1387,10 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1246
1387
  return openBlock(), createElementBlock("div", _hoisted_1$15, [
1247
1388
  __props.showDate ? (openBlock(), createBlock(ADatePicker_default, {
1248
1389
  key: 0,
1390
+ "model-value": __props.defaultDate,
1249
1391
  "select-range": __props.selectRange,
1250
1392
  onGetDate: handleDate
1251
- }, null, 8, ["select-range"])) : createCommentVNode("", true),
1393
+ }, null, 8, ["model-value", "select-range"])) : createCommentVNode("", true),
1252
1394
  __props.showTime ? (openBlock(), createBlock(ADateTimeInput_default, {
1253
1395
  key: 1,
1254
1396
  "allow-military-time": __props.allowMilitaryTime,
@@ -1286,19 +1428,67 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1286
1428
  ]);
1287
1429
  };
1288
1430
  }
1289
- }), [["__scopeId", "data-v-36e737ea"]]);
1431
+ }), [["__scopeId", "data-v-4a079b23"]]);
1432
+ //#endregion
1433
+ //#region src/utils/fieldCalendar.ts
1434
+ /** The keys a date field opens its calendar on: the ones a date input opens the browser's own calendar on, in Chrome. */
1435
+ var opensCalendar = (event) => event.key === " " || event.key === "F4" || event.altKey && event.key === "ArrowDown";
1436
+ /**
1437
+ * The calendar a date field opens from its box, and the keys and focus that go with it.
1438
+ * @param box - the field's box, which focus returns to when the calendar closes
1439
+ * @param calendar - the calendar, while it is open
1440
+ * @param uuid - the field's own id, when it has one
1441
+ */
1442
+ function useFieldCalendar(box, calendar, uuid) {
1443
+ const showPicker = ref(false);
1444
+ const calendarId = `${uuid ?? `aform-date-${useId()}`}-calendar`;
1445
+ onClickOutside(calendar, () => showPicker.value = false);
1446
+ const closePicker = () => {
1447
+ box.value?.focus();
1448
+ showPicker.value = false;
1449
+ };
1450
+ const openFromKey = async (event) => {
1451
+ const readOnlyDown = event.key === "ArrowDown" && box.value instanceof HTMLInputElement && box.value.readOnly;
1452
+ if (!opensCalendar(event) && !readOnlyDown) return;
1453
+ event.preventDefault();
1454
+ showPicker.value = true;
1455
+ await nextTick();
1456
+ const root = calendar.value?.$el;
1457
+ if (root instanceof HTMLElement) root.querySelector("[role=\"grid\"] [tabindex=\"0\"]")?.focus();
1458
+ };
1459
+ const closeOnEscape = (event) => {
1460
+ if (event.key !== "Escape" || !showPicker.value) return;
1461
+ event.stopPropagation();
1462
+ closePicker();
1463
+ };
1464
+ const closeWhenFocusLeaves = (event) => {
1465
+ const field = event.currentTarget;
1466
+ const next = event.relatedTarget;
1467
+ if (!(field instanceof Node && next instanceof Node && field.contains(next))) showPicker.value = false;
1468
+ };
1469
+ return {
1470
+ showPicker,
1471
+ calendarId,
1472
+ closePicker,
1473
+ openFromKey,
1474
+ closeOnEscape,
1475
+ closeWhenFocusLeaves
1476
+ };
1477
+ }
1290
1478
  //#endregion
1291
1479
  //#region src/components/form/ADate.vue?vue&type=script&setup=true&lang.ts
1292
- var _hoisted_1$14 = { class: "aform_form-element" };
1293
- var _hoisted_2$13 = { class: "aform_display-value" };
1294
- var _hoisted_3$12 = { class: "aform_field-label" };
1295
- var _hoisted_4$11 = [
1480
+ var _hoisted_1$14 = { class: "aform_display-value" };
1481
+ var _hoisted_2$13 = { class: "aform_field-label" };
1482
+ var _hoisted_3$12 = [
1296
1483
  "id",
1484
+ "aria-controls",
1297
1485
  "disabled",
1298
- "required"
1486
+ "required",
1487
+ "aria-invalid",
1488
+ "aria-describedby"
1299
1489
  ];
1300
- var _hoisted_5$11 = ["for"];
1301
- var _hoisted_6$11 = ["innerHTML"];
1490
+ var _hoisted_4$11 = ["for"];
1491
+ var _hoisted_5$11 = ["id"];
1302
1492
  //#endregion
1303
1493
  //#region src/components/form/ADate.vue
1304
1494
  var ADate_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1320,61 +1510,71 @@ var ADate_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
1320
1510
  emits: ["update:modelValue"],
1321
1511
  setup(__props) {
1322
1512
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
1513
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
1323
1514
  const modelValue = useModel(__props, "modelValue");
1324
- const currentDate = ref(modelValue.value ? new Date(modelValue.value) : /* @__PURE__ */ new Date());
1325
- const inputDate = computed({
1326
- get: () => currentDate.value.toISOString().split("T")[0],
1327
- set: (value) => {
1328
- currentDate.value = new Date(value);
1329
- modelValue.value = value;
1330
- }
1515
+ const boxDay = computed({
1516
+ get: () => modelValue.value,
1517
+ set: (text) => modelValue.value = dayFromBox(text)
1331
1518
  });
1332
- const pickerRef = useTemplateRef("picker");
1333
- const showPicker = ref(false);
1334
- onClickOutside(pickerRef, () => showPicker.value = false);
1519
+ const displayValue = computed(() => modelValue.value ? fromISODate(modelValue.value)?.toLocaleString() ?? "Invalid Date" : "");
1520
+ const { showPicker, calendarId, closePicker, openFromKey, closeOnEscape, closeWhenFocusLeaves } = useFieldCalendar(useTemplateRef("date"), useTemplateRef("picker"), __props.uuid);
1335
1521
  const openPicker = () => {
1336
1522
  if (__props.mode !== "read") showPicker.value = !showPicker.value;
1337
1523
  };
1338
- watch(() => modelValue.value, (newValue) => {
1339
- if (newValue) currentDate.value = new Date(newValue);
1340
- });
1341
1524
  const handleDate = (data) => {
1342
- currentDate.value = data.selected;
1343
- modelValue.value = inputDate.value;
1344
- showPicker.value = false;
1525
+ modelValue.value = data.selected;
1526
+ closePicker();
1345
1527
  };
1346
1528
  return (_ctx, _cache) => {
1347
- return openBlock(), createElementBlock("div", _hoisted_1$14, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$13, toDisplayString(modelValue.value ? new Date(inputDate.value).toLocaleDateString() : ""), 1), createElementVNode("label", _hoisted_3$12, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
1529
+ return openBlock(), createElementBlock("div", {
1530
+ class: "aform_form-element",
1531
+ onFocusout: _cache[2] || (_cache[2] = (...args) => unref(closeWhenFocusLeaves) && unref(closeWhenFocusLeaves)(...args)),
1532
+ onKeydown: _cache[3] || (_cache[3] = (...args) => unref(closeOnEscape) && unref(closeOnEscape)(...args))
1533
+ }, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_1$14, toDisplayString(displayValue.value), 1), createElementVNode("label", _hoisted_2$13, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
1348
1534
  withDirectives(createElementVNode("input", {
1349
1535
  id: __props.uuid,
1350
1536
  ref: "date",
1351
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputDate.value = $event),
1537
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => boxDay.value = $event),
1352
1538
  class: "aform_input-field",
1353
1539
  type: "date",
1540
+ "aria-haspopup": "dialog",
1541
+ "aria-controls": unref(showPicker) ? unref(calendarId) : void 0,
1354
1542
  disabled: __props.mode === "read",
1355
1543
  required: __props.required,
1356
- onClick: openPicker
1357
- }, null, 8, _hoisted_4$11), [[vModelText, inputDate.value]]),
1544
+ "aria-invalid": unref(invalid),
1545
+ "aria-describedby": unref(describedBy),
1546
+ onClick: withModifiers(openPicker, ["prevent"]),
1547
+ onKeydown: _cache[1] || (_cache[1] = (...args) => unref(openFromKey) && unref(openFromKey)(...args))
1548
+ }, null, 40, _hoisted_3$12), [[vModelText, boxDay.value]]),
1358
1549
  createElementVNode("label", {
1359
1550
  class: "aform_field-label",
1360
1551
  for: __props.uuid
1361
- }, toDisplayString(__props.label), 9, _hoisted_5$11),
1552
+ }, toDisplayString(__props.label), 9, _hoisted_4$11),
1362
1553
  withDirectives(createElementVNode("p", {
1554
+ id: unref(errorId),
1363
1555
  class: "aform_error",
1364
- innerHTML: errorText.value
1365
- }, null, 8, _hoisted_6$11), [[vShow, errorText.value]]),
1366
- showPicker.value ? (openBlock(), createBlock(ADateSelection_default, {
1556
+ role: "alert"
1557
+ }, toDisplayString(errorText.value), 9, _hoisted_5$11), [[vShow, errorText.value]]),
1558
+ unref(showPicker) ? (openBlock(), createBlock(ADateSelection_default, {
1367
1559
  key: 0,
1560
+ id: unref(calendarId),
1368
1561
  ref: "picker",
1369
1562
  class: "adate-picker",
1563
+ role: "dialog",
1564
+ "aria-label": __props.label,
1565
+ "default-date": modelValue.value || void 0,
1370
1566
  "select-range": false,
1371
1567
  "show-time": false,
1372
1568
  onGetDate: handleDate
1373
- }, null, 512)) : createCommentVNode("", true)
1374
- ], 64))]);
1569
+ }, null, 8, [
1570
+ "id",
1571
+ "aria-label",
1572
+ "default-date"
1573
+ ])) : createCommentVNode("", true)
1574
+ ], 64))], 32);
1375
1575
  };
1376
1576
  }
1377
- }), [["__scopeId", "data-v-5de3fe7a"]]);
1577
+ }), [["__scopeId", "data-v-16f8f8f2"]]);
1378
1578
  //#endregion
1379
1579
  //#region src/components/form/ADropdown.vue?vue&type=script&setup=true&lang.ts
1380
1580
  var _hoisted_1$13 = { class: "aform_form-element" };
@@ -1383,18 +1583,28 @@ var _hoisted_2$12 = {
1383
1583
  class: "aform_display-value"
1384
1584
  };
1385
1585
  var _hoisted_3$11 = { class: "aform_field-label" };
1386
- var _hoisted_4$10 = ["disabled"];
1387
- var _hoisted_5$10 = {
1388
- id: "autocomplete-results",
1389
- class: "autocomplete-results"
1390
- };
1391
- var _hoisted_6$10 = {
1586
+ var _hoisted_4$10 = [
1587
+ "id",
1588
+ "disabled",
1589
+ "aria-expanded",
1590
+ "aria-controls",
1591
+ "aria-activedescendant",
1592
+ "aria-invalid",
1593
+ "aria-describedby",
1594
+ "onKeydown"
1595
+ ];
1596
+ var _hoisted_5$10 = ["id", "aria-label"];
1597
+ var _hoisted_6$8 = {
1392
1598
  key: 0,
1393
1599
  class: "loading autocomplete-result"
1394
1600
  };
1395
- var _hoisted_7$3 = ["onClick"];
1396
- var _hoisted_8$3 = { class: "aform_field-label" };
1397
- var _hoisted_9$2 = ["innerHTML"];
1601
+ var _hoisted_7$3 = [
1602
+ "id",
1603
+ "aria-selected",
1604
+ "onMousedown"
1605
+ ];
1606
+ var _hoisted_8$3 = ["for"];
1607
+ var _hoisted_9$2 = ["id"];
1398
1608
  //#endregion
1399
1609
  //#region src/components/form/ADropdown.vue
1400
1610
  var ADropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1425,12 +1635,14 @@ var ADropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1425
1635
  }),
1426
1636
  emits: ["update:modelValue"],
1427
1637
  setup(__props) {
1638
+ const listboxId = useId();
1428
1639
  const choiceList = computed(() => selectChoices(__props.options));
1429
1640
  const formatFn = computed(() => __props.format ? deserializeFunction(__props.format) : void 0);
1430
1641
  const badgeDescriptor = computed(() => resolveFieldBadge(search.value, __props.options, formatFn.value));
1431
1642
  const inputAccentStyle = computed(() => badgeInputAccentStyle(badgeDescriptor.value));
1432
1643
  const displayAccentStyle = computed(() => badgeInputAccentStyle(badgeDescriptor.value));
1433
1644
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
1645
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
1434
1646
  const search = useModel(__props, "modelValue");
1435
1647
  const committedValue = ref(search.value ?? "");
1436
1648
  const dropdown = reactive({
@@ -1508,36 +1720,57 @@ var ADropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1508
1720
  style: normalizeStyle(displayAccentStyle.value)
1509
1721
  }, toDisplayString(badgeDescriptor.value.label), 5)) : (openBlock(), createElementBlock("span", _hoisted_2$12, toDisplayString(search.value ?? ""), 1)), createElementVNode("label", _hoisted_3$11, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [withDirectives((openBlock(), createElementBlock("div", { class: normalizeClass(["autocomplete", { isOpen: dropdown.open }]) }, [
1510
1722
  withDirectives(createElementVNode("input", {
1723
+ id: __props.uuid,
1511
1724
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => search.value = $event),
1512
1725
  type: "text",
1726
+ role: "combobox",
1727
+ autocomplete: "off",
1728
+ "aria-autocomplete": "list",
1513
1729
  class: "aform_input-field",
1514
1730
  disabled: __props.mode === "read",
1515
1731
  style: normalizeStyle(inputAccentStyle.value),
1732
+ "aria-expanded": dropdown.open,
1733
+ "aria-controls": dropdown.open ? unref(listboxId) : void 0,
1734
+ "aria-activedescendant": dropdown.activeItemIndex === null ? void 0 : `${unref(listboxId)}-opt-${dropdown.activeItemIndex}`,
1735
+ "aria-invalid": unref(invalid),
1736
+ "aria-describedby": unref(describedBy),
1516
1737
  onInput: filter,
1517
1738
  onFocus: openDropdown,
1518
1739
  onKeydown: [
1519
- withKeys(selectNextResult, ["down"]),
1520
- withKeys(selectPrevResult, ["up"]),
1521
- withKeys(setCurrentResult, ["enter"]),
1740
+ withKeys(withModifiers(selectNextResult, ["prevent"]), ["down"]),
1741
+ withKeys(withModifiers(selectPrevResult, ["prevent"]), ["up"]),
1742
+ withKeys(withModifiers(setCurrentResult, ["prevent"]), ["enter"]),
1522
1743
  withKeys(onClickOutside, ["esc"]),
1523
1744
  withKeys(onClickOutside, ["tab"])
1524
1745
  ]
1525
1746
  }, null, 44, _hoisted_4$10), [[vModelText, search.value]]),
1526
- withDirectives(createElementVNode("ul", _hoisted_5$10, [dropdown.loading ? (openBlock(), createElementBlock("li", _hoisted_6$10, "Loading results...")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(dropdown.results, (result, i) => {
1747
+ withDirectives(createElementVNode("ul", {
1748
+ id: unref(listboxId),
1749
+ class: "autocomplete-results",
1750
+ role: "listbox",
1751
+ "aria-label": __props.label
1752
+ }, [dropdown.loading ? (openBlock(), createElementBlock("li", _hoisted_6$8, "Loading results...")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(dropdown.results, (result, i) => {
1527
1753
  return openBlock(), createElementBlock("li", {
1754
+ id: `${unref(listboxId)}-opt-${i}`,
1528
1755
  key: result,
1756
+ role: "option",
1757
+ "aria-selected": i === dropdown.activeItemIndex,
1529
1758
  class: normalizeClass(["autocomplete-result", { "is-active": i === dropdown.activeItemIndex }]),
1530
- onClick: withModifiers(($event) => setResult(result), ["stop"])
1531
- }, toDisplayString(result), 11, _hoisted_7$3);
1532
- }), 128))], 512), [[vShow, dropdown.open]]),
1533
- createElementVNode("label", _hoisted_8$3, toDisplayString(__props.label), 1)
1759
+ onMousedown: withModifiers(($event) => setResult(result), ["prevent"])
1760
+ }, toDisplayString(result), 43, _hoisted_7$3);
1761
+ }), 128))], 8, _hoisted_5$10), [[vShow, dropdown.open]]),
1762
+ createElementVNode("label", {
1763
+ class: "aform_field-label",
1764
+ for: __props.uuid
1765
+ }, toDisplayString(__props.label), 9, _hoisted_8$3)
1534
1766
  ], 2)), [[unref(vOnClickOutside), onClickOutside]]), withDirectives(createElementVNode("p", {
1767
+ id: unref(errorId),
1535
1768
  class: "aform_error",
1536
- innerHTML: errorText.value
1537
- }, null, 8, _hoisted_9$2), [[vShow, errorText.value]])], 64))]);
1769
+ role: "alert"
1770
+ }, toDisplayString(errorText.value), 9, _hoisted_9$2), [[vShow, errorText.value]])], 64))]);
1538
1771
  };
1539
1772
  }
1540
- }), [["__scopeId", "data-v-4206fcf7"]]);
1773
+ }), [["__scopeId", "data-v-2275075e"]]);
1541
1774
  //#endregion
1542
1775
  //#region src/components/form/ABadge.vue
1543
1776
  var ABadge_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1599,7 +1832,9 @@ var _hoisted_3$10 = [
1599
1832
  "role",
1600
1833
  "aria-labelledby",
1601
1834
  "aria-label",
1602
- "aria-busy"
1835
+ "aria-busy",
1836
+ "aria-invalid",
1837
+ "aria-describedby"
1603
1838
  ];
1604
1839
  var _hoisted_4$9 = ["data-segment-value"];
1605
1840
  var _hoisted_5$9 = [
@@ -1607,13 +1842,13 @@ var _hoisted_5$9 = [
1607
1842
  "value",
1608
1843
  "disabled"
1609
1844
  ];
1610
- var _hoisted_6$9 = [
1845
+ var _hoisted_6$7 = [
1611
1846
  "name",
1612
1847
  "value",
1613
1848
  "disabled"
1614
1849
  ];
1615
1850
  var _hoisted_7$2 = { class: "aform_segmented-label" };
1616
- var _hoisted_8$2 = ["innerHTML"];
1851
+ var _hoisted_8$2 = ["id"];
1617
1852
  //#endregion
1618
1853
  //#region src/components/form/ASegmentedControl.vue
1619
1854
  var ASegmentedControl_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1661,6 +1896,7 @@ var ASegmentedControl_default = /*#__PURE__*/ _plugin_vue_export_helper_default(
1661
1896
  }
1662
1897
  });
1663
1898
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
1899
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
1664
1900
  const fallbackId = useId();
1665
1901
  const groupName = computed(() => __props.uuid ?? `aform-segmented-${fallbackId}`);
1666
1902
  const labelId = computed(() => `${groupName.value}-label`);
@@ -1729,7 +1965,9 @@ var ASegmentedControl_default = /*#__PURE__*/ _plugin_vue_export_helper_default(
1729
1965
  class: "aform_segmented-track",
1730
1966
  "aria-labelledby": groupAriaLabel.value ? void 0 : labelId.value,
1731
1967
  "aria-label": groupAriaLabel.value,
1732
- "aria-busy": __props.busy || void 0
1968
+ "aria-busy": __props.busy || void 0,
1969
+ "aria-invalid": unref(invalid),
1970
+ "aria-describedby": unref(describedBy)
1733
1971
  }, [(openBlock(true), createElementBlock(Fragment, null, renderList(choices.value, (choice) => {
1734
1972
  return openBlock(), createElementBlock("label", {
1735
1973
  key: choice,
@@ -1752,28 +1990,32 @@ var ASegmentedControl_default = /*#__PURE__*/ _plugin_vue_export_helper_default(
1752
1990
  name: groupName.value,
1753
1991
  value: choice,
1754
1992
  disabled: __props.mode === "read"
1755
- }, null, 8, _hoisted_6$9)), [[vModelRadio, selected.value]]), createElementVNode("span", _hoisted_7$2, toDisplayString(segmentLabel(choice)), 1)], 14, _hoisted_4$9);
1993
+ }, null, 8, _hoisted_6$7)), [[vModelRadio, selected.value]]), createElementVNode("span", _hoisted_7$2, toDisplayString(segmentLabel(choice)), 1)], 14, _hoisted_4$9);
1756
1994
  }), 128))], 8, _hoisted_3$10),
1757
1995
  withDirectives(createElementVNode("p", {
1996
+ id: unref(errorId),
1758
1997
  class: "aform_error",
1759
- innerHTML: errorText.value
1760
- }, null, 8, _hoisted_8$2), [[vShow, errorText.value]])
1998
+ role: "alert"
1999
+ }, toDisplayString(errorText.value), 9, _hoisted_8$2), [[vShow, errorText.value]])
1761
2000
  ], 64))], 2);
1762
2001
  };
1763
2002
  }
1764
- }), [["__scopeId", "data-v-23f09651"]]);
2003
+ }), [["__scopeId", "data-v-8b740fd8"]]);
1765
2004
  //#endregion
1766
2005
  //#region src/components/form/ADateTime.vue?vue&type=script&setup=true&lang.ts
1767
- var _hoisted_1$11 = { class: "aform_form-element" };
1768
- var _hoisted_2$10 = { class: "aform_display-value" };
1769
- var _hoisted_3$9 = { class: "aform_field-label" };
1770
- var _hoisted_4$8 = [
2006
+ var _hoisted_1$11 = { class: "aform_display-value" };
2007
+ var _hoisted_2$10 = { class: "aform_field-label" };
2008
+ var _hoisted_3$9 = [
1771
2009
  "id",
2010
+ "aria-expanded",
2011
+ "aria-controls",
1772
2012
  "value",
1773
- "disabled"
2013
+ "disabled",
2014
+ "aria-invalid",
2015
+ "aria-describedby"
1774
2016
  ];
1775
- var _hoisted_5$8 = ["for"];
1776
- var _hoisted_6$8 = ["innerHTML"];
2017
+ var _hoisted_4$8 = ["for"];
2018
+ var _hoisted_5$8 = ["id"];
1777
2019
  //#endregion
1778
2020
  //#region src/components/form/ADateTime.vue
1779
2021
  var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1803,79 +2045,121 @@ var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1803
2045
  emits: ["update:modelValue"],
1804
2046
  setup(__props) {
1805
2047
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
2048
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
1806
2049
  const modelValue = useModel(__props, "modelValue");
1807
- const currentDateTime = ref(modelValue.value ? new Date(modelValue.value) : /* @__PURE__ */ new Date());
1808
- const showPicker = ref(false);
1809
- const pickerRef = ref(null);
1810
- onClickOutside(pickerRef, () => showPicker.value = false);
2050
+ /** Now, cut down to the smallest unit the picker shows, so a pick that leaves the time alone saves the time shown. */
2051
+ const now = () => Temporal.Now.zonedDateTimeISO().round({
2052
+ smallestUnit: __props.useSeconds ? "second" : "minute",
2053
+ roundingMode: "trunc"
2054
+ });
2055
+ /** The field's moment on the user's clock, or null when the value names no moment. */
2056
+ const readMoment = (value) => {
2057
+ const epochMilliseconds = new Date(value).getTime();
2058
+ if (isNaN(epochMilliseconds)) return null;
2059
+ return Temporal.Instant.fromEpochMilliseconds(epochMilliseconds).toZonedDateTimeISO(Temporal.Now.timeZoneId());
2060
+ };
2061
+ /** What the field holds for a value: its moment, or now for an empty field, where the picker starts. */
2062
+ const heldMoment = (value) => value ? readMoment(value) : now();
2063
+ const currentDateTime = ref(heldMoment(modelValue.value));
2064
+ const { showPicker, calendarId, openFromKey, closeOnEscape, closeWhenFocusLeaves } = useFieldCalendar(useTemplateRef("box"), useTemplateRef("picker"), __props.uuid);
1811
2065
  const openPicker = () => {
1812
2066
  if (__props.mode !== "read") showPicker.value = true;
1813
2067
  };
2068
+ const WITHOUT_SECONDS = {
2069
+ year: "numeric",
2070
+ month: "numeric",
2071
+ day: "numeric",
2072
+ hour: "numeric",
2073
+ minute: "numeric"
2074
+ };
1814
2075
  const displayValue = computed(() => {
1815
2076
  if (!modelValue.value) return "";
1816
- return currentDateTime.value.toLocaleString();
2077
+ const format = __props.useSeconds ? void 0 : WITHOUT_SECONDS;
2078
+ return currentDateTime.value?.toPlainDateTime().toLocaleString(void 0, format) ?? "Invalid Date";
1817
2079
  });
1818
2080
  const datetimeDisplay = computed(() => displayValue.value);
1819
2081
  const pickerDefaults = computed(() => {
1820
- const d = currentDateTime.value;
1821
- const hours24 = d.getHours();
2082
+ const d = currentDateTime.value ?? now();
2083
+ const hours24 = d.hour;
1822
2084
  const meridiem = hours24 >= 12 ? "PM" : "AM";
1823
2085
  const hours12 = hours24 % 12 || 12;
1824
2086
  return {
1825
2087
  hours: __props.allowMilitaryTime ? hours24 : hours12,
1826
- minutes: d.getMinutes(),
1827
- seconds: d.getSeconds(),
2088
+ minutes: d.minute,
2089
+ seconds: d.second,
1828
2090
  meridiem
1829
2091
  };
1830
2092
  });
1831
- const emitModel = () => {
1832
- modelValue.value = currentDateTime.value.toISOString();
2093
+ const setMoment = (moment) => {
2094
+ currentDateTime.value = moment;
2095
+ modelValue.value = moment.toInstant().toString({ fractionalSecondDigits: 3 });
1833
2096
  };
1834
2097
  const handleDate = (data) => {
1835
- const next = new Date(currentDateTime.value);
1836
- next.setFullYear(data.selected.getFullYear(), data.selected.getMonth(), data.selected.getDate());
1837
- currentDateTime.value = next;
1838
- emitModel();
2098
+ const day = fromISODate(data.selected);
2099
+ if (!day) return;
2100
+ setMoment((currentDateTime.value ?? now()).with({
2101
+ year: day.year,
2102
+ month: day.month,
2103
+ day: day.day
2104
+ }));
1839
2105
  };
1840
2106
  const handleTime = (data) => {
1841
2107
  if (data.source === "init") return;
1842
- const next = new Date(currentDateTime.value);
1843
2108
  const hours = data.militaryTime ?? data.hours;
1844
- next.setHours(hours, data.minutes, __props.useSeconds ? data.seconds : 0, 0);
1845
- currentDateTime.value = next;
1846
- emitModel();
1847
- };
1848
- watch(() => modelValue.value, (newValue) => {
1849
- if (newValue) currentDateTime.value = new Date(newValue);
1850
- });
2109
+ setMoment((currentDateTime.value ?? now()).with({
2110
+ hour: hours,
2111
+ minute: data.minutes,
2112
+ second: __props.useSeconds ? data.seconds : 0,
2113
+ millisecond: 0,
2114
+ microsecond: 0,
2115
+ nanosecond: 0
2116
+ }));
2117
+ };
2118
+ watch(() => modelValue.value, (newValue) => currentDateTime.value = heldMoment(newValue));
1851
2119
  return (_ctx, _cache) => {
1852
- return openBlock(), createElementBlock("div", _hoisted_1$11, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$10, toDisplayString(displayValue.value), 1), createElementVNode("label", _hoisted_3$9, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2120
+ return openBlock(), createElementBlock("div", {
2121
+ class: "aform_form-element",
2122
+ onFocusout: _cache[1] || (_cache[1] = (...args) => unref(closeWhenFocusLeaves) && unref(closeWhenFocusLeaves)(...args)),
2123
+ onKeydown: _cache[2] || (_cache[2] = (...args) => unref(closeOnEscape) && unref(closeOnEscape)(...args))
2124
+ }, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_1$11, toDisplayString(displayValue.value), 1), createElementVNode("label", _hoisted_2$10, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
1853
2125
  createElementVNode("input", {
1854
2126
  id: __props.uuid,
2127
+ ref: "box",
1855
2128
  class: "aform_input-field",
1856
2129
  type: "text",
2130
+ role: "combobox",
2131
+ "aria-haspopup": "dialog",
2132
+ "aria-expanded": unref(showPicker),
2133
+ "aria-controls": unref(showPicker) ? unref(calendarId) : void 0,
1857
2134
  value: datetimeDisplay.value,
1858
2135
  placeholder: "Select date and time",
1859
2136
  disabled: __props.mode === "read",
1860
2137
  readonly: "",
1861
- onClick: openPicker
1862
- }, null, 8, _hoisted_4$8),
2138
+ "aria-invalid": unref(invalid),
2139
+ "aria-describedby": unref(describedBy),
2140
+ onClick: openPicker,
2141
+ onKeydown: _cache[0] || (_cache[0] = (...args) => unref(openFromKey) && unref(openFromKey)(...args))
2142
+ }, null, 40, _hoisted_3$9),
1863
2143
  createElementVNode("label", {
1864
2144
  class: "aform_field-label",
1865
2145
  for: __props.uuid
1866
- }, toDisplayString(__props.label), 9, _hoisted_5$8),
2146
+ }, toDisplayString(__props.label), 9, _hoisted_4$8),
1867
2147
  withDirectives(createElementVNode("p", {
2148
+ id: unref(errorId),
1868
2149
  class: "aform_error",
1869
- innerHTML: errorText.value
1870
- }, null, 8, _hoisted_6$8), [[vShow, errorText.value]]),
1871
- showPicker.value ? (openBlock(), createBlock(ADateSelection_default, {
2150
+ role: "alert"
2151
+ }, toDisplayString(errorText.value), 9, _hoisted_5$8), [[vShow, errorText.value]]),
2152
+ unref(showPicker) ? (openBlock(), createBlock(ADateSelection_default, {
1872
2153
  key: 0,
1873
- ref_key: "pickerRef",
1874
- ref: pickerRef,
2154
+ id: unref(calendarId),
2155
+ ref: "picker",
1875
2156
  class: "adatetime-picker",
2157
+ role: "dialog",
2158
+ "aria-label": __props.label,
1876
2159
  "select-range": false,
1877
2160
  "show-date": true,
1878
2161
  "show-time": true,
2162
+ "default-date": currentDateTime.value?.toPlainDate().toString(),
1879
2163
  "default-hours": pickerDefaults.value.hours,
1880
2164
  "default-minutes": pickerDefaults.value.minutes,
1881
2165
  "default-seconds": pickerDefaults.value.seconds,
@@ -1885,6 +2169,9 @@ var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1885
2169
  onGetDate: handleDate,
1886
2170
  onGetTime: handleTime
1887
2171
  }, null, 8, [
2172
+ "id",
2173
+ "aria-label",
2174
+ "default-date",
1888
2175
  "default-hours",
1889
2176
  "default-minutes",
1890
2177
  "default-seconds",
@@ -1892,10 +2179,10 @@ var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1892
2179
  "allow-military-time",
1893
2180
  "use-seconds"
1894
2181
  ])) : createCommentVNode("", true)
1895
- ], 64))]);
2182
+ ], 64))], 32);
1896
2183
  };
1897
2184
  }
1898
- }), [["__scopeId", "data-v-b0fa488b"]]);
2185
+ }), [["__scopeId", "data-v-05cbbabe"]]);
1899
2186
  //#endregion
1900
2187
  //#region src/components/form/ADuration.vue?vue&type=script&setup=true&lang.ts
1901
2188
  var _hoisted_1$10 = { class: "aduration" };
@@ -1906,7 +2193,7 @@ var _hoisted_4$7 = {
1906
2193
  class: "aduration__summary"
1907
2194
  };
1908
2195
  var _hoisted_5$7 = { class: "aduration__value" };
1909
- var _hoisted_6$7 = { class: "aduration__ms" };
2196
+ var _hoisted_6$6 = { class: "aduration__held" };
1910
2197
  //#endregion
1911
2198
  //#region src/components/form/ADuration.vue
1912
2199
  var ADuration_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1931,47 +2218,40 @@ var ADuration_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1931
2218
  const modelValue = useModel(__props, "modelValue");
1932
2219
  const startDatetime = ref(null);
1933
2220
  const endDatetime = ref(null);
1934
- const duration = computed(() => {
1935
- if (!startDatetime.value || !endDatetime.value) return 0;
1936
- const ms = endDatetime.value.getTime() - startDatetime.value.getTime();
1937
- return ms > 0 ? ms : 0;
1938
- });
1939
- watch(duration, (newMs) => {
1940
- modelValue.value = newMs;
1941
- });
2221
+ const durationBetween = (start, end) => Temporal.Duration.from({ milliseconds: Math.max(end.getTime() - start.getTime(), 0) }).round({ largestUnit: "days" }).toString();
1942
2222
  const handleRange = (data) => {
1943
2223
  if (data.source === "init") return;
1944
2224
  startDatetime.value = data.start;
1945
2225
  endDatetime.value = data.end;
1946
- modelValue.value = duration.value;
1947
- };
1948
- const humanDuration = computed(() => {
1949
- const ms = duration.value;
1950
- if (ms === 0) return "0s";
1951
- const s = Math.floor(ms / 1e3) % 60;
1952
- const m = Math.floor(ms / 6e4) % 60;
1953
- const h = Math.floor(ms / 36e5) % 24;
1954
- const d = Math.floor(ms / 864e5);
1955
- return [
1956
- d && `${d}d`,
1957
- h && `${h}h`,
1958
- m && `${m}m`,
1959
- s && `${s}s`
1960
- ].filter(Boolean).join(" ") || "0s";
1961
- });
2226
+ modelValue.value = durationBetween(data.start, data.end);
2227
+ };
2228
+ const readDuration = (value) => {
2229
+ try {
2230
+ return Temporal.Duration.from(value);
2231
+ } catch {
2232
+ return;
2233
+ }
2234
+ };
2235
+ const UNIT_SUFFIXES = [
2236
+ ["years", "y"],
2237
+ ["months", "mo"],
2238
+ ["weeks", "w"],
2239
+ ["days", "d"],
2240
+ ["hours", "h"],
2241
+ ["minutes", "m"]
2242
+ ];
2243
+ const describeDuration = (duration) => {
2244
+ const parts = UNIT_SUFFIXES.filter(([unit]) => duration[unit]).map(([unit, suffix]) => `${duration[unit]}${suffix}`);
2245
+ const seconds = duration.seconds + duration.milliseconds / 1e3 + duration.microseconds / 1e6 + duration.nanoseconds / 1e9;
2246
+ if (seconds) parts.push(`${seconds}s`);
2247
+ return parts.join(" ") || "0s";
2248
+ };
2249
+ const heldDuration = computed(() => modelValue.value ? readDuration(modelValue.value) : void 0);
2250
+ const humanDuration = computed(() => heldDuration.value ? describeDuration(heldDuration.value) : "0s");
1962
2251
  const displayValue = computed(() => {
1963
- const ms = modelValue.value;
1964
- if (!ms) return "—";
1965
- const s = Math.floor(ms / 1e3) % 60;
1966
- const m = Math.floor(ms / 6e4) % 60;
1967
- const h = Math.floor(ms / 36e5) % 24;
1968
- const d = Math.floor(ms / 864e5);
1969
- return [
1970
- d && `${d}d`,
1971
- h && `${h}h`,
1972
- m && `${m}m`,
1973
- s && `${s}s`
1974
- ].filter(Boolean).join(" ") || "0s";
2252
+ if (!modelValue.value) return "—";
2253
+ if (!heldDuration.value) return "Invalid Duration";
2254
+ return heldDuration.value.blank ? "—" : describeDuration(heldDuration.value);
1975
2255
  });
1976
2256
  return (_ctx, _cache) => {
1977
2257
  return openBlock(), createElementBlock("div", _hoisted_1$10, [__props.mode === "display" || __props.mode === "read" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$9, toDisplayString(displayValue.value), 1), createElementVNode("label", null, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createVNode(ADateSelection_default, {
@@ -1985,23 +2265,26 @@ var ADuration_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1985
2265
  }, null, 8, ["allow-military-time", "use-seconds"]), createElementVNode("div", _hoisted_3$8, [createElementVNode("label", null, toDisplayString(__props.label), 1), startDatetime.value && endDatetime.value ? (openBlock(), createElementBlock("div", _hoisted_4$7, [
1986
2266
  _cache[0] || (_cache[0] = createElementVNode("span", { class: "aduration__label" }, "Duration:", -1)),
1987
2267
  createElementVNode("span", _hoisted_5$7, toDisplayString(humanDuration.value), 1),
1988
- createElementVNode("span", _hoisted_6$7, "(" + toDisplayString(modelValue.value ?? 0) + " ms)", 1)
2268
+ createElementVNode("span", _hoisted_6$6, "(" + toDisplayString(modelValue.value) + ")", 1)
1989
2269
  ])) : createCommentVNode("", true)])], 64))]);
1990
2270
  };
1991
2271
  }
1992
- }), [["__scopeId", "data-v-43788a3d"]]);
2272
+ }), [["__scopeId", "data-v-133162a6"]]);
1993
2273
  //#endregion
1994
2274
  //#region src/components/form/ADateRange.vue?vue&type=script&setup=true&lang.ts
1995
- var _hoisted_1$9 = { class: "aform_form-element" };
1996
- var _hoisted_2$8 = { class: "aform_display-value" };
1997
- var _hoisted_3$7 = { class: "aform_field-label" };
1998
- var _hoisted_4$6 = [
2275
+ var _hoisted_1$9 = { class: "aform_display-value" };
2276
+ var _hoisted_2$8 = { class: "aform_field-label" };
2277
+ var _hoisted_3$7 = [
1999
2278
  "id",
2279
+ "aria-expanded",
2280
+ "aria-controls",
2000
2281
  "value",
2001
- "disabled"
2282
+ "disabled",
2283
+ "aria-invalid",
2284
+ "aria-describedby"
2002
2285
  ];
2003
- var _hoisted_5$6 = ["for"];
2004
- var _hoisted_6$6 = ["innerHTML"];
2286
+ var _hoisted_4$6 = ["for"];
2287
+ var _hoisted_5$6 = ["id"];
2005
2288
  //#endregion
2006
2289
  //#region src/components/form/ADateRange.vue
2007
2290
  var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -2025,21 +2308,18 @@ var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
2025
2308
  }),
2026
2309
  emits: ["update:modelValue"],
2027
2310
  setup(__props) {
2028
- const fmt = (d) => new Date(d).toLocaleDateString();
2311
+ const fmt = (d) => fromISODate(d)?.toLocaleString() ?? "Invalid Date";
2029
2312
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
2313
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
2030
2314
  const modelValue = useModel(__props, "modelValue");
2031
- const startDate = ref(modelValue.value.start_date ? new Date(modelValue.value.start_date) : null);
2032
- const endDate = ref(modelValue.value.end_date ? new Date(modelValue.value.end_date) : null);
2033
- const showPicker = ref(false);
2034
- const pickerRef = ref(null);
2035
- onClickOutside(pickerRef, () => showPicker.value = false);
2315
+ const readDay = (day) => day ? fromISODate(day) ?? null : null;
2316
+ const startDate = ref(readDay(modelValue.value?.start_date));
2317
+ const endDate = ref(readDay(modelValue.value?.end_date));
2318
+ const { showPicker, calendarId, closePicker, openFromKey, closeOnEscape, closeWhenFocusLeaves } = useFieldCalendar(useTemplateRef("box"), useTemplateRef("picker"), __props.uuid);
2036
2319
  const openPicker = () => {
2037
2320
  if (__props.mode !== "read") showPicker.value = true;
2038
2321
  };
2039
- const formatDate = (d) => {
2040
- if (!d) return "";
2041
- return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
2042
- };
2322
+ const formatDate = (d) => d?.toLocaleString() ?? "";
2043
2323
  const rangeDisplay = computed(() => {
2044
2324
  const s = formatDate(startDate.value);
2045
2325
  const e = formatDate(endDate.value);
@@ -2048,8 +2328,8 @@ var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
2048
2328
  return "";
2049
2329
  });
2050
2330
  const displayValue = computed(() => {
2051
- const s = modelValue.value.start_date;
2052
- const e = modelValue.value.end_date;
2331
+ const s = modelValue.value?.start_date;
2332
+ const e = modelValue.value?.end_date;
2053
2333
  if (!s && !e) return "";
2054
2334
  if (s && e) return `${fmt(s)} — ${fmt(e)}`;
2055
2335
  if (s) return `From ${fmt(s)}`;
@@ -2058,61 +2338,75 @@ var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
2058
2338
  const ensureOrder = () => {
2059
2339
  const s = startDate.value;
2060
2340
  const e = endDate.value;
2061
- if (s && e && e.getTime() < s.getTime()) [startDate.value, endDate.value] = [e, s];
2341
+ if (s && e && Temporal.PlainDate.compare(e, s) < 0) [startDate.value, endDate.value] = [e, s];
2062
2342
  };
2063
- const toISODate = (d) => d ? d.toISOString().split("T")[0] : null;
2064
2343
  const emitModel = () => {
2065
2344
  modelValue.value = {
2066
- start_date: toISODate(startDate.value),
2067
- end_date: toISODate(endDate.value)
2345
+ start_date: startDate.value?.toString() ?? null,
2346
+ end_date: endDate.value?.toString() ?? null
2068
2347
  };
2069
2348
  };
2070
2349
  const handlePickerDate = (data) => {
2071
- if (data.start) startDate.value = data.start;
2350
+ if (data.start) startDate.value = readDay(data.start);
2072
2351
  if (data.end) {
2073
- endDate.value = data.end;
2352
+ endDate.value = readDay(data.end);
2074
2353
  ensureOrder();
2075
- showPicker.value = false;
2354
+ closePicker();
2076
2355
  }
2077
2356
  emitModel();
2078
2357
  };
2079
2358
  watch(() => modelValue.value, (newVal) => {
2080
- startDate.value = newVal.start_date ? new Date(newVal.start_date) : null;
2081
- endDate.value = newVal.end_date ? new Date(newVal.end_date) : null;
2359
+ startDate.value = readDay(newVal?.start_date);
2360
+ endDate.value = readDay(newVal?.end_date);
2082
2361
  }, { deep: true });
2083
2362
  return (_ctx, _cache) => {
2084
- return openBlock(), createElementBlock("div", _hoisted_1$9, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$8, toDisplayString(displayValue.value), 1), createElementVNode("label", _hoisted_3$7, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2363
+ return openBlock(), createElementBlock("div", {
2364
+ class: "aform_form-element",
2365
+ onFocusout: _cache[1] || (_cache[1] = (...args) => unref(closeWhenFocusLeaves) && unref(closeWhenFocusLeaves)(...args)),
2366
+ onKeydown: _cache[2] || (_cache[2] = (...args) => unref(closeOnEscape) && unref(closeOnEscape)(...args))
2367
+ }, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_1$9, toDisplayString(displayValue.value), 1), createElementVNode("label", _hoisted_2$8, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2085
2368
  createElementVNode("input", {
2086
2369
  id: __props.uuid,
2370
+ ref: "box",
2087
2371
  class: "aform_input-field",
2088
2372
  type: "text",
2373
+ role: "combobox",
2374
+ "aria-haspopup": "dialog",
2375
+ "aria-expanded": unref(showPicker),
2376
+ "aria-controls": unref(showPicker) ? unref(calendarId) : void 0,
2089
2377
  value: rangeDisplay.value,
2090
2378
  placeholder: "Select date range",
2091
2379
  disabled: __props.mode === "read",
2092
2380
  readonly: "",
2093
- onClick: openPicker
2094
- }, null, 8, _hoisted_4$6),
2381
+ "aria-invalid": unref(invalid),
2382
+ "aria-describedby": unref(describedBy),
2383
+ onClick: openPicker,
2384
+ onKeydown: _cache[0] || (_cache[0] = (...args) => unref(openFromKey) && unref(openFromKey)(...args))
2385
+ }, null, 40, _hoisted_3$7),
2095
2386
  createElementVNode("label", {
2096
2387
  class: "aform_field-label",
2097
2388
  for: __props.uuid
2098
- }, toDisplayString(__props.label), 9, _hoisted_5$6),
2389
+ }, toDisplayString(__props.label), 9, _hoisted_4$6),
2099
2390
  withDirectives(createElementVNode("p", {
2391
+ id: unref(errorId),
2100
2392
  class: "aform_error",
2101
- innerHTML: errorText.value
2102
- }, null, 8, _hoisted_6$6), [[vShow, errorText.value]]),
2103
- showPicker.value ? (openBlock(), createBlock(ADateSelection_default, {
2393
+ role: "alert"
2394
+ }, toDisplayString(errorText.value), 9, _hoisted_5$6), [[vShow, errorText.value]]),
2395
+ unref(showPicker) ? (openBlock(), createBlock(ADateSelection_default, {
2104
2396
  key: 0,
2105
- ref_key: "pickerRef",
2106
- ref: pickerRef,
2397
+ id: unref(calendarId),
2398
+ ref: "picker",
2107
2399
  class: "adaterange-picker",
2400
+ role: "dialog",
2401
+ "aria-label": __props.label,
2108
2402
  "select-range": true,
2109
2403
  "show-time": false,
2110
2404
  onGetDate: handlePickerDate
2111
- }, null, 512)) : createCommentVNode("", true)
2112
- ], 64))]);
2405
+ }, null, 8, ["id", "aria-label"])) : createCommentVNode("", true)
2406
+ ], 64))], 32);
2113
2407
  };
2114
2408
  }
2115
- }), [["__scopeId", "data-v-f8dad4d3"]]);
2409
+ }), [["__scopeId", "data-v-ac2c160b"]]);
2116
2410
  //#endregion
2117
2411
  //#region src/components/AForm.vue?vue&type=script&setup=true&lang.ts
2118
2412
  var _hoisted_1$8 = { class: "aform" };
@@ -2155,6 +2449,23 @@ var AForm_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
2155
2449
  emit("update:data", { ...dataModel.value });
2156
2450
  }
2157
2451
  };
2452
+ const isListExpansionTable = (componentObj) => componentObj.kind === "table" && componentObj.config?.view === "list-expansion";
2453
+ const tableExpansionSchema = (table) => table.schema.filter((col) => Boolean(col.component)).map(({ fieldname, component, ...rest }) => Object.assign(rest, {
2454
+ kind: "field",
2455
+ fieldname,
2456
+ component
2457
+ }));
2458
+ const updateTableRow = (fieldname, rowIndex, val) => {
2459
+ const rows = Array.isArray(dataModel.value?.[fieldname]) ? [...dataModel.value[fieldname]] : [];
2460
+ rows[rowIndex] = {
2461
+ ...rows[rowIndex],
2462
+ ...val
2463
+ };
2464
+ if (dataModel.value) {
2465
+ dataModel.value[fieldname] = rows;
2466
+ emit("update:data", { ...dataModel.value });
2467
+ }
2468
+ };
2158
2469
  const componentProps = (componentObj) => {
2159
2470
  const propsToPass = {};
2160
2471
  for (const [key, value] of Object.entries(componentObj)) if (![
@@ -2212,6 +2523,7 @@ var AForm_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
2212
2523
  });
2213
2524
  const childModels = computed(() => childModelsCache.value);
2214
2525
  return (_ctx, _cache) => {
2526
+ const _component_AForm = resolveComponent("AForm", true);
2215
2527
  return openBlock(), createElementBlock("form", _hoisted_1$8, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.schema, (componentObj, key) => {
2216
2528
  return openBlock(), createElementBlock(Fragment, { key }, [isNestedSection(componentObj) ? (openBlock(), createElementBlock("div", _hoisted_2$7, [componentObj.label && componentObj.kind !== "fieldset" ? (openBlock(), createElementBlock("h4", _hoisted_3$6, toDisplayString(componentObj.label), 1)) : createCommentVNode("", true), (openBlock(), createBlock(resolveDynamicComponent(componentObj.component ?? "AForm"), {
2217
2529
  data: nestedData.value[componentObj.fieldname],
@@ -2238,7 +2550,22 @@ var AForm_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
2238
2550
  data: dataModel.value[componentObj.fieldname],
2239
2551
  mode: resolvedMode(componentObj),
2240
2552
  errors: __props.errors?.[componentObj.fieldname]
2241
- }, { ref_for: true }, componentProps(componentObj)), null, 16, [
2553
+ }, { ref_for: true }, componentProps(componentObj)), createSlots({ _: 2 }, [isListExpansionTable(componentObj) ? {
2554
+ name: "content",
2555
+ fn: withCtx(({ row, rowIndex }) => [createVNode(_component_AForm, {
2556
+ class: "aform-table-expansion",
2557
+ data: row,
2558
+ schema: tableExpansionSchema(componentObj),
2559
+ mode: resolvedMode(componentObj),
2560
+ "onUpdate:data": (val) => updateTableRow(componentObj.fieldname, rowIndex, val)
2561
+ }, null, 8, [
2562
+ "data",
2563
+ "schema",
2564
+ "mode",
2565
+ "onUpdate:data"
2566
+ ])]),
2567
+ key: "0"
2568
+ } : void 0]), 1040, [
2242
2569
  "modelValue",
2243
2570
  "onUpdate:modelValue",
2244
2571
  "style",
@@ -2250,7 +2577,7 @@ var AForm_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
2250
2577
  }), 128))]);
2251
2578
  };
2252
2579
  }
2253
- }), [["__scopeId", "data-v-713aca93"]]);
2580
+ }), [["__scopeId", "data-v-dbfe4358"]]);
2254
2581
  //#endregion
2255
2582
  //#region src/components/base/CollapseButton.vue
2256
2583
  var CollapseButton_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -2364,7 +2691,7 @@ var AFileAttach_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
2364
2691
  ], 64))]);
2365
2692
  };
2366
2693
  }
2367
- }), [["__scopeId", "data-v-8eefd179"]]);
2694
+ }), [["__scopeId", "data-v-79ce33a7"]]);
2368
2695
  //#endregion
2369
2696
  //#region src/components/form/ANumericInput.vue?vue&type=script&setup=true&lang.ts
2370
2697
  var _hoisted_1$6 = { class: "aform_form-element" };
@@ -2373,10 +2700,12 @@ var _hoisted_3$4 = { class: "aform_field-label" };
2373
2700
  var _hoisted_4$4 = [
2374
2701
  "id",
2375
2702
  "disabled",
2376
- "required"
2703
+ "required",
2704
+ "aria-invalid",
2705
+ "aria-describedby"
2377
2706
  ];
2378
2707
  var _hoisted_5$4 = ["for"];
2379
- var _hoisted_6$4 = ["innerHTML"];
2708
+ var _hoisted_6$4 = ["id"];
2380
2709
  //#endregion
2381
2710
  //#region src/components/form/ANumericInput.vue
2382
2711
  var ANumericInput_default = /* @__PURE__ */ defineComponent({
@@ -2398,25 +2727,33 @@ var ANumericInput_default = /* @__PURE__ */ defineComponent({
2398
2727
  emits: ["update:modelValue"],
2399
2728
  setup(__props) {
2400
2729
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
2730
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
2401
2731
  const inputNumber = useModel(__props, "modelValue");
2732
+ const boxNumber = computed({
2733
+ get: () => inputNumber.value,
2734
+ set: (value) => inputNumber.value = numberFromBox(value)
2735
+ });
2402
2736
  return (_ctx, _cache) => {
2403
2737
  return openBlock(), createElementBlock("div", _hoisted_1$6, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$5, toDisplayString(inputNumber.value ?? ""), 1), createElementVNode("label", _hoisted_3$4, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2404
2738
  withDirectives(createElementVNode("input", {
2405
2739
  id: __props.uuid,
2406
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputNumber.value = $event),
2740
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => boxNumber.value = $event),
2407
2741
  class: "aform_input-field",
2408
2742
  type: "number",
2409
2743
  disabled: __props.mode === "read",
2410
- required: __props.required
2411
- }, null, 8, _hoisted_4$4), [[vModelText, inputNumber.value]]),
2744
+ required: __props.required,
2745
+ "aria-invalid": unref(invalid),
2746
+ "aria-describedby": unref(describedBy)
2747
+ }, null, 8, _hoisted_4$4), [[vModelText, boxNumber.value]]),
2412
2748
  createElementVNode("label", {
2413
2749
  class: "aform_field-label",
2414
2750
  for: __props.uuid
2415
2751
  }, toDisplayString(__props.label), 9, _hoisted_5$4),
2416
2752
  withDirectives(createElementVNode("p", {
2753
+ id: unref(errorId),
2417
2754
  class: "aform_error",
2418
- innerHTML: errorText.value
2419
- }, null, 8, _hoisted_6$4), [[vShow, errorText.value]])
2755
+ role: "alert"
2756
+ }, toDisplayString(errorText.value), 9, _hoisted_6$4), [[vShow, errorText.value]])
2420
2757
  ], 64))]);
2421
2758
  };
2422
2759
  }
@@ -2432,7 +2769,9 @@ var _hoisted_6$3 = { class: "aquantity__group" };
2432
2769
  var _hoisted_7$1 = [
2433
2770
  "id",
2434
2771
  "disabled",
2435
- "required"
2772
+ "required",
2773
+ "aria-invalid",
2774
+ "aria-describedby"
2436
2775
  ];
2437
2776
  var _hoisted_8$1 = { class: "aquantity__uom" };
2438
2777
  var _hoisted_9$1 = [
@@ -2461,7 +2800,7 @@ var _hoisted_20 = { class: "aform_field-label" };
2461
2800
  var _hoisted_21 = { class: "aquantity__field aquantity__field--conversion" };
2462
2801
  var _hoisted_22 = ["value"];
2463
2802
  var _hoisted_23 = { class: "aform_field-label" };
2464
- var _hoisted_24 = ["innerHTML"];
2803
+ var _hoisted_24 = ["id"];
2465
2804
  //#endregion
2466
2805
  //#region src/components/form/AQuantityInput.vue
2467
2806
  var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -2474,7 +2813,7 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2474
2813
  required: { type: Boolean },
2475
2814
  mode: {},
2476
2815
  uuid: {},
2477
- validation: { default: () => ({ errorMessage: "&nbsp;" }) },
2816
+ validation: { default: () => ({ errorMessage: "" }) },
2478
2817
  errors: {},
2479
2818
  options: { default: () => ({}) },
2480
2819
  uomLabel: { default: "UOM" },
@@ -2483,9 +2822,9 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2483
2822
  conversionFactorLabel: { default: "Conversion Factor" }
2484
2823
  }, {
2485
2824
  "modelValue": { default: () => ({
2486
- qty: 0,
2825
+ qty: null,
2487
2826
  uom: "",
2488
- stockQty: 0,
2827
+ stockQty: null,
2489
2828
  stockUom: "",
2490
2829
  conversionFactor: 1
2491
2830
  }) },
@@ -2493,15 +2832,17 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2493
2832
  }),
2494
2833
  emits: ["update:modelValue"],
2495
2834
  setup(__props) {
2835
+ const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
2836
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
2496
2837
  const modelValue = useModel(__props, "modelValue");
2497
2838
  const uoms = computed(() => __props.options.uoms ?? []);
2498
2839
  const roundQty = (value) => Number(value.toFixed(6));
2499
2840
  const resolveConversionFactor = (uom) => {
2500
- const stockUom = __props.options.stockUom ?? modelValue.value.stockUom;
2841
+ const stockUom = __props.options.stockUom ?? modelValue.value?.stockUom;
2501
2842
  if (!uom || uom === stockUom) return 1;
2502
2843
  const mapped = __props.options.conversionFactors?.[uom];
2503
2844
  if (mapped !== void 0) return mapped;
2504
- if (uom === modelValue.value.uom) return modelValue.value.conversionFactor ?? 1;
2845
+ if (uom === modelValue.value?.uom) return modelValue.value.conversionFactor ?? 1;
2505
2846
  return 1;
2506
2847
  };
2507
2848
  const recompute = (qty, uom) => {
@@ -2510,17 +2851,17 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2510
2851
  qty,
2511
2852
  uom,
2512
2853
  conversionFactor,
2513
- stockUom: __props.options.stockUom ?? modelValue.value.stockUom,
2514
- stockQty: roundQty(qty * conversionFactor)
2854
+ stockUom: __props.options.stockUom ?? modelValue.value?.stockUom ?? "",
2855
+ stockQty: qty === null ? null : roundQty(qty * conversionFactor)
2515
2856
  };
2516
2857
  };
2517
2858
  const qty = computed({
2518
- get: () => modelValue.value?.qty ?? 0,
2519
- set: (value) => recompute(value, modelValue.value?.uom ?? "")
2859
+ get: () => modelValue.value?.qty ?? null,
2860
+ set: (value) => recompute(numberFromBox(value), modelValue.value?.uom ?? "")
2520
2861
  });
2521
2862
  const uom = computed({
2522
2863
  get: () => modelValue.value?.uom ?? "",
2523
- set: (value) => recompute(modelValue.value?.qty ?? 0, value)
2864
+ set: (value) => recompute(modelValue.value?.qty ?? null, value)
2524
2865
  });
2525
2866
  const qtyNavigationKeys = /* @__PURE__ */ new Set([
2526
2867
  "Backspace",
@@ -2589,90 +2930,97 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2589
2930
  });
2590
2931
  const displayText = computed(() => {
2591
2932
  const v = modelValue.value;
2592
- if (!v || !v.uom) return "—";
2933
+ if (!v || !v.uom || v.qty === null) return "—";
2593
2934
  const base = `${v.qty} ${v.uom}`;
2594
2935
  return showStock.value ? `${base} (${v.stockQty} ${v.stockUom})` : base;
2595
2936
  });
2596
2937
  return (_ctx, _cache) => {
2597
2938
  return openBlock(), createElementBlock("div", _hoisted_1$5, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$4, toDisplayString(displayText.value), 1), createElementVNode("label", _hoisted_3$3, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
2598
- createElementVNode("div", _hoisted_4$3, [createElementVNode("div", _hoisted_5$3, [createElementVNode("div", _hoisted_6$3, [withDirectives(createElementVNode("input", {
2599
- id: __props.uuid,
2600
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => qty.value = $event),
2601
- class: "aquantity__qty",
2602
- type: "number",
2603
- disabled: __props.mode === "read",
2604
- required: __props.required,
2605
- onKeydown: onQtyKeydown,
2606
- onPaste: onQtyPaste
2607
- }, null, 40, _hoisted_7$1), [[
2608
- vModelText,
2609
- qty.value,
2610
- void 0,
2611
- { number: true }
2612
- ]]), withDirectives((openBlock(), createElementBlock("div", _hoisted_8$1, [createElementVNode("button", {
2613
- id: `${__props.uuid}-uom`,
2614
- type: "button",
2615
- class: "aquantity__uom-toggle",
2616
- disabled: __props.mode === "read",
2617
- "aria-haspopup": "listbox",
2618
- "aria-expanded": dropdown.open,
2619
- "aria-activedescendant": dropdown.open && dropdown.activeIndex >= 0 ? `${__props.uuid}-uom-opt-${dropdown.activeIndex}` : void 0,
2620
- onClick: toggleDropdown,
2621
- onKeydown: [
2622
- _cache[1] || (_cache[1] = withKeys(withModifiers(($event) => moveActive(1), ["prevent"]), ["down"])),
2623
- _cache[2] || (_cache[2] = withKeys(withModifiers(($event) => moveActive(-1), ["prevent"]), ["up"])),
2624
- withKeys(withModifiers(selectActive, ["prevent"]), ["enter"]),
2625
- withKeys(closeDropdown, ["esc"])
2626
- ]
2627
- }, [createElementVNode("span", _hoisted_10$1, toDisplayString(uom.value || __props.uomLabel), 1), _cache[3] || (_cache[3] = createElementVNode("span", {
2628
- class: "aquantity__caret",
2629
- "aria-hidden": "true"
2630
- }, null, -1))], 40, _hoisted_9$1), withDirectives(createElementVNode("ul", {
2631
- class: "aquantity__uom-menu",
2632
- role: "listbox",
2633
- "aria-label": __props.uomLabel
2634
- }, [(openBlock(true), createElementBlock(Fragment, null, renderList(uoms.value, (option, i) => {
2635
- return openBlock(), createElementBlock("li", {
2636
- id: `${__props.uuid}-uom-opt-${i}`,
2637
- key: option,
2638
- role: "option",
2639
- "aria-selected": option === uom.value,
2640
- class: normalizeClass(["aquantity__uom-option", { "is-active": i === dropdown.activeIndex }]),
2641
- onMouseenter: ($event) => dropdown.activeIndex = i,
2642
- onClick: ($event) => selectUom(option)
2643
- }, toDisplayString(option), 43, _hoisted_12$1);
2644
- }), 128))], 8, _hoisted_11$1), [[vShow, dropdown.open]])])), [[unref(vOnClickOutside), closeDropdown]])]), createElementVNode("label", {
2645
- class: "aform_field-label",
2646
- for: __props.uuid
2647
- }, toDisplayString(__props.label), 9, _hoisted_13)])]),
2939
+ createElementVNode("div", _hoisted_4$3, [createElementVNode("div", _hoisted_5$3, [createElementVNode("div", _hoisted_6$3, [
2940
+ withDirectives(createElementVNode("input", {
2941
+ id: __props.uuid,
2942
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => qty.value = $event),
2943
+ class: "aquantity__qty",
2944
+ type: "number",
2945
+ disabled: __props.mode === "read",
2946
+ required: __props.required,
2947
+ "aria-invalid": unref(invalid),
2948
+ "aria-describedby": unref(describedBy),
2949
+ onKeydown: onQtyKeydown,
2950
+ onPaste: onQtyPaste
2951
+ }, null, 40, _hoisted_7$1), [[
2952
+ vModelText,
2953
+ qty.value,
2954
+ void 0,
2955
+ { number: true }
2956
+ ]]),
2957
+ withDirectives((openBlock(), createElementBlock("div", _hoisted_8$1, [createElementVNode("button", {
2958
+ id: `${__props.uuid}-uom`,
2959
+ type: "button",
2960
+ class: "aquantity__uom-toggle",
2961
+ disabled: __props.mode === "read",
2962
+ "aria-haspopup": "listbox",
2963
+ "aria-expanded": dropdown.open,
2964
+ "aria-activedescendant": dropdown.open && dropdown.activeIndex >= 0 ? `${__props.uuid}-uom-opt-${dropdown.activeIndex}` : void 0,
2965
+ onClick: toggleDropdown,
2966
+ onKeydown: [
2967
+ _cache[1] || (_cache[1] = withKeys(withModifiers(($event) => moveActive(1), ["prevent"]), ["down"])),
2968
+ _cache[2] || (_cache[2] = withKeys(withModifiers(($event) => moveActive(-1), ["prevent"]), ["up"])),
2969
+ withKeys(withModifiers(selectActive, ["prevent"]), ["enter"]),
2970
+ withKeys(closeDropdown, ["esc"])
2971
+ ]
2972
+ }, [createElementVNode("span", _hoisted_10$1, toDisplayString(uom.value || __props.uomLabel), 1), _cache[3] || (_cache[3] = createElementVNode("span", {
2973
+ class: "aquantity__caret",
2974
+ "aria-hidden": "true"
2975
+ }, null, -1))], 40, _hoisted_9$1), withDirectives(createElementVNode("ul", {
2976
+ class: "aquantity__uom-menu",
2977
+ role: "listbox",
2978
+ "aria-label": __props.uomLabel
2979
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(uoms.value, (option, i) => {
2980
+ return openBlock(), createElementBlock("li", {
2981
+ id: `${__props.uuid}-uom-opt-${i}`,
2982
+ key: option,
2983
+ role: "option",
2984
+ "aria-selected": option === uom.value,
2985
+ class: normalizeClass(["aquantity__uom-option", { "is-active": i === dropdown.activeIndex }]),
2986
+ onMouseenter: ($event) => dropdown.activeIndex = i,
2987
+ onClick: ($event) => selectUom(option)
2988
+ }, toDisplayString(option), 43, _hoisted_12$1);
2989
+ }), 128))], 8, _hoisted_11$1), [[vShow, dropdown.open]])])), [[unref(vOnClickOutside), closeDropdown]]),
2990
+ createElementVNode("label", {
2991
+ class: "aform_field-label",
2992
+ for: __props.uuid
2993
+ }, toDisplayString(__props.label), 9, _hoisted_13)
2994
+ ])])]),
2648
2995
  createElementVNode("div", _hoisted_14, [
2649
2996
  createElementVNode("div", _hoisted_15, [createElementVNode("input", {
2650
- value: modelValue.value.stockUom,
2997
+ value: modelValue.value?.stockUom,
2651
2998
  class: "aform_input-field aquantity__stock-field",
2652
2999
  type: "text",
2653
3000
  disabled: ""
2654
3001
  }, null, 8, _hoisted_16), createElementVNode("label", _hoisted_17, toDisplayString(__props.stockUomLabel), 1)]),
2655
3002
  createElementVNode("div", _hoisted_18, [createElementVNode("input", {
2656
- value: modelValue.value.stockQty,
3003
+ value: modelValue.value?.stockQty,
2657
3004
  class: "aform_input-field aquantity__stock-field",
2658
3005
  type: "number",
2659
3006
  disabled: ""
2660
3007
  }, null, 8, _hoisted_19), createElementVNode("label", _hoisted_20, toDisplayString(__props.stockQtyLabel), 1)]),
2661
3008
  createElementVNode("div", _hoisted_21, [createElementVNode("input", {
2662
- value: modelValue.value.conversionFactor,
3009
+ value: modelValue.value?.conversionFactor,
2663
3010
  class: "aform_input-field aquantity__stock-field",
2664
3011
  type: "number",
2665
3012
  disabled: ""
2666
3013
  }, null, 8, _hoisted_22), createElementVNode("label", _hoisted_23, toDisplayString(__props.conversionFactorLabel), 1)])
2667
3014
  ]),
2668
3015
  withDirectives(createElementVNode("p", {
3016
+ id: unref(errorId),
2669
3017
  class: "aform_error",
2670
- innerHTML: __props.validation.errorMessage
2671
- }, null, 8, _hoisted_24), [[vShow, __props.validation.errorMessage]])
3018
+ role: "alert"
3019
+ }, toDisplayString(errorText.value), 9, _hoisted_24), [[vShow, errorText.value]])
2672
3020
  ], 64))]);
2673
3021
  };
2674
3022
  }
2675
- }), [["__scopeId", "data-v-f16b20ca"]]);
3023
+ }), [["__scopeId", "data-v-b2c39a5c"]]);
2676
3024
  //#endregion
2677
3025
  //#region src/directives/mask.ts
2678
3026
  /**
@@ -2771,10 +3119,12 @@ var _hoisted_4$2 = [
2771
3119
  "id",
2772
3120
  "disabled",
2773
3121
  "maxlength",
2774
- "required"
3122
+ "required",
3123
+ "aria-invalid",
3124
+ "aria-describedby"
2775
3125
  ];
2776
3126
  var _hoisted_5$2 = ["for"];
2777
- var _hoisted_6$2 = ["innerHTML"];
3127
+ var _hoisted_6$2 = ["id"];
2778
3128
  //#endregion
2779
3129
  //#region src/components/form/ATextInput.vue
2780
3130
  var ATextInput_default = /* @__PURE__ */ defineComponent({
@@ -2796,6 +3146,7 @@ var ATextInput_default = /* @__PURE__ */ defineComponent({
2796
3146
  emits: ["update:modelValue"],
2797
3147
  setup(__props) {
2798
3148
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
3149
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
2799
3150
  const maskFilled = ref(true);
2800
3151
  const inputText = useModel(__props, "modelValue");
2801
3152
  return (_ctx, _cache) => {
@@ -2806,16 +3157,19 @@ var ATextInput_default = /* @__PURE__ */ defineComponent({
2806
3157
  class: "aform_input-field",
2807
3158
  disabled: __props.mode === "read",
2808
3159
  maxlength: __props.mask ? maskFilled.value ? __props.mask.length : void 0 : void 0,
2809
- required: __props.required
3160
+ required: __props.required,
3161
+ "aria-invalid": unref(invalid),
3162
+ "aria-describedby": unref(describedBy)
2810
3163
  }, null, 8, _hoisted_4$2), [[vModelText, inputText.value], [unref(useStringMask), __props.mask]]),
2811
3164
  createElementVNode("label", {
2812
3165
  class: "aform_field-label",
2813
3166
  for: __props.uuid
2814
3167
  }, toDisplayString(__props.label), 9, _hoisted_5$2),
2815
3168
  withDirectives(createElementVNode("p", {
3169
+ id: unref(errorId),
2816
3170
  class: "aform_error",
2817
- innerHTML: errorText.value
2818
- }, null, 8, _hoisted_6$2), [[vShow, errorText.value]])
3171
+ role: "alert"
3172
+ }, toDisplayString(errorText.value), 9, _hoisted_6$2), [[vShow, errorText.value]])
2819
3173
  ], 64))]);
2820
3174
  };
2821
3175
  }
@@ -2831,10 +3185,12 @@ var _hoisted_4$1 = [
2831
3185
  "rows",
2832
3186
  "maxlength",
2833
3187
  "disabled",
2834
- "required"
3188
+ "required",
3189
+ "aria-invalid",
3190
+ "aria-describedby"
2835
3191
  ];
2836
3192
  var _hoisted_5$1 = ["for"];
2837
- var _hoisted_6$1 = ["innerHTML"];
3193
+ var _hoisted_6$1 = ["id"];
2838
3194
  //#endregion
2839
3195
  //#region src/components/form/ATextboxInput.vue
2840
3196
  var ATextboxInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -2859,6 +3215,7 @@ var ATextboxInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @
2859
3215
  emits: ["update:modelValue"],
2860
3216
  setup(__props) {
2861
3217
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
3218
+ const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
2862
3219
  const inputText = useModel(__props, "modelValue");
2863
3220
  return (_ctx, _cache) => {
2864
3221
  return openBlock(), createElementBlock("div", _hoisted_1$3, [__props.mode === "display" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("span", _hoisted_2$2, toDisplayString(inputText.value ?? ""), 1), createElementVNode("label", _hoisted_3$1, toDisplayString(__props.label), 1)], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
@@ -2870,20 +3227,23 @@ var ATextboxInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @
2870
3227
  rows: __props.rows,
2871
3228
  maxlength: __props.maxlength,
2872
3229
  disabled: __props.mode === "read",
2873
- required: __props.required
3230
+ required: __props.required,
3231
+ "aria-invalid": unref(invalid),
3232
+ "aria-describedby": unref(describedBy)
2874
3233
  }, null, 8, _hoisted_4$1), [[vModelText, inputText.value]]),
2875
3234
  createElementVNode("label", {
2876
3235
  class: "aform_field-label",
2877
3236
  for: __props.uuid
2878
3237
  }, toDisplayString(__props.label), 9, _hoisted_5$1),
2879
3238
  withDirectives(createElementVNode("p", {
3239
+ id: unref(errorId),
2880
3240
  class: "aform_error",
2881
- innerHTML: errorText.value
2882
- }, null, 8, _hoisted_6$1), [[vShow, errorText.value]])
3241
+ role: "alert"
3242
+ }, toDisplayString(errorText.value), 9, _hoisted_6$1), [[vShow, errorText.value]])
2883
3243
  ], 64))]);
2884
3244
  };
2885
3245
  }
2886
- }), [["__scopeId", "data-v-2a9c793b"]]);
3246
+ }), [["__scopeId", "data-v-19e611fa"]]);
2887
3247
  //#endregion
2888
3248
  //#region src/components/utilities/Login.vue?vue&type=script&setup=true&lang.ts
2889
3249
  var _hoisted_1$2 = { class: "login-container" };
@@ -2942,9 +3302,9 @@ var Login_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
2942
3302
  name: "email",
2943
3303
  placeholder: "name@example.com",
2944
3304
  type: "email",
2945
- "auto-capitalize": "none",
2946
- "auto-complete": "email",
2947
- "auto-correct": "off",
3305
+ autocomplete: "email",
3306
+ autocapitalize: "none",
3307
+ autocorrect: "off",
2948
3308
  disabled: isLoading.value
2949
3309
  }, null, 8, _hoisted_8), [[vModelText, email.value]])]),
2950
3310
  createElementVNode("div", _hoisted_9, [_cache[3] || (_cache[3] = createElementVNode("label", {
@@ -2957,19 +3317,23 @@ var Login_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
2957
3317
  class: "aform_input-field",
2958
3318
  name: "password",
2959
3319
  type: "password",
3320
+ autocomplete: "current-password",
2960
3321
  disabled: isLoading.value
2961
3322
  }, null, 8, _hoisted_10), [[vModelText, password.value]])]),
2962
3323
  createElementVNode("button", {
3324
+ type: "submit",
2963
3325
  class: "btn",
2964
- disabled: isLoading.value || !email.value || !password.value,
2965
- onClick: onSubmit
3326
+ disabled: isLoading.value || !email.value || !password.value
2966
3327
  }, [isLoading.value ? (openBlock(), createElementBlock("span", _hoisted_12, "progress_activity")) : createCommentVNode("", true), _cache[4] || (_cache[4] = createElementVNode("span", { id: "login-form-button" }, "Login", -1))], 8, _hoisted_11)
2967
3328
  ])], 32),
2968
- _cache[5] || (_cache[5] = createElementVNode("button", { class: "btn" }, [createElementVNode("span", { id: "forgot-password-button" }, "Forgot password?")], -1))
3329
+ _cache[5] || (_cache[5] = createElementVNode("button", {
3330
+ type: "button",
3331
+ class: "btn"
3332
+ }, [createElementVNode("span", { id: "forgot-password-button" }, "Forgot password?")], -1))
2969
3333
  ])])]);
2970
3334
  };
2971
3335
  }
2972
- }), [["__scopeId", "data-v-2c0f0634"]]);
3336
+ }), [["__scopeId", "data-v-555c3224"]]);
2973
3337
  //#endregion
2974
3338
  //#region src/components/AFormLoading.vue
2975
3339
  var _sfc_main = {};