@stonecrop/aform 0.35.0 → 0.38.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 (30) hide show
  1. package/dist/aform.js +508 -315
  2. package/dist/aform.js.map +1 -1
  3. package/dist/assets/index.css +80 -70
  4. package/dist/src/components/form/ACurrencyInput.vue.d.ts +3 -3
  5. package/dist/src/components/form/ACurrencyInput.vue.d.ts.map +1 -1
  6. package/dist/src/components/form/ADate.vue.d.ts +3 -3
  7. package/dist/src/components/form/ADate.vue.d.ts.map +1 -1
  8. package/dist/src/components/form/ADatePicker.vue.d.ts +10 -10
  9. package/dist/src/components/form/ADatePicker.vue.d.ts.map +1 -1
  10. package/dist/src/components/form/ADateRange.vue.d.ts +3 -3
  11. package/dist/src/components/form/ADateRange.vue.d.ts.map +1 -1
  12. package/dist/src/components/form/ADateSelection.vue.d.ts +8 -6
  13. package/dist/src/components/form/ADateSelection.vue.d.ts.map +1 -1
  14. package/dist/src/components/form/ADateTime.vue.d.ts +3 -3
  15. package/dist/src/components/form/ADateTime.vue.d.ts.map +1 -1
  16. package/dist/src/components/form/ADuration.vue.d.ts +3 -3
  17. package/dist/src/components/form/ADuration.vue.d.ts.map +1 -1
  18. package/dist/src/components/form/ANumericInput.vue.d.ts +3 -3
  19. package/dist/src/components/form/ANumericInput.vue.d.ts.map +1 -1
  20. package/dist/src/components/form/AQuantityInput.vue.d.ts +3 -3
  21. package/dist/src/components/form/AQuantityInput.vue.d.ts.map +1 -1
  22. package/dist/src/types/index.d.ts +8 -8
  23. package/dist/src/types/index.d.ts.map +1 -1
  24. package/dist/src/utils/emptiedBox.d.ts +11 -0
  25. package/dist/src/utils/emptiedBox.d.ts.map +1 -0
  26. package/dist/src/utils/fieldCalendar.d.ts +18 -0
  27. package/dist/src/utils/fieldCalendar.d.ts.map +1 -0
  28. package/dist/src/utils/typedDay.d.ts +14 -0
  29. package/dist/src/utils/typedDay.d.ts.map +1 -0
  30. package/package.json +8 -5
package/dist/aform.js CHANGED
@@ -2,6 +2,8 @@ import { hasBadgeOptions, isBadgeDescriptor, lookupBadge, selectChoices } from "
2
2
  import { install as install$1 } from "@stonecrop/atable";
3
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
  /**
@@ -133,7 +135,7 @@ var _hoisted_2$19 = { class: "aform_field-label" };
133
135
  var _hoisted_3$17 = { class: "aform_display-value" };
134
136
  var _hoisted_4$16 = ["for"];
135
137
  var _hoisted_5$16 = { class: "aform_checkbox-container aform_input-field" };
136
- var _hoisted_6$16 = [
138
+ var _hoisted_6$13 = [
137
139
  "id",
138
140
  "disabled",
139
141
  "required",
@@ -182,7 +184,7 @@ var ACheckbox_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
182
184
  required: __props.required,
183
185
  "aria-invalid": unref(invalid),
184
186
  "aria-describedby": unref(describedBy)
185
- }, null, 8, _hoisted_6$16), [[vModelCheckbox, checkbox.value]])]),
187
+ }, null, 8, _hoisted_6$13), [[vModelCheckbox, checkbox.value]])]),
186
188
  withDirectives(createElementVNode("p", {
187
189
  id: unref(errorId),
188
190
  class: "aform_error",
@@ -203,6 +205,22 @@ var _plugin_vue_export_helper_default = (sfc, props) => {
203
205
  //#region src/components/form/ACheckbox.vue
204
206
  var ACheckbox_default = /*#__PURE__*/ _plugin_vue_export_helper_default(ACheckbox_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-f28e7db6"]]);
205
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
+ }
223
+ //#endregion
206
224
  //#region src/components/form/AFormLink.vue?vue&type=script&setup=true&lang.ts
207
225
  var _hoisted_1$19 = { class: "aform_display-value" };
208
226
  var _hoisted_2$18 = {
@@ -222,7 +240,7 @@ var _hoisted_5$15 = [
222
240
  "disabled",
223
241
  "onKeydown"
224
242
  ];
225
- var _hoisted_6$15 = ["onKeydown"];
243
+ var _hoisted_6$12 = ["onKeydown"];
226
244
  var _hoisted_7$6 = ["data-icon"];
227
245
  var _hoisted_8$6 = {
228
246
  key: 0,
@@ -238,12 +256,12 @@ var _hoisted_12$4 = {
238
256
  key: 0,
239
257
  class: "autocomplete-result loading"
240
258
  };
241
- var _hoisted_13$2 = [
259
+ var _hoisted_13$3 = [
242
260
  "id",
243
261
  "aria-selected",
244
262
  "onMousedown"
245
263
  ];
246
- var _hoisted_14$2 = {
264
+ var _hoisted_14$3 = {
247
265
  key: 0,
248
266
  class: "aform_field-label"
249
267
  };
@@ -440,7 +458,7 @@ var AFormLink_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
440
458
  viewBox: "1.75 2.75 12.5 10.5",
441
459
  "aria-hidden": "true",
442
460
  focusable: "false"
443
- }, [__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$15)) : createCommentVNode("", true)]), dropdownOpen.value ? (openBlock(), createElementBlock("ul", {
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", {
444
462
  key: 0,
445
463
  id: listboxId,
446
464
  class: "autocomplete-results",
@@ -454,8 +472,8 @@ var AFormLink_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
454
472
  "aria-selected": i === activeIndex.value,
455
473
  class: normalizeClass(["autocomplete-result", { "is-active": i === activeIndex.value }]),
456
474
  onMousedown: withModifiers(($event) => selectOption(option), ["prevent"])
457
- }, [renderSlot(_ctx.$slots, "option", { option }, () => [createTextVNode(toDisplayString(option.displayText ?? String(option.id)), 1)], true)], 42, _hoisted_13$2);
458
- }), 128))], 8, _hoisted_11$4)) : createCommentVNode("", true)])), [[unref(vOnClickOutside), onClickOutside]]), __props.label && !__props.embedded ? (openBlock(), createElementBlock("label", _hoisted_14$2, 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);
459
477
  };
460
478
  }
461
479
  }), [["__scopeId", "data-v-07d20e9b"]]);
@@ -466,7 +484,7 @@ var _hoisted_2$17 = { class: "aform_display-value" };
466
484
  var _hoisted_3$15 = { class: "aform_field-label" };
467
485
  var _hoisted_4$14 = { class: "acurrency__row" };
468
486
  var _hoisted_5$14 = { class: "acurrency__field acurrency__field--amount" };
469
- var _hoisted_6$14 = { class: "acurrency__group" };
487
+ var _hoisted_6$11 = { class: "acurrency__group" };
470
488
  var _hoisted_7$5 = { class: "acurrency__currency" };
471
489
  var _hoisted_8$5 = { class: "acurrency__amount-wrap" };
472
490
  var _hoisted_9$4 = [
@@ -479,8 +497,8 @@ var _hoisted_9$4 = [
479
497
  var _hoisted_10$3 = ["for"];
480
498
  var _hoisted_11$3 = { class: "acurrency__row acurrency__row--base" };
481
499
  var _hoisted_12$3 = { class: "acurrency__field acurrency__field--base-currency" };
482
- var _hoisted_13$1 = ["value"];
483
- var _hoisted_14$1 = { class: "aform_field-label" };
500
+ var _hoisted_13$2 = ["value"];
501
+ var _hoisted_14$2 = { class: "aform_field-label" };
484
502
  var _hoisted_15$1 = { class: "acurrency__field acurrency__field--base-amount" };
485
503
  var _hoisted_16$1 = ["value"];
486
504
  var _hoisted_17$1 = { class: "aform_field-label" };
@@ -510,9 +528,9 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
510
528
  exchangeRateLabel: { default: "Exchange Rate" }
511
529
  }, {
512
530
  "modelValue": { default: () => ({
513
- amount: 0,
531
+ amount: null,
514
532
  currency: { id: "" },
515
- baseAmount: 0,
533
+ baseAmount: null,
516
534
  baseCurrency: { id: "" },
517
535
  exchangeRate: 1
518
536
  }) },
@@ -570,16 +588,16 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
570
588
  currency: currencyValue,
571
589
  exchangeRate,
572
590
  baseCurrency: resolvedBaseCurrency.value,
573
- baseAmount: roundAmount(amount * exchangeRate)
591
+ baseAmount: amount === null ? null : roundAmount(amount * exchangeRate)
574
592
  };
575
593
  };
576
594
  const amount = computed({
577
- get: () => modelValue.value?.amount ?? 0,
578
- set: (value) => recompute(value, modelValue.value?.currency ?? { id: "" })
595
+ get: () => modelValue.value?.amount ?? null,
596
+ set: (value) => recompute(numberFromBox(value), modelValue.value?.currency ?? { id: "" })
579
597
  });
580
598
  const currency = computed({
581
599
  get: () => modelValue.value?.currency ?? { id: "" },
582
- set: (value) => recompute(modelValue.value?.amount ?? 0, value)
600
+ set: (value) => recompute(modelValue.value?.amount ?? null, value)
583
601
  });
584
602
  const amountNavigationKeys = /* @__PURE__ */ new Set([
585
603
  "Backspace",
@@ -613,7 +631,7 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
613
631
  });
614
632
  const displayText = computed(() => {
615
633
  const v = modelValue.value;
616
- if (!v || !v.currency?.id) return "—";
634
+ if (!v || !v.currency?.id || v.amount === null) return "—";
617
635
  const currencyText = v.currency.displayText ?? String(v.currency.id);
618
636
  const base = `${v.amount} ${currencyText}`;
619
637
  if (!showBase.value) return base;
@@ -622,7 +640,7 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
622
640
  });
623
641
  return (_ctx, _cache) => {
624
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 }, [
625
- 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, {
626
644
  modelValue: currency.value,
627
645
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => currency.value = $event),
628
646
  mode: __props.mode,
@@ -672,15 +690,15 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
672
690
  class: "aform_input-field acurrency__base-field",
673
691
  type: "text",
674
692
  disabled: ""
675
- }, 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)]),
676
694
  createElementVNode("div", _hoisted_15$1, [createElementVNode("input", {
677
- value: modelValue.value.baseAmount,
695
+ value: modelValue.value?.baseAmount,
678
696
  class: "aform_input-field acurrency__base-field",
679
697
  type: "number",
680
698
  disabled: ""
681
699
  }, null, 8, _hoisted_16$1), createElementVNode("label", _hoisted_17$1, toDisplayString(__props.baseAmountLabel), 1)]),
682
700
  createElementVNode("div", _hoisted_18$1, [createElementVNode("input", {
683
- value: modelValue.value.exchangeRate,
701
+ value: modelValue.value?.exchangeRate,
684
702
  class: "aform_input-field acurrency__base-field",
685
703
  type: "number",
686
704
  disabled: ""
@@ -694,32 +712,74 @@ var ACurrencyInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
694
712
  ], 64))]);
695
713
  };
696
714
  }
697
- }), [["__scopeId", "data-v-f61b058f"]]);
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
+ }
698
753
  //#endregion
699
754
  //#region src/components/form/ADatePicker.vue?vue&type=script&setup=true&lang.ts
700
755
  var _hoisted_1$17 = { class: "aform_display-value" };
701
756
  var _hoisted_2$16 = { key: 0 };
702
757
  var _hoisted_3$14 = ["id"];
703
- var _hoisted_4$13 = {
704
- ref: "datepicker",
705
- class: "adatepicker",
706
- tabindex: "0"
707
- };
708
- var _hoisted_5$13 = {
758
+ var _hoisted_4$13 = { class: "adatepicker" };
759
+ var _hoisted_5$13 = ["aria-label"];
760
+ var _hoisted_6$10 = {
709
761
  colspan: "5",
710
762
  tabindex: -1
711
763
  };
712
- var _hoisted_6$13 = { key: 0 };
713
- var _hoisted_7$4 = { colspan: "7" };
714
- var _hoisted_8$4 = { class: "date-input" };
715
- 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" };
716
770
  var _hoisted_10$2 = ["value"];
717
- 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",
718
778
  "onClick",
719
779
  "onKeydown",
720
780
  "onMouseover"
721
781
  ];
722
- var _hoisted_12$2 = ["id"];
782
+ var _hoisted_14$1 = ["id"];
723
783
  var numberOfRows = 6;
724
784
  var numberOfColumns = 7;
725
785
  //#endregion
@@ -740,7 +800,7 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
740
800
  validation: { default: () => ({ errorMessage: "" }) },
741
801
  errors: {}
742
802
  }, {
743
- "modelValue": { default: () => /* @__PURE__ */ new Date() },
803
+ "modelValue": {},
744
804
  "modelModifiers": {}
745
805
  }),
746
806
  emits: /*@__PURE__*/ mergeModels(["get-date"], ["update:modelValue"]),
@@ -748,11 +808,13 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
748
808
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
749
809
  const { errorId } = fieldErrorA11y(__props.uuid, errorText);
750
810
  const date = useModel(__props, "modelValue");
751
- const selectedDate = ref(new Date(date.value));
752
- const currentMonth = ref(selectedDate.value.getMonth());
753
- 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);
754
814
  const currentDates = ref([]);
755
- const hoveredDate = ref(new Date(date.value));
815
+ const focusedDay = ref(selectedDate.value);
816
+ const grid = useTemplateRef("grid");
817
+ const hoveredDate = ref(selectedDate.value);
756
818
  const start_date = ref(null);
757
819
  const end_date = ref(null);
758
820
  const startDateInput = useTemplateRef("start-date-input");
@@ -764,69 +826,65 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
764
826
  /*******************
765
827
  Computed
766
828
  *******************/
767
- const monthAndYear = computed(() => {
768
- return new Date(currentYear.value, currentMonth.value, 1).toLocaleDateString(void 0, {
769
- year: "numeric",
770
- month: "long"
771
- });
772
- });
773
- const getStartDate = computed(() => {
774
- return start_date.value != null ? parseDateToString(start_date.value) : "";
775
- });
776
- const getEndDate = computed(() => {
777
- return end_date.value != null ? parseDateToString(end_date.value) : "";
778
- });
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));
779
842
  /*******************
780
843
  Functions
781
844
  *******************/
782
- const parseDateToString = (dateValue) => {
783
- if (!validateDate(dateValue)) return "";
784
- return dateValue.getMonth() + 1 + "/" + dateValue.getDate() + "/" + dateValue.getFullYear();
785
- };
845
+ const boxText = (day) => day ? writeTypedDay(day) : "";
786
846
  const isTodaysDate = (day) => {
787
- const todaysDate = /* @__PURE__ */ new Date();
788
- if (currentMonth.value !== todaysDate.getMonth()) return false;
789
- 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);
790
850
  };
791
851
  const isSelectedDate = (day) => {
792
- return new Date(day).toDateString() === new Date(selectedDate.value).toDateString();
852
+ return day.equals(selectedDate.value);
793
853
  };
794
854
  const isStartDate = (day) => {
795
- const start = start_date.value;
796
- if (!validateDate(start)) return false;
797
- return new Date(day).toDateString() === start.toDateString();
855
+ return start_date.value !== null && day.equals(start_date.value);
798
856
  };
799
857
  const isEndDate = (day) => {
800
- const end = end_date.value;
801
- if (!validateDate(end)) return false;
802
- return new Date(day).toDateString() === end.toDateString();
803
- };
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
+ });
804
867
  const getCurrentCell = (rowNo, colNo) => {
805
- return (rowNo - 1) * numberOfColumns + colNo;
868
+ return (rowNo - 1) * numberOfColumns + colNo - 1;
806
869
  };
807
870
  const isInDateRange = (day) => {
808
871
  const start = start_date.value;
809
- if (!validateDate(start)) return false;
810
- const this_date = new Date(day);
811
- const end = end_date.value;
812
- const temp_end_date = validateDate(end) ? end : new Date(hoveredDate.value);
813
- 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;
814
875
  };
815
876
  const getCurrentDate = (rowNo, colNo) => {
816
877
  return currentDates.value[getCurrentCell(rowNo, colNo)];
817
878
  };
818
879
  const hoverDate = (currentIndex) => {
819
- hoveredDate.value = new Date(currentDates.value[currentIndex]);
880
+ hoveredDate.value = currentDates.value[currentIndex];
820
881
  };
821
882
  const preventCellSelection = (event) => {
822
883
  if (event.target?.tagName !== "INPUT") event.preventDefault();
823
884
  };
824
885
  const populateMonth = () => {
825
- currentDates.value = [];
826
- const firstOfMonth = new Date(currentYear.value, currentMonth.value, 1);
827
- const monthStartWeekday = firstOfMonth.getDay();
828
- const calendarStartDay = firstOfMonth.setDate(firstOfMonth.getDate() - monthStartWeekday);
829
- 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 }));
830
888
  };
831
889
  const previousYear = () => currentYear.value -= 1;
832
890
  const nextYear = () => currentYear.value += 1;
@@ -845,103 +903,141 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
845
903
  const enterDate = (event) => {
846
904
  if (event.key === "Enter") enterInputDate();
847
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
+ };
848
937
  const selectDate = (currentIndex) => {
849
- 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();
850
943
  if (__props.selectRange) {
851
944
  const start = start_date.value;
852
945
  if (start == null || end_date.value != null) {
853
- start_date.value = date.value;
946
+ start_date.value = picked;
854
947
  end_date.value = null;
855
- } else if (validateDate(start) && selectedDate.value.getTime() < start.getTime()) {
948
+ } else if (Temporal.PlainDate.compare(picked, start) < 0) {
856
949
  end_date.value = null;
857
- start_date.value = date.value;
858
- } else end_date.value = date.value;
859
- if (startDateInput.value) startDateInput.value.value = parseDateToString(start_date.value) ?? "";
860
- 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);
861
954
  }
862
955
  emitData();
863
956
  };
864
957
  const testDateOrder = () => {
865
958
  const start = start_date.value;
866
959
  const end = end_date.value;
867
- if (validateDate(end) && validateDate(start) && end.getTime() < start.getTime()) [start_date.value, end_date.value] = [end, start];
868
- };
869
- const validateDate = (dateValue) => {
870
- 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];
871
961
  };
872
962
  const enterInputDate = () => {
873
963
  if (startDateInput.value?.value == "") start_date.value = null;
874
- else if (startDateInput.value) {
875
- const start = new Date(startDateInput.value.value);
876
- start_date.value = validateDate(start) ? start : null;
877
- }
964
+ else if (startDateInput.value) start_date.value = readTypedDay(startDateInput.value.value) ?? null;
878
965
  if (endDateInput.value?.value == "") end_date.value = null;
879
- else if (endDateInput.value) {
880
- const end = new Date(endDateInput.value.value);
881
- end_date.value = validateDate(end) ? end : null;
882
- }
883
- if (validateDate(start_date.value)) {
884
- 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();
885
969
  selectedDate.value = start_date.value;
886
970
  }
887
971
  emitData();
888
972
  };
889
973
  const emitData = () => {
890
974
  emit("get-date", {
891
- start: __props.selectRange ? start_date.value : null,
892
- end: __props.selectRange ? end_date.value : null,
893
- 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()
894
978
  });
895
979
  };
896
980
  /*******************
897
981
  Hooks
898
982
  *******************/
899
- onMounted(async () => {
900
- populateMonth();
901
- await nextTick();
902
- const $selectedDate = document.getElementsByClassName("selectedDate");
903
- if ($selectedDate.length > 0) $selectedDate[0].focus();
904
- else {
905
- const $todaysDate = document.getElementsByClassName("todaysDate");
906
- if ($todaysDate.length > 0) $todaysDate[0].focus();
907
- }
908
- });
983
+ populateMonth();
909
984
  /*******************
910
985
  Watchers
911
986
  *******************/
912
- 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
+ });
913
1003
  /*******************
914
1004
  Expose
915
1005
  *******************/
916
1006
  __expose({
917
1007
  currentMonth,
918
1008
  currentYear,
919
- selectedDate
1009
+ selectedDate: computed(() => selectedDate.value.toString())
920
1010
  });
921
1011
  return (_ctx, _cache) => {
922
1012
  return __props.mode === "display" || __props.mode === "read" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
923
- createElementVNode("span", _hoisted_1$17, toDisplayString(date.value ? new Date(date.value).toLocaleDateString() : ""), 1),
1013
+ createElementVNode("span", _hoisted_1$17, toDisplayString(displayValue.value), 1),
924
1014
  __props.label ? (openBlock(), createElementBlock("label", _hoisted_2$16, toDisplayString(__props.label), 1)) : createCommentVNode("", true),
925
1015
  withDirectives(createElementVNode("p", {
926
1016
  id: unref(errorId),
927
1017
  class: "aform_error",
928
1018
  role: "alert"
929
1019
  }, toDisplayString(errorText.value), 9, _hoisted_3$14), [[vShow, errorText.value]])
930
- ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createElementVNode("div", _hoisted_4$13, [createElementVNode("table", { onMousedown: preventCellSelection }, [createElementVNode("tbody", null, [
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, [
931
1027
  createElementVNode("tr", null, [
932
1028
  createElementVNode("td", {
933
1029
  id: "previous-month-btn",
934
1030
  tabindex: -1,
935
1031
  onClick: previousMonth
936
1032
  }, "<"),
937
- createElementVNode("th", _hoisted_5$13, toDisplayString(monthAndYear.value), 1),
1033
+ createElementVNode("th", _hoisted_6$10, toDisplayString(monthAndYear.value), 1),
938
1034
  createElementVNode("td", {
939
1035
  id: "next-month-btn",
940
1036
  tabindex: -1,
941
1037
  onClick: nextMonth
942
1038
  }, ">")
943
1039
  ]),
944
- __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, [
945
1041
  createElementVNode("input", {
946
1042
  ref: "start-date-input",
947
1043
  value: getStartDate.value,
@@ -950,7 +1046,7 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
950
1046
  placeholder: "start date",
951
1047
  onBlur: _cache[0] || (_cache[0] = ($event) => enterInputDate()),
952
1048
  onKeydown: enterDate
953
- }, null, 40, _hoisted_9$3),
1049
+ }, null, 40, _hoisted_10$2),
954
1050
  _cache[2] || (_cache[2] = createElementVNode("div", null, "-", -1)),
955
1051
  createElementVNode("input", {
956
1052
  ref: "end-date-input",
@@ -960,17 +1056,11 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
960
1056
  placeholder: "end date",
961
1057
  onBlur: _cache[1] || (_cache[1] = ($event) => enterInputDate()),
962
1058
  onKeydown: enterDate
963
- }, null, 40, _hoisted_10$2)
1059
+ }, null, 40, _hoisted_11$2)
964
1060
  ])])])) : createCommentVNode("", true),
965
- _cache[3] || (_cache[3] = createElementVNode("tr", { class: "days-header" }, [
966
- createElementVNode("td", null, "M"),
967
- createElementVNode("td", null, "T"),
968
- createElementVNode("td", null, "W"),
969
- createElementVNode("td", null, "T"),
970
- createElementVNode("td", null, "F"),
971
- createElementVNode("td", null, "S"),
972
- createElementVNode("td", null, "S")
973
- ], -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))]),
974
1064
  (openBlock(), createElementBlock(Fragment, null, renderList(numberOfRows, (rowNo) => {
975
1065
  return createElementVNode("tr", { key: rowNo }, [(openBlock(), createElementBlock(Fragment, null, renderList(numberOfColumns, (colNo) => {
976
1066
  return createElementVNode("td", {
@@ -986,21 +1076,24 @@ var ADatePicker_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__
986
1076
  }]),
987
1077
  contenteditable: false,
988
1078
  spellcheck: false,
989
- 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,
990
1083
  onClick: withModifiers(($event) => selectDate(getCurrentCell(rowNo, colNo)), ["prevent", "stop"]),
991
- onKeydown: withKeys(($event) => selectDate(getCurrentCell(rowNo, colNo)), ["enter"]),
1084
+ onKeydown: ($event) => onDayKeydown($event, getCurrentCell(rowNo, colNo)),
992
1085
  onMouseover: ($event) => hoverDate(getCurrentCell(rowNo, colNo))
993
- }, toDisplayString(new Date(getCurrentDate(rowNo, colNo)).getDate()), 43, _hoisted_11$2);
1086
+ }, toDisplayString(getCurrentDate(rowNo, colNo).day), 43, _hoisted_13$1);
994
1087
  }), 64))]);
995
1088
  }), 64))
996
- ])], 32)], 512), withDirectives(createElementVNode("p", {
1089
+ ])], 40, _hoisted_5$13)]), withDirectives(createElementVNode("p", {
997
1090
  id: unref(errorId),
998
1091
  class: "aform_error",
999
1092
  role: "alert"
1000
- }, toDisplayString(errorText.value), 9, _hoisted_12$2), [[vShow, errorText.value]])], 64));
1093
+ }, toDisplayString(errorText.value), 9, _hoisted_14$1), [[vShow, errorText.value]])], 64));
1001
1094
  };
1002
1095
  }
1003
- }), [["__scopeId", "data-v-e22e44d1"]]);
1096
+ }), [["__scopeId", "data-v-d0133599"]]);
1004
1097
  //#endregion
1005
1098
  //#region src/components/form/ADateTimeInput.vue?vue&type=script&setup=true&lang.ts
1006
1099
  var _hoisted_1$16 = { class: "adate_time" };
@@ -1011,7 +1104,7 @@ var _hoisted_5$12 = {
1011
1104
  key: 0,
1012
1105
  class: "colon"
1013
1106
  };
1014
- var _hoisted_6$12 = ["onKeydown"];
1107
+ var _hoisted_6$9 = ["onKeydown"];
1015
1108
  //#endregion
1016
1109
  //#region src/components/form/ADateTimeInput.vue
1017
1110
  var ADateTimeInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1185,7 +1278,7 @@ var ADateTimeInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1185
1278
  _cache[8] || (_cache[8] = withKeys(withModifiers(($event) => tick("seconds"), ["prevent"]), ["up"])),
1186
1279
  _cache[9] || (_cache[9] = withKeys(withModifiers(($event) => tick("seconds", -1), ["prevent"]), ["down"]))
1187
1280
  ]
1188
- }, null, 40, _hoisted_6$12)), [[vModelText, timeData.seconds]]) : createCommentVNode("", true),
1281
+ }, null, 40, _hoisted_6$9)), [[vModelText, timeData.seconds]]) : createCommentVNode("", true),
1189
1282
  !__props.allowMilitaryTime ? withDirectives((openBlock(), createElementBlock("select", {
1190
1283
  key: 2,
1191
1284
  ref: "meridiem-selector",
@@ -1229,6 +1322,7 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1229
1322
  type: Boolean,
1230
1323
  default: false
1231
1324
  },
1325
+ defaultDate: {},
1232
1326
  defaultHours: { default: 12 },
1233
1327
  defaultMinutes: { default: 0 },
1234
1328
  defaultSeconds: { default: 0 },
@@ -1246,15 +1340,14 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1246
1340
  setup(__props, { emit: __emit }) {
1247
1341
  const emit = __emit;
1248
1342
  provide("select-range", __props.selectRange);
1249
- const today = /* @__PURE__ */ new Date();
1343
+ const today = Temporal.Now.plainDateISO();
1250
1344
  const pickerStart = ref(today);
1251
1345
  const pickerEnd = ref(today);
1252
1346
  const startTimeMs = ref(0);
1253
1347
  const endTimeMs = ref(0);
1254
- const mergeDateTime = (date, timeMs) => {
1255
- const d = new Date(date);
1256
- d.setHours(0, 0, 0, 0);
1257
- 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);
1258
1351
  };
1259
1352
  const timePayloadToMs = (payload) => {
1260
1353
  let h;
@@ -1276,8 +1369,8 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1276
1369
  const handleDate = (data) => {
1277
1370
  emit("get-date", data);
1278
1371
  if (__props.selectRange) {
1279
- pickerStart.value = data.start ?? data.selected;
1280
- pickerEnd.value = data.end ?? data.selected;
1372
+ pickerStart.value = fromISODate(data.start ?? data.selected) ?? today;
1373
+ pickerEnd.value = fromISODate(data.end ?? data.selected) ?? today;
1281
1374
  tryEmitRange("user");
1282
1375
  }
1283
1376
  };
@@ -1294,9 +1387,10 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1294
1387
  return openBlock(), createElementBlock("div", _hoisted_1$15, [
1295
1388
  __props.showDate ? (openBlock(), createBlock(ADatePicker_default, {
1296
1389
  key: 0,
1390
+ "model-value": __props.defaultDate,
1297
1391
  "select-range": __props.selectRange,
1298
1392
  onGetDate: handleDate
1299
- }, null, 8, ["select-range"])) : createCommentVNode("", true),
1393
+ }, null, 8, ["model-value", "select-range"])) : createCommentVNode("", true),
1300
1394
  __props.showTime ? (openBlock(), createBlock(ADateTimeInput_default, {
1301
1395
  key: 1,
1302
1396
  "allow-military-time": __props.allowMilitaryTime,
@@ -1334,21 +1428,67 @@ var ADateSelection_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
1334
1428
  ]);
1335
1429
  };
1336
1430
  }
1337
- }), [["__scopeId", "data-v-48e71f28"]]);
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
+ }
1338
1478
  //#endregion
1339
1479
  //#region src/components/form/ADate.vue?vue&type=script&setup=true&lang.ts
1340
- var _hoisted_1$14 = { class: "aform_form-element" };
1341
- var _hoisted_2$13 = { class: "aform_display-value" };
1342
- var _hoisted_3$12 = { class: "aform_field-label" };
1343
- 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 = [
1344
1483
  "id",
1484
+ "aria-controls",
1345
1485
  "disabled",
1346
1486
  "required",
1347
1487
  "aria-invalid",
1348
1488
  "aria-describedby"
1349
1489
  ];
1350
- var _hoisted_5$11 = ["for"];
1351
- var _hoisted_6$11 = ["id"];
1490
+ var _hoisted_4$11 = ["for"];
1491
+ var _hoisted_5$11 = ["id"];
1352
1492
  //#endregion
1353
1493
  //#region src/components/form/ADate.vue
1354
1494
  var ADate_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1372,63 +1512,69 @@ var ADate_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__
1372
1512
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
1373
1513
  const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
1374
1514
  const modelValue = useModel(__props, "modelValue");
1375
- const currentDate = ref(modelValue.value ? new Date(modelValue.value) : /* @__PURE__ */ new Date());
1376
- const inputDate = computed({
1377
- get: () => currentDate.value.toISOString().split("T")[0],
1378
- set: (value) => {
1379
- currentDate.value = new Date(value);
1380
- modelValue.value = value;
1381
- }
1515
+ const boxDay = computed({
1516
+ get: () => modelValue.value,
1517
+ set: (text) => modelValue.value = dayFromBox(text)
1382
1518
  });
1383
- const pickerRef = useTemplateRef("picker");
1384
- const showPicker = ref(false);
1385
- 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);
1386
1521
  const openPicker = () => {
1387
1522
  if (__props.mode !== "read") showPicker.value = !showPicker.value;
1388
1523
  };
1389
- watch(() => modelValue.value, (newValue) => {
1390
- if (newValue) currentDate.value = new Date(newValue);
1391
- });
1392
1524
  const handleDate = (data) => {
1393
- currentDate.value = data.selected;
1394
- modelValue.value = inputDate.value;
1395
- showPicker.value = false;
1525
+ modelValue.value = data.selected;
1526
+ closePicker();
1396
1527
  };
1397
1528
  return (_ctx, _cache) => {
1398
- 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 }, [
1399
1534
  withDirectives(createElementVNode("input", {
1400
1535
  id: __props.uuid,
1401
1536
  ref: "date",
1402
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputDate.value = $event),
1537
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => boxDay.value = $event),
1403
1538
  class: "aform_input-field",
1404
1539
  type: "date",
1540
+ "aria-haspopup": "dialog",
1541
+ "aria-controls": unref(showPicker) ? unref(calendarId) : void 0,
1405
1542
  disabled: __props.mode === "read",
1406
1543
  required: __props.required,
1407
1544
  "aria-invalid": unref(invalid),
1408
1545
  "aria-describedby": unref(describedBy),
1409
- onClick: openPicker
1410
- }, null, 8, _hoisted_4$11), [[vModelText, inputDate.value]]),
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]]),
1411
1549
  createElementVNode("label", {
1412
1550
  class: "aform_field-label",
1413
1551
  for: __props.uuid
1414
- }, toDisplayString(__props.label), 9, _hoisted_5$11),
1552
+ }, toDisplayString(__props.label), 9, _hoisted_4$11),
1415
1553
  withDirectives(createElementVNode("p", {
1416
1554
  id: unref(errorId),
1417
1555
  class: "aform_error",
1418
1556
  role: "alert"
1419
- }, toDisplayString(errorText.value), 9, _hoisted_6$11), [[vShow, errorText.value]]),
1420
- showPicker.value ? (openBlock(), createBlock(ADateSelection_default, {
1557
+ }, toDisplayString(errorText.value), 9, _hoisted_5$11), [[vShow, errorText.value]]),
1558
+ unref(showPicker) ? (openBlock(), createBlock(ADateSelection_default, {
1421
1559
  key: 0,
1560
+ id: unref(calendarId),
1422
1561
  ref: "picker",
1423
1562
  class: "adate-picker",
1563
+ role: "dialog",
1564
+ "aria-label": __props.label,
1565
+ "default-date": modelValue.value || void 0,
1424
1566
  "select-range": false,
1425
1567
  "show-time": false,
1426
1568
  onGetDate: handleDate
1427
- }, null, 512)) : createCommentVNode("", true)
1428
- ], 64))]);
1569
+ }, null, 8, [
1570
+ "id",
1571
+ "aria-label",
1572
+ "default-date"
1573
+ ])) : createCommentVNode("", true)
1574
+ ], 64))], 32);
1429
1575
  };
1430
1576
  }
1431
- }), [["__scopeId", "data-v-e1403ea0"]]);
1577
+ }), [["__scopeId", "data-v-16f8f8f2"]]);
1432
1578
  //#endregion
1433
1579
  //#region src/components/form/ADropdown.vue?vue&type=script&setup=true&lang.ts
1434
1580
  var _hoisted_1$13 = { class: "aform_form-element" };
@@ -1448,7 +1594,7 @@ var _hoisted_4$10 = [
1448
1594
  "onKeydown"
1449
1595
  ];
1450
1596
  var _hoisted_5$10 = ["id", "aria-label"];
1451
- var _hoisted_6$10 = {
1597
+ var _hoisted_6$8 = {
1452
1598
  key: 0,
1453
1599
  class: "loading autocomplete-result"
1454
1600
  };
@@ -1603,7 +1749,7 @@ var ADropdown_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1603
1749
  class: "autocomplete-results",
1604
1750
  role: "listbox",
1605
1751
  "aria-label": __props.label
1606
- }, [dropdown.loading ? (openBlock(), createElementBlock("li", _hoisted_6$10, "Loading results...")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(dropdown.results, (result, i) => {
1752
+ }, [dropdown.loading ? (openBlock(), createElementBlock("li", _hoisted_6$8, "Loading results...")) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(dropdown.results, (result, i) => {
1607
1753
  return openBlock(), createElementBlock("li", {
1608
1754
  id: `${unref(listboxId)}-opt-${i}`,
1609
1755
  key: result,
@@ -1696,7 +1842,7 @@ var _hoisted_5$9 = [
1696
1842
  "value",
1697
1843
  "disabled"
1698
1844
  ];
1699
- var _hoisted_6$9 = [
1845
+ var _hoisted_6$7 = [
1700
1846
  "name",
1701
1847
  "value",
1702
1848
  "disabled"
@@ -1844,7 +1990,7 @@ var ASegmentedControl_default = /*#__PURE__*/ _plugin_vue_export_helper_default(
1844
1990
  name: groupName.value,
1845
1991
  value: choice,
1846
1992
  disabled: __props.mode === "read"
1847
- }, 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);
1848
1994
  }), 128))], 8, _hoisted_3$10),
1849
1995
  withDirectives(createElementVNode("p", {
1850
1996
  id: unref(errorId),
@@ -1857,18 +2003,19 @@ var ASegmentedControl_default = /*#__PURE__*/ _plugin_vue_export_helper_default(
1857
2003
  }), [["__scopeId", "data-v-8b740fd8"]]);
1858
2004
  //#endregion
1859
2005
  //#region src/components/form/ADateTime.vue?vue&type=script&setup=true&lang.ts
1860
- var _hoisted_1$11 = { class: "aform_form-element" };
1861
- var _hoisted_2$10 = { class: "aform_display-value" };
1862
- var _hoisted_3$9 = { class: "aform_field-label" };
1863
- 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 = [
1864
2009
  "id",
2010
+ "aria-expanded",
2011
+ "aria-controls",
1865
2012
  "value",
1866
2013
  "disabled",
1867
2014
  "aria-invalid",
1868
2015
  "aria-describedby"
1869
2016
  ];
1870
- var _hoisted_5$8 = ["for"];
1871
- var _hoisted_6$8 = ["id"];
2017
+ var _hoisted_4$8 = ["for"];
2018
+ var _hoisted_5$8 = ["id"];
1872
2019
  //#endregion
1873
2020
  //#region src/components/form/ADateTime.vue
1874
2021
  var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -1900,81 +2047,119 @@ var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1900
2047
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
1901
2048
  const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
1902
2049
  const modelValue = useModel(__props, "modelValue");
1903
- const currentDateTime = ref(modelValue.value ? new Date(modelValue.value) : /* @__PURE__ */ new Date());
1904
- const showPicker = ref(false);
1905
- const pickerRef = ref(null);
1906
- 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);
1907
2065
  const openPicker = () => {
1908
2066
  if (__props.mode !== "read") showPicker.value = true;
1909
2067
  };
2068
+ const WITHOUT_SECONDS = {
2069
+ year: "numeric",
2070
+ month: "numeric",
2071
+ day: "numeric",
2072
+ hour: "numeric",
2073
+ minute: "numeric"
2074
+ };
1910
2075
  const displayValue = computed(() => {
1911
2076
  if (!modelValue.value) return "";
1912
- return currentDateTime.value.toLocaleString();
2077
+ const format = __props.useSeconds ? void 0 : WITHOUT_SECONDS;
2078
+ return currentDateTime.value?.toPlainDateTime().toLocaleString(void 0, format) ?? "Invalid Date";
1913
2079
  });
1914
2080
  const datetimeDisplay = computed(() => displayValue.value);
1915
2081
  const pickerDefaults = computed(() => {
1916
- const d = currentDateTime.value;
1917
- const hours24 = d.getHours();
2082
+ const d = currentDateTime.value ?? now();
2083
+ const hours24 = d.hour;
1918
2084
  const meridiem = hours24 >= 12 ? "PM" : "AM";
1919
2085
  const hours12 = hours24 % 12 || 12;
1920
2086
  return {
1921
2087
  hours: __props.allowMilitaryTime ? hours24 : hours12,
1922
- minutes: d.getMinutes(),
1923
- seconds: d.getSeconds(),
2088
+ minutes: d.minute,
2089
+ seconds: d.second,
1924
2090
  meridiem
1925
2091
  };
1926
2092
  });
1927
- const emitModel = () => {
1928
- modelValue.value = currentDateTime.value.toISOString();
2093
+ const setMoment = (moment) => {
2094
+ currentDateTime.value = moment;
2095
+ modelValue.value = moment.toInstant().toString({ fractionalSecondDigits: 3 });
1929
2096
  };
1930
2097
  const handleDate = (data) => {
1931
- const next = new Date(currentDateTime.value);
1932
- next.setFullYear(data.selected.getFullYear(), data.selected.getMonth(), data.selected.getDate());
1933
- currentDateTime.value = next;
1934
- 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
+ }));
1935
2105
  };
1936
2106
  const handleTime = (data) => {
1937
2107
  if (data.source === "init") return;
1938
- const next = new Date(currentDateTime.value);
1939
2108
  const hours = data.militaryTime ?? data.hours;
1940
- next.setHours(hours, data.minutes, __props.useSeconds ? data.seconds : 0, 0);
1941
- currentDateTime.value = next;
1942
- emitModel();
1943
- };
1944
- watch(() => modelValue.value, (newValue) => {
1945
- if (newValue) currentDateTime.value = new Date(newValue);
1946
- });
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));
1947
2119
  return (_ctx, _cache) => {
1948
- 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 }, [
1949
2125
  createElementVNode("input", {
1950
2126
  id: __props.uuid,
2127
+ ref: "box",
1951
2128
  class: "aform_input-field",
1952
2129
  type: "text",
2130
+ role: "combobox",
2131
+ "aria-haspopup": "dialog",
2132
+ "aria-expanded": unref(showPicker),
2133
+ "aria-controls": unref(showPicker) ? unref(calendarId) : void 0,
1953
2134
  value: datetimeDisplay.value,
1954
2135
  placeholder: "Select date and time",
1955
2136
  disabled: __props.mode === "read",
1956
2137
  readonly: "",
1957
2138
  "aria-invalid": unref(invalid),
1958
2139
  "aria-describedby": unref(describedBy),
1959
- onClick: openPicker
1960
- }, null, 8, _hoisted_4$8),
2140
+ onClick: openPicker,
2141
+ onKeydown: _cache[0] || (_cache[0] = (...args) => unref(openFromKey) && unref(openFromKey)(...args))
2142
+ }, null, 40, _hoisted_3$9),
1961
2143
  createElementVNode("label", {
1962
2144
  class: "aform_field-label",
1963
2145
  for: __props.uuid
1964
- }, toDisplayString(__props.label), 9, _hoisted_5$8),
2146
+ }, toDisplayString(__props.label), 9, _hoisted_4$8),
1965
2147
  withDirectives(createElementVNode("p", {
1966
2148
  id: unref(errorId),
1967
2149
  class: "aform_error",
1968
2150
  role: "alert"
1969
- }, toDisplayString(errorText.value), 9, _hoisted_6$8), [[vShow, errorText.value]]),
1970
- showPicker.value ? (openBlock(), createBlock(ADateSelection_default, {
2151
+ }, toDisplayString(errorText.value), 9, _hoisted_5$8), [[vShow, errorText.value]]),
2152
+ unref(showPicker) ? (openBlock(), createBlock(ADateSelection_default, {
1971
2153
  key: 0,
1972
- ref_key: "pickerRef",
1973
- ref: pickerRef,
2154
+ id: unref(calendarId),
2155
+ ref: "picker",
1974
2156
  class: "adatetime-picker",
2157
+ role: "dialog",
2158
+ "aria-label": __props.label,
1975
2159
  "select-range": false,
1976
2160
  "show-date": true,
1977
2161
  "show-time": true,
2162
+ "default-date": currentDateTime.value?.toPlainDate().toString(),
1978
2163
  "default-hours": pickerDefaults.value.hours,
1979
2164
  "default-minutes": pickerDefaults.value.minutes,
1980
2165
  "default-seconds": pickerDefaults.value.seconds,
@@ -1984,6 +2169,9 @@ var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1984
2169
  onGetDate: handleDate,
1985
2170
  onGetTime: handleTime
1986
2171
  }, null, 8, [
2172
+ "id",
2173
+ "aria-label",
2174
+ "default-date",
1987
2175
  "default-hours",
1988
2176
  "default-minutes",
1989
2177
  "default-seconds",
@@ -1991,10 +2179,10 @@ var ADateTime_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
1991
2179
  "allow-military-time",
1992
2180
  "use-seconds"
1993
2181
  ])) : createCommentVNode("", true)
1994
- ], 64))]);
2182
+ ], 64))], 32);
1995
2183
  };
1996
2184
  }
1997
- }), [["__scopeId", "data-v-427253ba"]]);
2185
+ }), [["__scopeId", "data-v-05cbbabe"]]);
1998
2186
  //#endregion
1999
2187
  //#region src/components/form/ADuration.vue?vue&type=script&setup=true&lang.ts
2000
2188
  var _hoisted_1$10 = { class: "aduration" };
@@ -2005,7 +2193,7 @@ var _hoisted_4$7 = {
2005
2193
  class: "aduration__summary"
2006
2194
  };
2007
2195
  var _hoisted_5$7 = { class: "aduration__value" };
2008
- var _hoisted_6$7 = { class: "aduration__ms" };
2196
+ var _hoisted_6$6 = { class: "aduration__held" };
2009
2197
  //#endregion
2010
2198
  //#region src/components/form/ADuration.vue
2011
2199
  var ADuration_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -2030,47 +2218,40 @@ var ADuration_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
2030
2218
  const modelValue = useModel(__props, "modelValue");
2031
2219
  const startDatetime = ref(null);
2032
2220
  const endDatetime = ref(null);
2033
- const duration = computed(() => {
2034
- if (!startDatetime.value || !endDatetime.value) return 0;
2035
- const ms = endDatetime.value.getTime() - startDatetime.value.getTime();
2036
- return ms > 0 ? ms : 0;
2037
- });
2038
- watch(duration, (newMs) => {
2039
- modelValue.value = newMs;
2040
- });
2221
+ const durationBetween = (start, end) => Temporal.Duration.from({ milliseconds: Math.max(end.getTime() - start.getTime(), 0) }).round({ largestUnit: "days" }).toString();
2041
2222
  const handleRange = (data) => {
2042
2223
  if (data.source === "init") return;
2043
2224
  startDatetime.value = data.start;
2044
2225
  endDatetime.value = data.end;
2045
- modelValue.value = duration.value;
2046
- };
2047
- const humanDuration = computed(() => {
2048
- const ms = duration.value;
2049
- if (ms === 0) return "0s";
2050
- const s = Math.floor(ms / 1e3) % 60;
2051
- const m = Math.floor(ms / 6e4) % 60;
2052
- const h = Math.floor(ms / 36e5) % 24;
2053
- const d = Math.floor(ms / 864e5);
2054
- return [
2055
- d && `${d}d`,
2056
- h && `${h}h`,
2057
- m && `${m}m`,
2058
- s && `${s}s`
2059
- ].filter(Boolean).join(" ") || "0s";
2060
- });
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");
2061
2251
  const displayValue = computed(() => {
2062
- const ms = modelValue.value;
2063
- if (!ms) return "—";
2064
- const s = Math.floor(ms / 1e3) % 60;
2065
- const m = Math.floor(ms / 6e4) % 60;
2066
- const h = Math.floor(ms / 36e5) % 24;
2067
- const d = Math.floor(ms / 864e5);
2068
- return [
2069
- d && `${d}d`,
2070
- h && `${h}h`,
2071
- m && `${m}m`,
2072
- s && `${s}s`
2073
- ].filter(Boolean).join(" ") || "0s";
2252
+ if (!modelValue.value) return "—";
2253
+ if (!heldDuration.value) return "Invalid Duration";
2254
+ return heldDuration.value.blank ? "—" : describeDuration(heldDuration.value);
2074
2255
  });
2075
2256
  return (_ctx, _cache) => {
2076
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, {
@@ -2084,25 +2265,26 @@ var ADuration_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PU
2084
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, [
2085
2266
  _cache[0] || (_cache[0] = createElementVNode("span", { class: "aduration__label" }, "Duration:", -1)),
2086
2267
  createElementVNode("span", _hoisted_5$7, toDisplayString(humanDuration.value), 1),
2087
- createElementVNode("span", _hoisted_6$7, "(" + toDisplayString(modelValue.value ?? 0) + " ms)", 1)
2268
+ createElementVNode("span", _hoisted_6$6, "(" + toDisplayString(modelValue.value) + ")", 1)
2088
2269
  ])) : createCommentVNode("", true)])], 64))]);
2089
2270
  };
2090
2271
  }
2091
- }), [["__scopeId", "data-v-64f6240e"]]);
2272
+ }), [["__scopeId", "data-v-133162a6"]]);
2092
2273
  //#endregion
2093
2274
  //#region src/components/form/ADateRange.vue?vue&type=script&setup=true&lang.ts
2094
- var _hoisted_1$9 = { class: "aform_form-element" };
2095
- var _hoisted_2$8 = { class: "aform_display-value" };
2096
- var _hoisted_3$7 = { class: "aform_field-label" };
2097
- 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 = [
2098
2278
  "id",
2279
+ "aria-expanded",
2280
+ "aria-controls",
2099
2281
  "value",
2100
2282
  "disabled",
2101
2283
  "aria-invalid",
2102
2284
  "aria-describedby"
2103
2285
  ];
2104
- var _hoisted_5$6 = ["for"];
2105
- var _hoisted_6$6 = ["id"];
2286
+ var _hoisted_4$6 = ["for"];
2287
+ var _hoisted_5$6 = ["id"];
2106
2288
  //#endregion
2107
2289
  //#region src/components/form/ADateRange.vue
2108
2290
  var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
@@ -2126,22 +2308,18 @@ var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
2126
2308
  }),
2127
2309
  emits: ["update:modelValue"],
2128
2310
  setup(__props) {
2129
- const fmt = (d) => new Date(d).toLocaleDateString();
2311
+ const fmt = (d) => fromISODate(d)?.toLocaleString() ?? "Invalid Date";
2130
2312
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
2131
2313
  const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
2132
2314
  const modelValue = useModel(__props, "modelValue");
2133
- const startDate = ref(modelValue.value.start_date ? new Date(modelValue.value.start_date) : null);
2134
- const endDate = ref(modelValue.value.end_date ? new Date(modelValue.value.end_date) : null);
2135
- const showPicker = ref(false);
2136
- const pickerRef = ref(null);
2137
- 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);
2138
2319
  const openPicker = () => {
2139
2320
  if (__props.mode !== "read") showPicker.value = true;
2140
2321
  };
2141
- const formatDate = (d) => {
2142
- if (!d) return "";
2143
- return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
2144
- };
2322
+ const formatDate = (d) => d?.toLocaleString() ?? "";
2145
2323
  const rangeDisplay = computed(() => {
2146
2324
  const s = formatDate(startDate.value);
2147
2325
  const e = formatDate(endDate.value);
@@ -2150,8 +2328,8 @@ var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
2150
2328
  return "";
2151
2329
  });
2152
2330
  const displayValue = computed(() => {
2153
- const s = modelValue.value.start_date;
2154
- const e = modelValue.value.end_date;
2331
+ const s = modelValue.value?.start_date;
2332
+ const e = modelValue.value?.end_date;
2155
2333
  if (!s && !e) return "";
2156
2334
  if (s && e) return `${fmt(s)} — ${fmt(e)}`;
2157
2335
  if (s) return `From ${fmt(s)}`;
@@ -2160,64 +2338,75 @@ var ADateRange_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__P
2160
2338
  const ensureOrder = () => {
2161
2339
  const s = startDate.value;
2162
2340
  const e = endDate.value;
2163
- 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];
2164
2342
  };
2165
- const toISODate = (d) => d ? d.toISOString().split("T")[0] : null;
2166
2343
  const emitModel = () => {
2167
2344
  modelValue.value = {
2168
- start_date: toISODate(startDate.value),
2169
- end_date: toISODate(endDate.value)
2345
+ start_date: startDate.value?.toString() ?? null,
2346
+ end_date: endDate.value?.toString() ?? null
2170
2347
  };
2171
2348
  };
2172
2349
  const handlePickerDate = (data) => {
2173
- if (data.start) startDate.value = data.start;
2350
+ if (data.start) startDate.value = readDay(data.start);
2174
2351
  if (data.end) {
2175
- endDate.value = data.end;
2352
+ endDate.value = readDay(data.end);
2176
2353
  ensureOrder();
2177
- showPicker.value = false;
2354
+ closePicker();
2178
2355
  }
2179
2356
  emitModel();
2180
2357
  };
2181
2358
  watch(() => modelValue.value, (newVal) => {
2182
- startDate.value = newVal.start_date ? new Date(newVal.start_date) : null;
2183
- 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);
2184
2361
  }, { deep: true });
2185
2362
  return (_ctx, _cache) => {
2186
- 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 }, [
2187
2368
  createElementVNode("input", {
2188
2369
  id: __props.uuid,
2370
+ ref: "box",
2189
2371
  class: "aform_input-field",
2190
2372
  type: "text",
2373
+ role: "combobox",
2374
+ "aria-haspopup": "dialog",
2375
+ "aria-expanded": unref(showPicker),
2376
+ "aria-controls": unref(showPicker) ? unref(calendarId) : void 0,
2191
2377
  value: rangeDisplay.value,
2192
2378
  placeholder: "Select date range",
2193
2379
  disabled: __props.mode === "read",
2194
2380
  readonly: "",
2195
2381
  "aria-invalid": unref(invalid),
2196
2382
  "aria-describedby": unref(describedBy),
2197
- onClick: openPicker
2198
- }, null, 8, _hoisted_4$6),
2383
+ onClick: openPicker,
2384
+ onKeydown: _cache[0] || (_cache[0] = (...args) => unref(openFromKey) && unref(openFromKey)(...args))
2385
+ }, null, 40, _hoisted_3$7),
2199
2386
  createElementVNode("label", {
2200
2387
  class: "aform_field-label",
2201
2388
  for: __props.uuid
2202
- }, toDisplayString(__props.label), 9, _hoisted_5$6),
2389
+ }, toDisplayString(__props.label), 9, _hoisted_4$6),
2203
2390
  withDirectives(createElementVNode("p", {
2204
2391
  id: unref(errorId),
2205
2392
  class: "aform_error",
2206
2393
  role: "alert"
2207
- }, toDisplayString(errorText.value), 9, _hoisted_6$6), [[vShow, errorText.value]]),
2208
- showPicker.value ? (openBlock(), createBlock(ADateSelection_default, {
2394
+ }, toDisplayString(errorText.value), 9, _hoisted_5$6), [[vShow, errorText.value]]),
2395
+ unref(showPicker) ? (openBlock(), createBlock(ADateSelection_default, {
2209
2396
  key: 0,
2210
- ref_key: "pickerRef",
2211
- ref: pickerRef,
2397
+ id: unref(calendarId),
2398
+ ref: "picker",
2212
2399
  class: "adaterange-picker",
2400
+ role: "dialog",
2401
+ "aria-label": __props.label,
2213
2402
  "select-range": true,
2214
2403
  "show-time": false,
2215
2404
  onGetDate: handlePickerDate
2216
- }, null, 512)) : createCommentVNode("", true)
2217
- ], 64))]);
2405
+ }, null, 8, ["id", "aria-label"])) : createCommentVNode("", true)
2406
+ ], 64))], 32);
2218
2407
  };
2219
2408
  }
2220
- }), [["__scopeId", "data-v-b7560709"]]);
2409
+ }), [["__scopeId", "data-v-ac2c160b"]]);
2221
2410
  //#endregion
2222
2411
  //#region src/components/AForm.vue?vue&type=script&setup=true&lang.ts
2223
2412
  var _hoisted_1$8 = { class: "aform" };
@@ -2540,18 +2729,22 @@ var ANumericInput_default = /* @__PURE__ */ defineComponent({
2540
2729
  const errorText = computed(() => __props.errors?.length ? __props.errors.join("; ") : __props.validation.errorMessage ?? "");
2541
2730
  const { errorId, describedBy, invalid } = fieldErrorA11y(__props.uuid, errorText);
2542
2731
  const inputNumber = useModel(__props, "modelValue");
2732
+ const boxNumber = computed({
2733
+ get: () => inputNumber.value,
2734
+ set: (value) => inputNumber.value = numberFromBox(value)
2735
+ });
2543
2736
  return (_ctx, _cache) => {
2544
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 }, [
2545
2738
  withDirectives(createElementVNode("input", {
2546
2739
  id: __props.uuid,
2547
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => inputNumber.value = $event),
2740
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => boxNumber.value = $event),
2548
2741
  class: "aform_input-field",
2549
2742
  type: "number",
2550
2743
  disabled: __props.mode === "read",
2551
2744
  required: __props.required,
2552
2745
  "aria-invalid": unref(invalid),
2553
2746
  "aria-describedby": unref(describedBy)
2554
- }, null, 8, _hoisted_4$4), [[vModelText, inputNumber.value]]),
2747
+ }, null, 8, _hoisted_4$4), [[vModelText, boxNumber.value]]),
2555
2748
  createElementVNode("label", {
2556
2749
  class: "aform_field-label",
2557
2750
  for: __props.uuid
@@ -2629,9 +2822,9 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2629
2822
  conversionFactorLabel: { default: "Conversion Factor" }
2630
2823
  }, {
2631
2824
  "modelValue": { default: () => ({
2632
- qty: 0,
2825
+ qty: null,
2633
2826
  uom: "",
2634
- stockQty: 0,
2827
+ stockQty: null,
2635
2828
  stockUom: "",
2636
2829
  conversionFactor: 1
2637
2830
  }) },
@@ -2645,11 +2838,11 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2645
2838
  const uoms = computed(() => __props.options.uoms ?? []);
2646
2839
  const roundQty = (value) => Number(value.toFixed(6));
2647
2840
  const resolveConversionFactor = (uom) => {
2648
- const stockUom = __props.options.stockUom ?? modelValue.value.stockUom;
2841
+ const stockUom = __props.options.stockUom ?? modelValue.value?.stockUom;
2649
2842
  if (!uom || uom === stockUom) return 1;
2650
2843
  const mapped = __props.options.conversionFactors?.[uom];
2651
2844
  if (mapped !== void 0) return mapped;
2652
- if (uom === modelValue.value.uom) return modelValue.value.conversionFactor ?? 1;
2845
+ if (uom === modelValue.value?.uom) return modelValue.value.conversionFactor ?? 1;
2653
2846
  return 1;
2654
2847
  };
2655
2848
  const recompute = (qty, uom) => {
@@ -2658,17 +2851,17 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2658
2851
  qty,
2659
2852
  uom,
2660
2853
  conversionFactor,
2661
- stockUom: __props.options.stockUom ?? modelValue.value.stockUom,
2662
- stockQty: roundQty(qty * conversionFactor)
2854
+ stockUom: __props.options.stockUom ?? modelValue.value?.stockUom ?? "",
2855
+ stockQty: qty === null ? null : roundQty(qty * conversionFactor)
2663
2856
  };
2664
2857
  };
2665
2858
  const qty = computed({
2666
- get: () => modelValue.value?.qty ?? 0,
2667
- set: (value) => recompute(value, modelValue.value?.uom ?? "")
2859
+ get: () => modelValue.value?.qty ?? null,
2860
+ set: (value) => recompute(numberFromBox(value), modelValue.value?.uom ?? "")
2668
2861
  });
2669
2862
  const uom = computed({
2670
2863
  get: () => modelValue.value?.uom ?? "",
2671
- set: (value) => recompute(modelValue.value?.qty ?? 0, value)
2864
+ set: (value) => recompute(modelValue.value?.qty ?? null, value)
2672
2865
  });
2673
2866
  const qtyNavigationKeys = /* @__PURE__ */ new Set([
2674
2867
  "Backspace",
@@ -2737,7 +2930,7 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2737
2930
  });
2738
2931
  const displayText = computed(() => {
2739
2932
  const v = modelValue.value;
2740
- if (!v || !v.uom) return "—";
2933
+ if (!v || !v.uom || v.qty === null) return "—";
2741
2934
  const base = `${v.qty} ${v.uom}`;
2742
2935
  return showStock.value ? `${base} (${v.stockQty} ${v.stockUom})` : base;
2743
2936
  });
@@ -2801,19 +2994,19 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2801
2994
  ])])]),
2802
2995
  createElementVNode("div", _hoisted_14, [
2803
2996
  createElementVNode("div", _hoisted_15, [createElementVNode("input", {
2804
- value: modelValue.value.stockUom,
2997
+ value: modelValue.value?.stockUom,
2805
2998
  class: "aform_input-field aquantity__stock-field",
2806
2999
  type: "text",
2807
3000
  disabled: ""
2808
3001
  }, null, 8, _hoisted_16), createElementVNode("label", _hoisted_17, toDisplayString(__props.stockUomLabel), 1)]),
2809
3002
  createElementVNode("div", _hoisted_18, [createElementVNode("input", {
2810
- value: modelValue.value.stockQty,
3003
+ value: modelValue.value?.stockQty,
2811
3004
  class: "aform_input-field aquantity__stock-field",
2812
3005
  type: "number",
2813
3006
  disabled: ""
2814
3007
  }, null, 8, _hoisted_19), createElementVNode("label", _hoisted_20, toDisplayString(__props.stockQtyLabel), 1)]),
2815
3008
  createElementVNode("div", _hoisted_21, [createElementVNode("input", {
2816
- value: modelValue.value.conversionFactor,
3009
+ value: modelValue.value?.conversionFactor,
2817
3010
  class: "aform_input-field aquantity__stock-field",
2818
3011
  type: "number",
2819
3012
  disabled: ""
@@ -2827,7 +3020,7 @@ var AQuantityInput_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/*
2827
3020
  ], 64))]);
2828
3021
  };
2829
3022
  }
2830
- }), [["__scopeId", "data-v-a904c817"]]);
3023
+ }), [["__scopeId", "data-v-b2c39a5c"]]);
2831
3024
  //#endregion
2832
3025
  //#region src/directives/mask.ts
2833
3026
  /**