@oods/components-vue 0.3.3 → 0.4.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.
package/README.md CHANGED
@@ -34,4 +34,4 @@ Use matching versions of the OODS packages. These packages are separate from the
34
34
 
35
35
  ## License
36
36
 
37
- Apache License 2.0. See [LICENSE](https://cdn.jsdelivr.net/npm/@oods/components-vue@0.3.3/LICENSE) and [NOTICE](https://cdn.jsdelivr.net/npm/@oods/components-vue@0.3.3/NOTICE). Generated output belongs to its user; installed package dependencies retain their own licenses.
37
+ Apache License 2.0. See [LICENSE](https://cdn.jsdelivr.net/npm/@oods/components-vue@0.4.0/LICENSE) and [NOTICE](https://cdn.jsdelivr.net/npm/@oods/components-vue@0.4.0/NOTICE). Generated output belongs to its user; installed package dependencies retain their own licenses.
package/dist/index.cjs CHANGED
@@ -3,6 +3,11 @@
3
3
  var vue = require('vue');
4
4
  var componentContracts = require('@oods/component-contracts');
5
5
 
6
+ // src/primitives.ts
7
+ function statusIcon(name) {
8
+ return vue.h("svg", { ...componentContracts.STATUS_ICON_SVG_ATTRS }, componentContracts.STATUS_ICON_SHAPES[name].map((shape) => vue.h(shape.tag, { ...shape.attrs })));
9
+ }
10
+
6
11
  // src/primitives.ts
7
12
  var toneProp = String;
8
13
  var emphasisProp = String;
@@ -144,7 +149,7 @@ var Banner = vue.defineComponent({
144
149
  class: "oods-banner-dismiss oods-banner__dismiss",
145
150
  "aria-label": props.dismissLabel,
146
151
  onClick: () => emit("dismiss")
147
- }, [vue.h("span", { "aria-hidden": "true" }, "\xD7")]) : null
152
+ }, [statusIcon("x")]) : null
148
153
  ]);
149
154
  };
150
155
  }
@@ -154,7 +159,7 @@ var Button = vue.defineComponent({
154
159
  props: {
155
160
  content: { type: [String, Number], default: "" },
156
161
  intent: { type: String, default: "neutral" },
157
- size: { type: sizeProp, default: "md" },
162
+ size: { type: String, default: "md" },
158
163
  disabled: Boolean,
159
164
  type: { type: String, default: "button" }
160
165
  },
@@ -181,6 +186,83 @@ var Button = vue.defineComponent({
181
186
  }, [slotOrValue(slots.default, props.content)]);
182
187
  }
183
188
  });
189
+ var canShowModal = () => typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
190
+ var Dialog = vue.defineComponent({
191
+ name: "OodsDialog",
192
+ props: {
193
+ id: String,
194
+ open: Boolean,
195
+ modal: { type: Boolean, default: true },
196
+ title: { type: String, default: "" },
197
+ description: String,
198
+ dismissLabel: { type: String, default: "Close" },
199
+ size: { type: String, default: "md" },
200
+ actions: { type: [String, Number, Object, Array], default: void 0 }
201
+ },
202
+ emits: {
203
+ close: () => true
204
+ },
205
+ setup(props, { emit, slots }) {
206
+ const generatedId = vue.useId().replace(/[^\w-]/g, "");
207
+ const dialogId = vue.computed(() => props.id || `oods-dialog-${generatedId}`);
208
+ const element = vue.ref(null);
209
+ const mounted = vue.ref(false);
210
+ const managed = vue.computed(() => props.modal && mounted.value && canShowModal());
211
+ const sync = () => {
212
+ const dialog = element.value;
213
+ if (!dialog || !managed.value) return;
214
+ if (props.open && !dialog.open) dialog.showModal();
215
+ else if (!props.open && dialog.open) dialog.close();
216
+ };
217
+ vue.onMounted(() => {
218
+ mounted.value = true;
219
+ });
220
+ vue.watch([() => props.open, managed], sync, { flush: "post" });
221
+ const requestClose = (event) => {
222
+ event.preventDefault();
223
+ emit("close");
224
+ };
225
+ return () => {
226
+ const titleId = `${dialogId.value}-title`;
227
+ const descriptionId = `${dialogId.value}-description`;
228
+ const actions = slotOrValue(slots.actions, props.actions);
229
+ return vue.h("dialog", {
230
+ ref: element,
231
+ id: dialogId.value,
232
+ class: "oods-dialog",
233
+ "data-oods-component": "Dialog",
234
+ "data-size": props.size,
235
+ "aria-labelledby": titleId,
236
+ "aria-describedby": props.description ? descriptionId : void 0,
237
+ open: managed.value ? void 0 : props.open,
238
+ onKeydown: (event) => {
239
+ if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
240
+ },
241
+ onCancel: requestClose,
242
+ // The browser closed it (a close request it would not let be cancelled) while open is still true. A close event
243
+ // from moving a mounted dialog into the top layer finds it open again, and is not reported.
244
+ onClose: () => {
245
+ if (props.open && !element.value?.open) emit("close");
246
+ }
247
+ }, [
248
+ vue.h("div", { class: "oods-dialog__header" }, [
249
+ vue.h("div", { class: "oods-dialog__heading" }, [
250
+ vue.h("h2", { id: titleId, class: "oods-dialog__title" }, props.title),
251
+ props.description ? vue.h("p", { id: descriptionId, class: "oods-dialog__description" }, props.description) : null
252
+ ]),
253
+ vue.h("button", {
254
+ type: "button",
255
+ class: "oods-dialog__close",
256
+ "aria-label": props.dismissLabel,
257
+ onClick: () => emit("close")
258
+ }, [statusIcon("x")])
259
+ ]),
260
+ slots.default ? vue.h("div", { class: "oods-dialog__body" }, slots.default()) : null,
261
+ actions !== void 0 && actions !== null && actions !== "" ? vue.h("div", { class: "oods-dialog__footer" }, [actions]) : null
262
+ ]);
263
+ };
264
+ }
265
+ });
184
266
  var SAFE_CARD_ELEMENTS = /* @__PURE__ */ new Set(["div", "article", "section", "aside"]);
185
267
  var Card = vue.defineComponent({
186
268
  name: "OodsCard",
@@ -460,6 +542,13 @@ var VizAreaPreview = vue.defineComponent({
460
542
  svgNarrow: String,
461
543
  /** The same chart rendered at the wide size; the figure shows it instead of svg when its own inline size is at least 900px. */
462
544
  svgWide: String,
545
+ /** s222-m02 (F7): the dark and high-contrast renders; with any of them, svg* are the light ones (see React's types). */
546
+ svgDark: String,
547
+ svgDarkNarrow: String,
548
+ svgDarkWide: String,
549
+ svgHc: String,
550
+ svgHcNarrow: String,
551
+ svgHcWide: String,
463
552
  title: String,
464
553
  description: String,
465
554
  width: { type: Number, default: 640 },
@@ -468,6 +557,7 @@ var VizAreaPreview = vue.defineComponent({
468
557
  setup(props, { slots }) {
469
558
  return () => {
470
559
  const content = authoredContent(slots.default?.());
560
+ const renders = (layer) => layer.renders.map(([attribute, markup]) => vue.h("div", { [attribute]: "true", innerHTML: markup }));
471
561
  if (props.svg !== void 0) return vue.h("figure", {
472
562
  class: "oods-viz-area-preview",
473
563
  "data-oods-component": "VizAreaPreview",
@@ -482,9 +572,11 @@ var VizAreaPreview = vue.defineComponent({
482
572
  style: { "--oods-viz-width": `${props.width}px` }
483
573
  }, [
484
574
  ...props.title && !componentContracts.svgCarriesTitle(props.svg, props.title) ? [vue.h("figcaption", props.title)] : [],
485
- vue.h("div", { "data-viz-svg": "true", innerHTML: componentContracts.assertStaticSvg(props.svg) }),
486
- ...props.svgNarrow !== void 0 ? [vue.h("div", { "data-viz-svg-narrow": "true", innerHTML: componentContracts.assertStaticSvg(props.svgNarrow) })] : [],
487
- ...props.svgWide !== void 0 ? [vue.h("div", { "data-viz-svg-wide": "true", innerHTML: componentContracts.assertStaticSvg(props.svgWide) })] : [],
575
+ ...componentContracts.vizPreviewLayers(props).flatMap((layer) => layer.theme === void 0 ? renders(layer) : [vue.h("div", {
576
+ "data-viz-theme": layer.theme,
577
+ ...layer.narrow ? { "data-viz-narrow": "true" } : {},
578
+ ...layer.wide ? { "data-viz-wide": "true" } : {}
579
+ }, renders(layer))]),
488
580
  ...props.description ? [vue.h("p", { "data-viz-description": "true" }, props.description)] : []
489
581
  ]);
490
582
  return vue.h("div", {
@@ -1727,6 +1819,75 @@ var Checkbox = vue.defineComponent({
1727
1819
  };
1728
1820
  }
1729
1821
  });
1822
+ var Switch = vue.defineComponent({
1823
+ name: "OodsSwitch",
1824
+ props: {
1825
+ id: String,
1826
+ label: String,
1827
+ modelValue: { type: Boolean, default: void 0 },
1828
+ checked: { type: Boolean, default: void 0 },
1829
+ defaultChecked: Boolean,
1830
+ required: Boolean,
1831
+ disabled: Boolean,
1832
+ help: String,
1833
+ validation: Object,
1834
+ size: { type: String, default: "md" }
1835
+ },
1836
+ emits: {
1837
+ "update:modelValue": (_value) => true,
1838
+ change: (_value) => true
1839
+ },
1840
+ setup(props, { emit, slots }) {
1841
+ const internalChecked = vue.ref(props.defaultChecked);
1842
+ const currentChecked = vue.computed(() => props.modelValue ?? props.checked ?? internalChecked.value);
1843
+ const metadata = useFieldMetadata("Switch", props, slots);
1844
+ const toggle = () => {
1845
+ const next = !currentChecked.value;
1846
+ if (props.modelValue === void 0 && props.checked === void 0) internalChecked.value = next;
1847
+ emit("update:modelValue", next);
1848
+ emit("change", next);
1849
+ };
1850
+ return () => {
1851
+ const label = slots.label?.() ?? props.label;
1852
+ const help = slots.help?.() ?? props.help;
1853
+ const validation = slots.validation?.() ?? props.validation?.message;
1854
+ const controlId = metadata.controlId.value;
1855
+ return vue.h("div", {
1856
+ class: "oods-field oods-switch",
1857
+ style: validationStyle(props.validation),
1858
+ "data-oods-component": "Switch",
1859
+ "data-size": props.size,
1860
+ "data-validation-state": props.validation?.state
1861
+ }, [
1862
+ vue.h("div", { class: "oods-switch__row" }, [
1863
+ vue.h("button", {
1864
+ id: controlId,
1865
+ type: "button",
1866
+ role: "switch",
1867
+ class: "oods-switch__control",
1868
+ "aria-checked": currentChecked.value ? "true" : "false",
1869
+ "aria-labelledby": `${controlId}-label`,
1870
+ "aria-describedby": metadata.describedBy.value,
1871
+ "aria-required": props.required ? "true" : void 0,
1872
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
1873
+ disabled: props.disabled,
1874
+ onClick: toggle
1875
+ }, [vue.h("span", { class: "oods-switch__thumb", "aria-hidden": "true" })]),
1876
+ vue.h("label", { id: `${controlId}-label`, class: "oods-field-label oods-switch__label", for: controlId }, [
1877
+ label,
1878
+ props.required ? vue.h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
1879
+ ])
1880
+ ]),
1881
+ help ? vue.h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
1882
+ validation ? vue.h("p", {
1883
+ id: metadata.validationId.value,
1884
+ class: "oods-field-error",
1885
+ role: props.validation?.state === "error" ? "alert" : void 0
1886
+ }, validation) : null
1887
+ ]);
1888
+ };
1889
+ }
1890
+ });
1730
1891
  var TableCaption = vue.defineComponent({
1731
1892
  name: "OodsTableCaption",
1732
1893
  setup(_props, { slots }) {
@@ -1771,6 +1932,8 @@ var Table = vue.defineComponent({
1771
1932
  inheritAttrs: false,
1772
1933
  props: {
1773
1934
  caption: { type: String, default: "" },
1935
+ // s222-m02 (#2502 ruling 11): false keeps the caption as the table's accessible name but hides it visually.
1936
+ showCaption: { type: Boolean, default: true },
1774
1937
  columns: { type: Array, default: () => [] },
1775
1938
  rows: { type: Array, default: () => [] },
1776
1939
  density: { type: String, default: "default" },
@@ -1787,7 +1950,7 @@ var Table = vue.defineComponent({
1787
1950
  "data-oods-component": "Table",
1788
1951
  "data-density": props.density
1789
1952
  }, [
1790
- slots.caption || props.caption ? vue.h(TableCaption, {}, {
1953
+ slots.caption || props.caption ? vue.h(TableCaption, { "data-visually-hidden": props.showCaption ? void 0 : "true" }, {
1791
1954
  default: () => slots.caption?.() ?? props.caption
1792
1955
  }) : null,
1793
1956
  vue.h(TableHead, {}, {
@@ -2005,82 +2168,6 @@ var Tabs = vue.defineComponent({
2005
2168
  ]);
2006
2169
  }
2007
2170
  });
2008
-
2009
- // src/ported-core.ts
2010
- var STATUS_METADATA = {
2011
- subscription: {
2012
- future: {
2013
- description: "Subscription is scheduled but not yet active.",
2014
- tone: "info",
2015
- icon: "\u23F2"
2016
- },
2017
- trialing: {
2018
- description: "Subscription is in a trial period before billing begins.",
2019
- tone: "accent",
2020
- icon: "\u2605"
2021
- },
2022
- active: {
2023
- description: "Subscription is paid and service is provisioned.",
2024
- tone: "success",
2025
- icon: "\u2714\uFE0E"
2026
- },
2027
- paused: {
2028
- description: "Billing paused temporarily by finance or customer request.",
2029
- tone: "neutral",
2030
- icon: "\u23F8"
2031
- },
2032
- pending_cancellation: {
2033
- description: "Subscription will cancel at period end but remains active now.",
2034
- tone: "info",
2035
- icon: "\u2026"
2036
- },
2037
- past_due: {
2038
- description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
2039
- tone: "critical",
2040
- icon: "\u26A0\uFE0E"
2041
- },
2042
- unpaid: {
2043
- description: "Payment retries exhausted; service access revoked.",
2044
- tone: "critical",
2045
- icon: "\u2A2F"
2046
- },
2047
- terminated: {
2048
- description: "Subscription ended; service access revoked and billing stopped.",
2049
- tone: "neutral",
2050
- icon: "\u2205"
2051
- }
2052
- },
2053
- invoice: {
2054
- draft: {
2055
- description: "Invoice is editable and not yet finalized.",
2056
- tone: "neutral",
2057
- icon: "\u270E"
2058
- },
2059
- posted: {
2060
- description: "Invoice finalized with terms before collection.",
2061
- tone: "info",
2062
- icon: "\u2026"
2063
- },
2064
- paid: {
2065
- description: "Invoice collected successfully in full.",
2066
- tone: "success",
2067
- icon: "\u2714\uFE0E"
2068
- },
2069
- past_due: {
2070
- description: "Invoice is unpaid past its due date.",
2071
- tone: "critical",
2072
- icon: "\u2A2F"
2073
- },
2074
- void: {
2075
- description: "Invoice canceled and no longer payable.",
2076
- tone: "neutral",
2077
- icon: "\u2205"
2078
- }
2079
- }
2080
- };
2081
- var STATUS_ALIASES = {
2082
- subscription: { delinquent: "unpaid" }
2083
- };
2084
2171
  function isRecord2(value) {
2085
2172
  return typeof value === "object" && value !== null && !Array.isArray(value);
2086
2173
  }
@@ -2097,18 +2184,13 @@ function statusLabel2(status) {
2097
2184
  return status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
2098
2185
  }
2099
2186
  function getPortedStatusPresentation(domain, status) {
2100
- const normalizedDomain = domain.toLowerCase();
2101
- const normalizedStatus = status.toLowerCase();
2102
- const alias = STATUS_ALIASES[normalizedDomain]?.[normalizedStatus];
2103
- const metadata = STATUS_METADATA[normalizedDomain]?.[alias ?? normalizedStatus];
2104
- if (!metadata) {
2105
- return {
2106
- label: statusLabel2(status || "Unknown"),
2107
- description: "Status not found in registry; falling back to the requested token set.",
2108
- tone: "neutral"
2109
- };
2110
- }
2111
- return { ...metadata, label: statusLabel2(status) };
2187
+ const presentation = componentContracts.getStatusPresentation(domain.toLowerCase(), status);
2188
+ return {
2189
+ label: statusLabel2(status || "Unknown"),
2190
+ description: presentation.description,
2191
+ tone: presentation.tone,
2192
+ icon: componentContracts.resolveStatusIcon(presentation.iconName)
2193
+ };
2112
2194
  }
2113
2195
  function formatPrice(amountCents, amount, currency) {
2114
2196
  const normalizedAmount = amount;
@@ -2252,11 +2334,10 @@ var StatusBadge = vue.defineComponent({
2252
2334
  domain: props.domain,
2253
2335
  tone,
2254
2336
  emphasis: props.variant === "solid" ? "solid" : props.emphasis,
2255
- icon: props.showIcon ? presentation.icon : void 0,
2256
2337
  title: stringValue(attrs.title) ?? presentation.description,
2257
2338
  "aria-label": ariaLabel,
2258
2339
  "data-oods-component": "StatusBadge"
2259
- }, slots);
2340
+ }, props.showIcon && presentation.icon && !slots.icon ? { ...slots, icon: () => statusIcon(presentation.icon) } : slots);
2260
2341
  };
2261
2342
  }
2262
2343
  });
@@ -2569,7 +2650,7 @@ var SearchInput = vue.defineComponent({
2569
2650
  "aria-label": "Clear search",
2570
2651
  disabled: props.disabled,
2571
2652
  onClick: clear
2572
- }, "\xD7") : null
2653
+ }, [statusIcon("x")]) : null
2573
2654
  ]);
2574
2655
  };
2575
2656
  }
@@ -2716,17 +2797,18 @@ var RelativeTimestamp = vue.defineComponent({
2716
2797
  props.createdAt
2717
2798
  ) ?? "";
2718
2799
  if (props.hideWhenEmpty && (!datetime || Number.isNaN(Date.parse(datetime)))) return null;
2800
+ const absolute = componentContracts.formatDateTime(datetime, { timeZone: props.timezone });
2719
2801
  const content = firstDefined(
2720
2802
  props.relative,
2721
2803
  props.label,
2722
2804
  props.text,
2723
- props.now === void 0 ? componentContracts.formatDateTime(datetime, { timeZone: props.timezone }) : relativeTimestampLabel(datetime, props.now)
2805
+ !absolute ? "Unknown time" : props.now === void 0 ? absolute : relativeTimestampLabel(datetime, props.now)
2724
2806
  ) ?? "";
2725
2807
  return vue.h("time", {
2726
2808
  ...attrs,
2727
2809
  class: mergedClass("oods-relative-timestamp", attrs.class),
2728
2810
  datetime: datetime || void 0,
2729
- title: stringValue(attrs.title) ?? componentContracts.formatDateTime(datetime, { timeZone: props.timezone }),
2811
+ title: stringValue(attrs.title) ?? absolute,
2730
2812
  "data-oods-component": "RelativeTimestamp"
2731
2813
  }, content);
2732
2814
  };
@@ -2974,7 +3056,7 @@ function summary(id) {
2974
3056
  } });
2975
3057
  }
2976
3058
  function preview(id, type) {
2977
- return vue.defineComponent({ name: id, props: { svg: String, svgNarrow: String, svgWide: String, title: String, description: String, width: Number, height: Number }, setup(props, { slots }) {
3059
+ return vue.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 }) {
2978
3060
  return () => vue.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 ?? (() => vue.h(Text, { content: "No rendered chart supplied" })) });
2979
3061
  } });
2980
3062
  }
@@ -3040,6 +3122,7 @@ exports.CommunicationDetailPanel = CommunicationDetailPanel;
3040
3122
  exports.CycleProgressCard = CycleProgressCard;
3041
3123
  exports.DatePicker = DatePicker;
3042
3124
  exports.DetailHeader = DetailHeader;
3125
+ exports.Dialog = Dialog;
3043
3126
  exports.FilterPanel = FilterPanel;
3044
3127
  exports.FormLabelGroup = FormLabelGroup;
3045
3128
  exports.GeoFieldMappingForm = GeoFieldMappingForm;
@@ -3078,6 +3161,7 @@ exports.StatusBadge = StatusBadge;
3078
3161
  exports.StatusColorLegend = StatusColorLegend;
3079
3162
  exports.StatusSelector = StatusSelector;
3080
3163
  exports.StatusTimeline = StatusTimeline;
3164
+ exports.Switch = Switch;
3081
3165
  exports.Table = Table;
3082
3166
  exports.TableBody = TableBody;
3083
3167
  exports.TableCaption = TableCaption;