@oods/components-vue 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
- import { defineComponent, h, ref, computed, useId, watch, nextTick, onMounted, onBeforeUnmount, isVNode, Text as Text$1, Comment, Fragment } from 'vue';
2
- import { svgCarriesTitle, assertStaticSvg, formatPriceAmount, formatPriceCode, formatReferenceLabel, summaryValue, formatDateTime, dateTimeInputValue, formatReadOnlyValue, BILLING_MINOR_UNITS, billingSummary, formatBillingAmount, billingAmountText, billingAmountMessage, parseBillingAmount, BILLING_INTERVALS, billingIntervalMessage, billingCycle, auditSummary, initialSort, ariaSort, nextSort, recipeItemLabels, DEFAULT_COLOR_STATES, geoFieldMapping, currencyMinorUnits, billingPaymentSummary, billingPaymentRows, traitEventRows, vizControlFields, VIZ_CONTROL_TITLES, vizFieldValue, vizSummaryRows, editVizIntent } from '@oods/component-contracts';
1
+ import { defineComponent, h, useId, computed, ref, onMounted, onBeforeUnmount, watch, nextTick, isVNode, Text as Text$1, Comment, Fragment } from 'vue';
2
+ import { vizPreviewLayers, svgCarriesTitle, formatPriceAmount, formatPriceCode, formatReferenceLabel, ownershipPhrase, summaryValue, formatDateTime, dateTimeInputValue, formatReadOnlyValue, BILLING_MINOR_UNITS, billingSummary, formatBillingAmount, billingAmountText, billingAmountMessage, parseBillingAmount, BILLING_INTERVALS, billingIntervalMessage, billingCycle, auditSummary, initialSort, ariaSort, nextSort, recipeItemLabels, DEFAULT_COLOR_STATES, geoFieldMapping, STATUS_ICON_SVG_ATTRS, STATUS_ICON_SHAPES, getStatusPresentation, resolveStatusIcon, currencyMinorUnits, billingPaymentSummary, billingPaymentRows, traitEventRows, vizControlFields, VIZ_CONTROL_TITLES, vizFieldValue, vizSummaryRows, editVizIntent } from '@oods/component-contracts';
3
+
4
+ // src/primitives.ts
5
+ function statusIcon(name) {
6
+ return h("svg", { ...STATUS_ICON_SVG_ATTRS }, STATUS_ICON_SHAPES[name].map((shape) => h(shape.tag, { ...shape.attrs })));
7
+ }
3
8
 
4
9
  // src/primitives.ts
5
10
  var toneProp = String;
@@ -142,7 +147,7 @@ var Banner = defineComponent({
142
147
  class: "oods-banner-dismiss oods-banner__dismiss",
143
148
  "aria-label": props.dismissLabel,
144
149
  onClick: () => emit("dismiss")
145
- }, [h("span", { "aria-hidden": "true" }, "\xD7")]) : null
150
+ }, [statusIcon("x")]) : null
146
151
  ]);
147
152
  };
148
153
  }
@@ -152,7 +157,7 @@ var Button = defineComponent({
152
157
  props: {
153
158
  content: { type: [String, Number], default: "" },
154
159
  intent: { type: String, default: "neutral" },
155
- size: { type: sizeProp, default: "md" },
160
+ size: { type: String, default: "md" },
156
161
  disabled: Boolean,
157
162
  type: { type: String, default: "button" }
158
163
  },
@@ -179,6 +184,130 @@ var Button = defineComponent({
179
184
  }, [slotOrValue(slots.default, props.content)]);
180
185
  }
181
186
  });
187
+ var canShowModal = () => typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
188
+ var pageLocks = 0;
189
+ var pageStyle;
190
+ function lockPageScroll() {
191
+ const page = document.documentElement;
192
+ if (pageLocks++ === 0) {
193
+ const scrollbar = window.innerWidth - page.clientWidth;
194
+ pageStyle = { overflow: page.style.overflow, paddingRight: page.style.paddingRight };
195
+ if (scrollbar > 0) page.style.paddingRight = `${parseFloat(getComputedStyle(page).paddingRight) + scrollbar}px`;
196
+ page.style.overflow = "hidden";
197
+ }
198
+ let held = true;
199
+ return () => {
200
+ if (!held) return;
201
+ held = false;
202
+ if (--pageLocks === 0 && pageStyle) {
203
+ page.style.overflow = pageStyle.overflow;
204
+ page.style.paddingRight = pageStyle.paddingRight;
205
+ pageStyle = void 0;
206
+ }
207
+ };
208
+ }
209
+ var outsideBox = (event, dialog) => {
210
+ const box = dialog.getBoundingClientRect();
211
+ return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
212
+ };
213
+ var Dialog = defineComponent({
214
+ name: "OodsDialog",
215
+ props: {
216
+ id: String,
217
+ open: Boolean,
218
+ modal: { type: Boolean, default: true },
219
+ title: { type: String, default: "" },
220
+ description: String,
221
+ dismissLabel: { type: String, default: "Close" },
222
+ size: { type: String, default: "md" },
223
+ actions: { type: [String, Number, Object, Array], default: void 0 }
224
+ },
225
+ emits: {
226
+ close: () => true
227
+ },
228
+ setup(props, { emit, slots }) {
229
+ const generatedId = useId().replace(/[^\w-]/g, "");
230
+ const dialogId = computed(() => props.id || `oods-dialog-${generatedId}`);
231
+ const element = ref(null);
232
+ const mounted = ref(false);
233
+ const managed = computed(() => props.modal && mounted.value && canShowModal());
234
+ let unlockScroll;
235
+ let pressedBackdrop = false;
236
+ const releaseScroll = () => {
237
+ unlockScroll?.();
238
+ unlockScroll = void 0;
239
+ };
240
+ const sync = () => {
241
+ const dialog = element.value;
242
+ if (!dialog || !managed.value) return;
243
+ if (props.open && !dialog.open) {
244
+ dialog.showModal();
245
+ unlockScroll ??= lockPageScroll();
246
+ } else if (!props.open && dialog.open) dialog.close();
247
+ };
248
+ onMounted(() => {
249
+ mounted.value = true;
250
+ });
251
+ onBeforeUnmount(releaseScroll);
252
+ watch([() => props.open, managed], sync, { flush: "post" });
253
+ const requestClose = (event) => {
254
+ event.preventDefault();
255
+ emit("close");
256
+ };
257
+ return () => {
258
+ const titleId = `${dialogId.value}-title`;
259
+ const descriptionId = `${dialogId.value}-description`;
260
+ const actions = slotOrValue(slots.actions, props.actions);
261
+ return h("dialog", {
262
+ ref: element,
263
+ id: dialogId.value,
264
+ class: "oods-dialog",
265
+ "data-oods-component": "Dialog",
266
+ "data-size": props.size,
267
+ "aria-labelledby": titleId,
268
+ "aria-describedby": props.description ? descriptionId : void 0,
269
+ open: managed.value ? void 0 : props.open,
270
+ onKeydown: (event) => {
271
+ if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
272
+ },
273
+ onCancel: requestClose,
274
+ // A close event from moving a mounted dialog into the top layer finds it open again, and is not reported. Any other
275
+ // close releases the page; when the browser closed it (a close request it would not let be cancelled) while open is
276
+ // still true, it is reported.
277
+ onClose: () => {
278
+ if (element.value?.open) return;
279
+ releaseScroll();
280
+ if (props.open) emit("close");
281
+ },
282
+ onMousedown: (event) => {
283
+ pressedBackdrop = managed.value && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
284
+ },
285
+ // s223-m02 (#2527 ruling 13b): a click on the backdrop emits close, as the close control does. A press that began
286
+ // inside the dialog (text selected and dragged out) is not a backdrop click.
287
+ onClick: (event) => {
288
+ const backdrop = pressedBackdrop && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
289
+ pressedBackdrop = false;
290
+ if (backdrop) emit("close");
291
+ }
292
+ }, [
293
+ h("div", { class: "oods-dialog__header" }, [
294
+ h("div", { class: "oods-dialog__heading" }, [
295
+ h("h2", { id: titleId, class: "oods-dialog__title" }, props.title),
296
+ props.description ? h("p", { id: descriptionId, class: "oods-dialog__description" }, props.description) : null
297
+ ]),
298
+ h("button", {
299
+ type: "button",
300
+ class: "oods-dialog__close",
301
+ "aria-label": props.dismissLabel,
302
+ onClick: () => emit("close")
303
+ }, [statusIcon("x")])
304
+ ]),
305
+ slots.default ? h("div", { class: "oods-dialog__body" }, slots.default()) : null,
306
+ actions !== void 0 && actions !== null && actions !== "" ? h("div", { class: "oods-dialog__footer" }, [actions]) : null
307
+ ]);
308
+ };
309
+ }
310
+ });
182
311
  var SAFE_CARD_ELEMENTS = /* @__PURE__ */ new Set(["div", "article", "section", "aside"]);
183
312
  var Card = defineComponent({
184
313
  name: "OodsCard",
@@ -458,6 +587,13 @@ var VizAreaPreview = defineComponent({
458
587
  svgNarrow: String,
459
588
  /** The same chart rendered at the wide size; the figure shows it instead of svg when its own inline size is at least 900px. */
460
589
  svgWide: String,
590
+ /** s222-m02 (F7): the dark and high-contrast renders; with any of them, svg* are the light ones (see React's types). */
591
+ svgDark: String,
592
+ svgDarkNarrow: String,
593
+ svgDarkWide: String,
594
+ svgHc: String,
595
+ svgHcNarrow: String,
596
+ svgHcWide: String,
461
597
  title: String,
462
598
  description: String,
463
599
  width: { type: Number, default: 640 },
@@ -466,6 +602,7 @@ var VizAreaPreview = defineComponent({
466
602
  setup(props, { slots }) {
467
603
  return () => {
468
604
  const content = authoredContent(slots.default?.());
605
+ const renders = (layer) => layer.renders.map(([attribute, markup]) => h("div", { [attribute]: "true", innerHTML: markup }));
469
606
  if (props.svg !== void 0) return h("figure", {
470
607
  class: "oods-viz-area-preview",
471
608
  "data-oods-component": "VizAreaPreview",
@@ -480,9 +617,11 @@ var VizAreaPreview = defineComponent({
480
617
  style: { "--oods-viz-width": `${props.width}px` }
481
618
  }, [
482
619
  ...props.title && !svgCarriesTitle(props.svg, props.title) ? [h("figcaption", props.title)] : [],
483
- h("div", { "data-viz-svg": "true", innerHTML: assertStaticSvg(props.svg) }),
484
- ...props.svgNarrow !== void 0 ? [h("div", { "data-viz-svg-narrow": "true", innerHTML: assertStaticSvg(props.svgNarrow) })] : [],
485
- ...props.svgWide !== void 0 ? [h("div", { "data-viz-svg-wide": "true", innerHTML: assertStaticSvg(props.svgWide) })] : [],
620
+ ...vizPreviewLayers(props).flatMap((layer) => layer.theme === void 0 ? renders(layer) : [h("div", {
621
+ "data-viz-theme": layer.theme,
622
+ ...layer.narrow ? { "data-viz-narrow": "true" } : {},
623
+ ...layer.wide ? { "data-viz-wide": "true" } : {}
624
+ }, renders(layer))]),
486
625
  ...props.description ? [h("p", { "data-viz-description": "true" }, props.description)] : []
487
626
  ]);
488
627
  return h("div", {
@@ -711,12 +850,15 @@ function createBadgeFamily(options) {
711
850
  };
712
851
  for (const key of options.extraProps) props[key] = String;
713
852
  for (const key of options.booleanProps ?? []) props[key] = { type: [String, Boolean], default: void 0 };
853
+ if (options.booleanProps) props.hideWhenFalse = { type: [Boolean, String], default: void 0 };
714
854
  return defineComponent({
715
855
  name: `Oods${options.component}`,
716
856
  props,
717
857
  setup(componentProps, { slots }) {
718
858
  return () => {
719
859
  const record = componentProps;
860
+ const flag = options.booleanProps?.map((key) => record[key]).find((value) => typeof value === "boolean");
861
+ if (record.hideWhenFalse === true && flag === false) return null;
720
862
  const content = authoredContent(slots.default?.());
721
863
  const scalar = scalarContent(content);
722
864
  const read = (key) => typeof record[key] === "boolean" && options.booleanProps?.includes(key) ? String(record[key]) : record[key];
@@ -1343,12 +1485,13 @@ var TagSummary = defineComponent({
1343
1485
  });
1344
1486
  var OwnershipMeta = defineComponent({
1345
1487
  name: "OodsOwnershipMeta",
1346
- props: { title: String, label: String, heading: String, name: String, ownerType: String, owner_type: String, role: String, ownershipRole: String },
1488
+ props: { title: String, label: String, heading: String, name: String, ownerType: String, owner_type: String, role: String, ownershipRole: String, ownerLabel: String },
1347
1489
  setup(props, { slots }) {
1348
1490
  return () => {
1349
1491
  const content = authoredContent(slots.default?.());
1350
1492
  const terms = [["Owner Type", firstScalar(props.ownerType, props.owner_type)], ["Role", firstScalar(props.role, props.ownershipRole)]].filter((entry) => entry[1] !== void 0);
1351
- return h("div", { class: "oods-ownership-meta", "data-oods-component": "OwnershipMeta", "data-meta-type": "ownership" }, content.length ? content : [
1493
+ const phrase = props.ownerLabel !== void 0 ? ownershipPhrase(props.ownerLabel, firstScalar(props.ownerType, props.owner_type), firstScalar(props.role, props.ownershipRole)) : void 0;
1494
+ return h("div", { class: "oods-ownership-meta", "data-oods-component": "OwnershipMeta", "data-meta-type": "ownership" }, content.length ? content : phrase ? [h("span", { "data-meta-item": "true" }, phrase)] : [
1352
1495
  h("span", { "data-meta-title": "true" }, firstText(props.title, props.label, props.heading, props.name) ?? "Ownership"),
1353
1496
  ...terms.map(([term, value]) => h("span", { key: term, "data-meta-item": "true" }, [h("strong", `${term}:`), ` ${value}`]))
1354
1497
  ]);
@@ -1725,6 +1868,75 @@ var Checkbox = defineComponent({
1725
1868
  };
1726
1869
  }
1727
1870
  });
1871
+ var Switch = defineComponent({
1872
+ name: "OodsSwitch",
1873
+ props: {
1874
+ id: String,
1875
+ label: String,
1876
+ modelValue: { type: Boolean, default: void 0 },
1877
+ checked: { type: Boolean, default: void 0 },
1878
+ defaultChecked: Boolean,
1879
+ required: Boolean,
1880
+ disabled: Boolean,
1881
+ help: String,
1882
+ validation: Object,
1883
+ size: { type: String, default: "md" }
1884
+ },
1885
+ emits: {
1886
+ "update:modelValue": (_value) => true,
1887
+ change: (_value) => true
1888
+ },
1889
+ setup(props, { emit, slots }) {
1890
+ const internalChecked = ref(props.defaultChecked);
1891
+ const currentChecked = computed(() => props.modelValue ?? props.checked ?? internalChecked.value);
1892
+ const metadata = useFieldMetadata("Switch", props, slots);
1893
+ const toggle = () => {
1894
+ const next = !currentChecked.value;
1895
+ if (props.modelValue === void 0 && props.checked === void 0) internalChecked.value = next;
1896
+ emit("update:modelValue", next);
1897
+ emit("change", next);
1898
+ };
1899
+ return () => {
1900
+ const label = slots.label?.() ?? props.label;
1901
+ const help = slots.help?.() ?? props.help;
1902
+ const validation = slots.validation?.() ?? props.validation?.message;
1903
+ const controlId = metadata.controlId.value;
1904
+ return h("div", {
1905
+ class: "oods-field oods-switch",
1906
+ style: validationStyle(props.validation),
1907
+ "data-oods-component": "Switch",
1908
+ "data-size": props.size,
1909
+ "data-validation-state": props.validation?.state
1910
+ }, [
1911
+ h("div", { class: "oods-switch__row" }, [
1912
+ h("button", {
1913
+ id: controlId,
1914
+ type: "button",
1915
+ role: "switch",
1916
+ class: "oods-switch__control",
1917
+ "aria-checked": currentChecked.value ? "true" : "false",
1918
+ "aria-labelledby": `${controlId}-label`,
1919
+ "aria-describedby": metadata.describedBy.value,
1920
+ "aria-required": props.required ? "true" : void 0,
1921
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
1922
+ disabled: props.disabled,
1923
+ onClick: toggle
1924
+ }, [h("span", { class: "oods-switch__thumb", "aria-hidden": "true" })]),
1925
+ h("label", { id: `${controlId}-label`, class: "oods-field-label oods-switch__label", for: controlId }, [
1926
+ label,
1927
+ props.required ? h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
1928
+ ])
1929
+ ]),
1930
+ help ? h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
1931
+ validation ? h("p", {
1932
+ id: metadata.validationId.value,
1933
+ class: "oods-field-error",
1934
+ role: props.validation?.state === "error" ? "alert" : void 0
1935
+ }, validation) : null
1936
+ ]);
1937
+ };
1938
+ }
1939
+ });
1728
1940
  var TableCaption = defineComponent({
1729
1941
  name: "OodsTableCaption",
1730
1942
  setup(_props, { slots }) {
@@ -1769,6 +1981,8 @@ var Table = defineComponent({
1769
1981
  inheritAttrs: false,
1770
1982
  props: {
1771
1983
  caption: { type: String, default: "" },
1984
+ // s222-m02 (#2502 ruling 11): false keeps the caption as the table's accessible name but hides it visually.
1985
+ showCaption: { type: Boolean, default: true },
1772
1986
  columns: { type: Array, default: () => [] },
1773
1987
  rows: { type: Array, default: () => [] },
1774
1988
  density: { type: String, default: "default" },
@@ -1785,7 +1999,7 @@ var Table = defineComponent({
1785
1999
  "data-oods-component": "Table",
1786
2000
  "data-density": props.density
1787
2001
  }, [
1788
- slots.caption || props.caption ? h(TableCaption, {}, {
2002
+ slots.caption || props.caption ? h(TableCaption, { "data-visually-hidden": props.showCaption ? void 0 : "true" }, {
1789
2003
  default: () => slots.caption?.() ?? props.caption
1790
2004
  }) : null,
1791
2005
  h(TableHead, {}, {
@@ -2003,82 +2217,63 @@ var Tabs = defineComponent({
2003
2217
  ]);
2004
2218
  }
2005
2219
  });
2006
-
2007
- // src/ported-core.ts
2008
- var STATUS_METADATA = {
2009
- subscription: {
2010
- future: {
2011
- description: "Subscription is scheduled but not yet active.",
2012
- tone: "info",
2013
- icon: "\u23F2"
2014
- },
2015
- trialing: {
2016
- description: "Subscription is in a trial period before billing begins.",
2017
- tone: "accent",
2018
- icon: "\u2605"
2019
- },
2020
- active: {
2021
- description: "Subscription is paid and service is provisioned.",
2022
- tone: "success",
2023
- icon: "\u2714\uFE0E"
2024
- },
2025
- paused: {
2026
- description: "Billing paused temporarily by finance or customer request.",
2027
- tone: "neutral",
2028
- icon: "\u23F8"
2029
- },
2030
- pending_cancellation: {
2031
- description: "Subscription will cancel at period end but remains active now.",
2032
- tone: "info",
2033
- icon: "\u2026"
2034
- },
2035
- past_due: {
2036
- description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
2037
- tone: "critical",
2038
- icon: "\u26A0\uFE0E"
2039
- },
2040
- unpaid: {
2041
- description: "Payment retries exhausted; service access revoked.",
2042
- tone: "critical",
2043
- icon: "\u2A2F"
2044
- },
2045
- terminated: {
2046
- description: "Subscription ended; service access revoked and billing stopped.",
2047
- tone: "neutral",
2048
- icon: "\u2205"
2049
- }
2220
+ var SegmentedControl = defineComponent({
2221
+ name: "OodsSegmentedControl",
2222
+ props: {
2223
+ id: String,
2224
+ label: String,
2225
+ modelValue: String,
2226
+ value: String,
2227
+ defaultValue: { type: String, default: "" },
2228
+ name: String,
2229
+ size: { type: String, default: "md" },
2230
+ disabled: Boolean,
2231
+ options: { type: Array, default: () => [] }
2050
2232
  },
2051
- invoice: {
2052
- draft: {
2053
- description: "Invoice is editable and not yet finalized.",
2054
- tone: "neutral",
2055
- icon: "\u270E"
2056
- },
2057
- posted: {
2058
- description: "Invoice finalized with terms before collection.",
2059
- tone: "info",
2060
- icon: "\u2026"
2061
- },
2062
- paid: {
2063
- description: "Invoice collected successfully in full.",
2064
- tone: "success",
2065
- icon: "\u2714\uFE0E"
2066
- },
2067
- past_due: {
2068
- description: "Invoice is unpaid past its due date.",
2069
- tone: "critical",
2070
- icon: "\u2A2F"
2071
- },
2072
- void: {
2073
- description: "Invoice canceled and no longer payable.",
2074
- tone: "neutral",
2075
- icon: "\u2205"
2076
- }
2233
+ emits: {
2234
+ "update:modelValue": (_value) => true,
2235
+ change: (_value) => true
2236
+ },
2237
+ setup(props, { emit, slots }) {
2238
+ const generatedId = useId();
2239
+ const internalValue = ref(props.defaultValue);
2240
+ const currentValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
2241
+ const select = (event) => {
2242
+ const next = event.target.value;
2243
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = next;
2244
+ emit("update:modelValue", next);
2245
+ emit("change", next);
2246
+ };
2247
+ return () => {
2248
+ const id = props.id || `oods-segmentedcontrol-${generatedId}`;
2249
+ const label = slots.label?.() ?? props.label;
2250
+ return h("div", {
2251
+ class: "oods-field oods-segmented-control",
2252
+ "data-oods-component": "SegmentedControl",
2253
+ "data-size": props.size
2254
+ }, [
2255
+ h("span", { id: `${id}-label`, class: "oods-field-label" }, [label]),
2256
+ h("div", {
2257
+ id,
2258
+ role: "radiogroup",
2259
+ "aria-labelledby": `${id}-label`,
2260
+ class: "oods-segmented-control__track"
2261
+ }, props.options.map((option) => h("label", { key: option.value, class: "oods-segmented-control__option" }, [
2262
+ h("input", {
2263
+ type: "radio",
2264
+ class: "oods-segmented-control__input",
2265
+ name: props.name || id,
2266
+ value: option.value,
2267
+ checked: option.value === currentValue.value,
2268
+ disabled: props.disabled || option.disabled,
2269
+ onChange: select
2270
+ }),
2271
+ h("span", { class: "oods-segmented-control__label" }, option.label)
2272
+ ])))
2273
+ ]);
2274
+ };
2077
2275
  }
2078
- };
2079
- var STATUS_ALIASES = {
2080
- subscription: { delinquent: "unpaid" }
2081
- };
2276
+ });
2082
2277
  function isRecord2(value) {
2083
2278
  return typeof value === "object" && value !== null && !Array.isArray(value);
2084
2279
  }
@@ -2095,18 +2290,13 @@ function statusLabel2(status) {
2095
2290
  return status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
2096
2291
  }
2097
2292
  function getPortedStatusPresentation(domain, status) {
2098
- const normalizedDomain = domain.toLowerCase();
2099
- const normalizedStatus = status.toLowerCase();
2100
- const alias = STATUS_ALIASES[normalizedDomain]?.[normalizedStatus];
2101
- const metadata = STATUS_METADATA[normalizedDomain]?.[alias ?? normalizedStatus];
2102
- if (!metadata) {
2103
- return {
2104
- label: statusLabel2(status || "Unknown"),
2105
- description: "Status not found in registry; falling back to the requested token set.",
2106
- tone: "neutral"
2107
- };
2108
- }
2109
- return { ...metadata, label: statusLabel2(status) };
2293
+ const presentation = getStatusPresentation(domain.toLowerCase(), status);
2294
+ return {
2295
+ label: statusLabel2(status || "Unknown"),
2296
+ description: presentation.description,
2297
+ tone: presentation.tone,
2298
+ icon: resolveStatusIcon(presentation.iconName)
2299
+ };
2110
2300
  }
2111
2301
  function formatPrice(amountCents, amount, currency) {
2112
2302
  const normalizedAmount = amount;
@@ -2250,11 +2440,10 @@ var StatusBadge = defineComponent({
2250
2440
  domain: props.domain,
2251
2441
  tone,
2252
2442
  emphasis: props.variant === "solid" ? "solid" : props.emphasis,
2253
- icon: props.showIcon ? presentation.icon : void 0,
2254
2443
  title: stringValue(attrs.title) ?? presentation.description,
2255
2444
  "aria-label": ariaLabel,
2256
2445
  "data-oods-component": "StatusBadge"
2257
- }, slots);
2446
+ }, props.showIcon && presentation.icon && !slots.icon ? { ...slots, icon: () => statusIcon(presentation.icon) } : slots);
2258
2447
  };
2259
2448
  }
2260
2449
  });
@@ -2567,7 +2756,7 @@ var SearchInput = defineComponent({
2567
2756
  "aria-label": "Clear search",
2568
2757
  disabled: props.disabled,
2569
2758
  onClick: clear
2570
- }, "\xD7") : null
2759
+ }, [statusIcon("x")]) : null
2571
2760
  ]);
2572
2761
  };
2573
2762
  }
@@ -2714,17 +2903,18 @@ var RelativeTimestamp = defineComponent({
2714
2903
  props.createdAt
2715
2904
  ) ?? "";
2716
2905
  if (props.hideWhenEmpty && (!datetime || Number.isNaN(Date.parse(datetime)))) return null;
2906
+ const absolute = formatDateTime(datetime, { timeZone: props.timezone });
2717
2907
  const content = firstDefined(
2718
2908
  props.relative,
2719
2909
  props.label,
2720
2910
  props.text,
2721
- props.now === void 0 ? formatDateTime(datetime, { timeZone: props.timezone }) : relativeTimestampLabel(datetime, props.now)
2911
+ !absolute ? "Unknown time" : props.now === void 0 ? absolute : relativeTimestampLabel(datetime, props.now)
2722
2912
  ) ?? "";
2723
2913
  return h("time", {
2724
2914
  ...attrs,
2725
2915
  class: mergedClass("oods-relative-timestamp", attrs.class),
2726
2916
  datetime: datetime || void 0,
2727
- title: stringValue(attrs.title) ?? formatDateTime(datetime, { timeZone: props.timezone }),
2917
+ title: stringValue(attrs.title) ?? absolute,
2728
2918
  "data-oods-component": "RelativeTimestamp"
2729
2919
  }, content);
2730
2920
  };
@@ -2901,7 +3091,8 @@ var array = Array;
2901
3091
  function eventRecipe(id, kind, title) {
2902
3092
  return defineComponent({ name: id, props: { id: String, title: { type: String, default: title }, archivedAt: String, restoredAt: String, archivedBy: String, reason: String, timestamp: String, code: String, history: array, status: String, showActor: { type: Boolean, default: true }, showReason: { type: Boolean, default: true } }, setup: (props) => () => {
2903
3093
  const rows = traitEventRows(kind, props);
2904
- return h("article", { id: props.id, class: "oods-trait-recipe", "data-oods-component": id, "aria-label": props.title }, [h("h2", props.title), rows.length ? h("ol", rows.map((row, index) => h("li", { key: index }, [h("strong", row.title), h("p", row.at ? h("time", { datetime: row.at }, row.time) : row.time), row.actor && h("p", `Actor: ${row.actor}`), row.reason && h("p", `Reason: ${row.reason}`), row.code && h("p", `Code: ${row.code}`)]))) : h("p", "No events recorded.")]);
3094
+ if (!rows.length) return null;
3095
+ return h("article", { id: props.id, class: "oods-trait-recipe", "data-oods-component": id, "aria-label": props.title }, [h("h2", props.title), h("ol", rows.map((row, index) => h("li", { key: index }, [h("strong", row.title), h("p", row.at ? h("time", { datetime: row.at }, row.time) : row.time), row.actor && h("p", `Actor: ${row.actor}`), row.reason && h("p", `Reason: ${row.reason}`), row.code && h("p", `Code: ${row.code}`)])))]);
2905
3096
  } });
2906
3097
  }
2907
3098
  var ArchiveEvent = eventRecipe("ArchiveEvent", "archive", "Archive events");
@@ -2972,7 +3163,7 @@ function summary(id) {
2972
3163
  } });
2973
3164
  }
2974
3165
  function preview(id, type) {
2975
- return defineComponent({ name: id, props: { svg: String, svgNarrow: String, svgWide: String, title: String, description: String, width: Number, height: Number }, setup(props, { slots }) {
3166
+ return defineComponent({ name: id, props: { svg: String, svgNarrow: String, svgWide: String, svgDark: String, svgDarkNarrow: String, svgDarkWide: String, svgHc: String, svgHcNarrow: String, svgHcWide: String, title: String, description: String, width: Number, height: Number }, setup(props, { slots }) {
2976
3167
  return () => h(VizAreaPreview, { ...props, title: props.title ?? `${type[0].toUpperCase()}${type.slice(1)} chart`, "data-oods-component": id, "data-viz-preview-type": type }, { default: slots.default ?? (() => h(Text, { content: "No rendered chart supplied" })) });
2977
3168
  } });
2978
3169
  }
@@ -3002,5 +3193,210 @@ var VizLinePreview = preview("VizLinePreview", "line");
3002
3193
  var VizMarkPreview = preview("VizMarkPreview", "bar");
3003
3194
  var VizPointPreview = preview("VizPointPreview", "scatter");
3004
3195
  var VizScatterPreview = preview("VizScatterPreview", "scatter");
3196
+ var Combobox = defineComponent({
3197
+ name: "OodsCombobox",
3198
+ props: {
3199
+ id: String,
3200
+ label: String,
3201
+ options: { type: Array, default: () => [] },
3202
+ modelValue: String,
3203
+ value: String,
3204
+ defaultValue: { type: String, default: "" },
3205
+ placeholder: String,
3206
+ name: String,
3207
+ required: Boolean,
3208
+ disabled: Boolean,
3209
+ help: String,
3210
+ validation: Object,
3211
+ /** The control height, as Button, Input and Select: xs 24, sm 28, md 32 (default) or lg 40. */
3212
+ size: { type: String, default: "md" }
3213
+ },
3214
+ emits: {
3215
+ "update:modelValue": (_value) => true,
3216
+ change: (_value) => true
3217
+ },
3218
+ setup(props, { emit, slots }) {
3219
+ const generatedId = useId();
3220
+ const controlId = computed(() => props.id || `oods-combobox-${generatedId}`);
3221
+ const internalValue = ref(props.defaultValue);
3222
+ const query = ref(null);
3223
+ const open = ref(false);
3224
+ const active = ref(-1);
3225
+ const root = ref(null);
3226
+ const listbox = ref(null);
3227
+ const chosenValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
3228
+ const chosen = computed(() => props.options.find((option) => option.value === chosenValue.value));
3229
+ const text = computed(() => query.value ?? chosen.value?.label ?? "");
3230
+ const matches = computed(() => {
3231
+ const needle = (query.value ?? "").trim().toLowerCase();
3232
+ return props.options.map((option) => needle === "" || option.label.toLowerCase().includes(needle));
3233
+ });
3234
+ const reachable = computed(() => props.options.flatMap((option, index) => matches.value[index] && !option.disabled ? [index] : []));
3235
+ const optionId = (index) => `${controlId.value}-option-${index}`;
3236
+ const close = () => {
3237
+ open.value = false;
3238
+ active.value = -1;
3239
+ };
3240
+ const commit = (next) => {
3241
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = next;
3242
+ emit("update:modelValue", next);
3243
+ emit("change", next);
3244
+ };
3245
+ const pick = (index) => {
3246
+ const option = props.options[index];
3247
+ if (!option || option.disabled) return;
3248
+ query.value = null;
3249
+ close();
3250
+ if (option.value !== chosenValue.value) commit(option.value);
3251
+ };
3252
+ const outside = (event) => {
3253
+ if (!root.value?.contains(event.target)) close();
3254
+ };
3255
+ watch(open, (isOpen) => {
3256
+ if (isOpen) document.addEventListener("pointerdown", outside);
3257
+ else document.removeEventListener("pointerdown", outside);
3258
+ });
3259
+ onBeforeUnmount(() => {
3260
+ if (typeof document !== "undefined") document.removeEventListener("pointerdown", outside);
3261
+ });
3262
+ watch([open, active], async () => {
3263
+ if (!open.value || active.value < 0) return;
3264
+ await nextTick();
3265
+ const element = listbox.value?.children[active.value];
3266
+ element?.scrollIntoView?.({ block: "nearest" });
3267
+ });
3268
+ const keydown = (event) => {
3269
+ if (event.defaultPrevented || props.disabled || event.isComposing) return;
3270
+ const order = reachable.value;
3271
+ const first = order[0] ?? -1;
3272
+ const last = order[order.length - 1] ?? -1;
3273
+ const at = order.indexOf(active.value);
3274
+ switch (event.key) {
3275
+ case "ArrowDown":
3276
+ event.preventDefault();
3277
+ active.value = !open.value || at === -1 ? first : order[(at + 1) % order.length];
3278
+ open.value = true;
3279
+ return;
3280
+ case "ArrowUp":
3281
+ event.preventDefault();
3282
+ active.value = !open.value || at <= 0 ? last : order[at - 1];
3283
+ open.value = true;
3284
+ return;
3285
+ case "Home":
3286
+ case "End":
3287
+ if (!open.value) return;
3288
+ event.preventDefault();
3289
+ active.value = event.key === "Home" ? first : last;
3290
+ return;
3291
+ case "Enter":
3292
+ if (!open.value) return;
3293
+ event.preventDefault();
3294
+ if (active.value >= 0) pick(active.value);
3295
+ return;
3296
+ case "Escape":
3297
+ if (open.value) {
3298
+ event.preventDefault();
3299
+ close();
3300
+ return;
3301
+ }
3302
+ if (text.value === "" && chosenValue.value === "") return;
3303
+ event.preventDefault();
3304
+ query.value = null;
3305
+ if (chosenValue.value !== "") commit("");
3306
+ return;
3307
+ case "Tab":
3308
+ if (open.value) close();
3309
+ return;
3310
+ }
3311
+ };
3312
+ return () => {
3313
+ const id = controlId.value;
3314
+ const label = slots.label?.() ?? props.label;
3315
+ const help = slots.help?.() ?? props.help;
3316
+ const validation = slots.validation?.() ?? props.validation?.message;
3317
+ const tone = props.validation ? props.validation.state === "error" ? "critical" : props.validation.state : void 0;
3318
+ const describedBy = [help ? `${id}-help` : void 0, validation ? `${id}-validation` : void 0].filter(Boolean).join(" ") || void 0;
3319
+ const noMatches = open.value && !matches.value.some(Boolean);
3320
+ return h("div", {
3321
+ ref: root,
3322
+ class: "oods-field oods-combobox",
3323
+ ...tone ? { style: { "--cmp-input-message-border": `var(--sys-status-${tone}-border)`, "--cmp-input-message-text": `var(--sys-status-${tone}-text)` } } : {},
3324
+ "data-oods-component": "Combobox",
3325
+ "data-size": props.size,
3326
+ "data-validation-state": props.validation?.state
3327
+ }, [
3328
+ h("label", { id: `${id}-label`, class: "oods-field-label", for: id }, [
3329
+ label,
3330
+ props.required ? h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
3331
+ ]),
3332
+ h("div", { class: "oods-combobox__control" }, [
3333
+ h("input", {
3334
+ id,
3335
+ type: "text",
3336
+ role: "combobox",
3337
+ class: "oods-field-control oods-combobox__input",
3338
+ value: text.value,
3339
+ placeholder: props.placeholder,
3340
+ autocomplete: "off",
3341
+ "aria-autocomplete": "list",
3342
+ "aria-expanded": open.value ? "true" : "false",
3343
+ "aria-controls": `${id}-listbox`,
3344
+ "aria-activedescendant": open.value && active.value >= 0 ? optionId(active.value) : void 0,
3345
+ "aria-describedby": describedBy,
3346
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
3347
+ required: props.required,
3348
+ disabled: props.disabled,
3349
+ onInput: (event) => {
3350
+ query.value = event.target.value;
3351
+ open.value = true;
3352
+ active.value = -1;
3353
+ },
3354
+ onKeydown: keydown,
3355
+ onClick: () => {
3356
+ if (!props.disabled) open.value = true;
3357
+ },
3358
+ onBlur: () => {
3359
+ query.value = null;
3360
+ close();
3361
+ }
3362
+ }),
3363
+ h("span", { class: "oods-combobox__chevron", "aria-hidden": "true" }, [statusIcon("chevron-down")]),
3364
+ h("div", {
3365
+ class: "oods-combobox__popup",
3366
+ hidden: !open.value,
3367
+ onMousedown: (event) => event.preventDefault()
3368
+ }, [
3369
+ h(
3370
+ "ul",
3371
+ { ref: listbox, id: `${id}-listbox`, class: "oods-combobox__listbox", role: "listbox", "aria-labelledby": `${id}-label` },
3372
+ props.options.map((option, index) => h("li", {
3373
+ key: option.value,
3374
+ id: optionId(index),
3375
+ class: "oods-combobox__option",
3376
+ role: "option",
3377
+ "aria-selected": option.value === chosenValue.value ? "true" : "false",
3378
+ "aria-disabled": option.disabled ? "true" : void 0,
3379
+ "data-value": option.value,
3380
+ "data-active": index === active.value ? "true" : void 0,
3381
+ hidden: !matches.value[index],
3382
+ onClick: () => pick(index),
3383
+ onPointermove: () => {
3384
+ if (!option.disabled && index !== active.value) active.value = index;
3385
+ }
3386
+ }, [
3387
+ h("span", { class: "oods-combobox__option-label" }, option.label),
3388
+ h("span", { class: "oods-combobox__check", "aria-hidden": "true" }, [statusIcon("check")])
3389
+ ]))
3390
+ ),
3391
+ h("p", { class: "oods-combobox__empty", role: "status" }, noMatches ? "No matches" : "")
3392
+ ])
3393
+ ]),
3394
+ props.name ? h("input", { type: "hidden", name: props.name, value: chosenValue.value }) : null,
3395
+ help ? h("p", { id: `${id}-help`, class: "oods-field-help" }, help) : null,
3396
+ validation ? h("p", { id: `${id}-validation`, class: "oods-field-error", role: props.validation?.state === "error" ? "alert" : void 0 }, validation) : null
3397
+ ]);
3398
+ };
3399
+ }
3400
+ });
3005
3401
 
3006
- export { AddressCollectionPanel, AddressEditor, AddressSummaryBadge, AddressValidationTimeline, ArchiveEvent, ArchivePill, ArchiveSummary, ArchivedRowOverlay, AuditEvent, AuditSummaryCard, AuditTimeline, Badge, Banner, BillingAmountInput, BillingCardMeta, BillingIntervalSelector, BillingSummaryBadge, Button, CancellationBadge, CancellationEvent, CancellationForm, CancellationSummary, Card, CardHeader, Checkbox, ClassificationBadge, ClassificationEditor, ClassificationPanel, ColorStatePicker, ColorSwatch, ColorizedBadge, CommunicationDetailPanel, CycleProgressCard, DatePicker, DetailHeader, FilterPanel, FormLabelGroup, GeoFieldMappingForm, GeoResolutionBadge, GeocodablePreview, Grid, InlineLabel, Input, LabelCell, MembershipAuditTimeline, MembershipPanel, MessageEventTimeline, MessageStatusBadge, OwnerBadge, OwnershipMeta, OwnershipSummary, PaginationBar, PaymentEventTimeline, PaymentTimeline, PreferenceEditor, PreferencePanel, PreferenceSummaryBadge, PreferenceTimeline, PriceBadge, PriceCardMeta, PriceSummary, RelativeTimestamp, RoleAssignmentForm, RoleBadgeList, SearchInput, Select, SortIndicator, Stack, StateTransitionEvent, StatusBadge, StatusColorLegend, StatusSelector, StatusTimeline, Table, TableBody, TableCaption, TableCell, TableHead, TableHeaderCell, TableRow, Tabs, TagInput, TagManager, TagPills, TagSummary, TemplatePicker, Text, Textarea, TimelineEntryLabel, VizAreaControls, VizAreaPreview, VizAxisControls, VizAxisSummary, VizColorControls, VizColorLegendConfig, VizEncodingBadge, VizGraphPreview, VizHeatmapControls, VizHeatmapPreview, VizLineControls, VizLinePreview, VizMarkControls, VizMarkPreview, VizOpacityControls, VizOpacitySummary, VizPointControls, VizPointPreview, VizRoleBadge, VizScaleControls, VizScaleSummary, VizScatterControls, VizScatterPreview, VizShapeControls, VizShapeLegend, VizSizeControls, VizSizeSummary };
3402
+ export { AddressCollectionPanel, AddressEditor, AddressSummaryBadge, AddressValidationTimeline, ArchiveEvent, ArchivePill, ArchiveSummary, ArchivedRowOverlay, AuditEvent, AuditSummaryCard, AuditTimeline, Badge, Banner, BillingAmountInput, BillingCardMeta, BillingIntervalSelector, BillingSummaryBadge, Button, CancellationBadge, CancellationEvent, CancellationForm, CancellationSummary, Card, CardHeader, Checkbox, ClassificationBadge, ClassificationEditor, ClassificationPanel, ColorStatePicker, ColorSwatch, ColorizedBadge, Combobox, CommunicationDetailPanel, CycleProgressCard, DatePicker, DetailHeader, Dialog, FilterPanel, FormLabelGroup, GeoFieldMappingForm, GeoResolutionBadge, GeocodablePreview, Grid, InlineLabel, Input, LabelCell, MembershipAuditTimeline, MembershipPanel, MessageEventTimeline, MessageStatusBadge, OwnerBadge, OwnershipMeta, OwnershipSummary, PaginationBar, PaymentEventTimeline, PaymentTimeline, PreferenceEditor, PreferencePanel, PreferenceSummaryBadge, PreferenceTimeline, PriceBadge, PriceCardMeta, PriceSummary, RelativeTimestamp, RoleAssignmentForm, RoleBadgeList, SearchInput, SegmentedControl, Select, SortIndicator, Stack, StateTransitionEvent, StatusBadge, StatusColorLegend, StatusSelector, StatusTimeline, Switch, Table, TableBody, TableCaption, TableCell, TableHead, TableHeaderCell, TableRow, Tabs, TagInput, TagManager, TagPills, TagSummary, TemplatePicker, Text, Textarea, TimelineEntryLabel, VizAreaControls, VizAreaPreview, VizAxisControls, VizAxisSummary, VizColorControls, VizColorLegendConfig, VizEncodingBadge, VizGraphPreview, VizHeatmapControls, VizHeatmapPreview, VizLineControls, VizLinePreview, VizMarkControls, VizMarkPreview, VizOpacityControls, VizOpacitySummary, VizPointControls, VizPointPreview, VizRoleBadge, VizScaleControls, VizScaleSummary, VizScatterControls, VizScatterPreview, VizShapeControls, VizShapeLegend, VizSizeControls, VizSizeSummary };