@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/dist/ported.cjs CHANGED
@@ -4,6 +4,11 @@ var componentContracts = require('@oods/component-contracts');
4
4
  var vue = require('vue');
5
5
 
6
6
  // src/ported.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
+
11
+ // src/primitives.ts
7
12
  var toneProp = String;
8
13
  var emphasisProp = String;
9
14
  var sizeProp = String;
@@ -144,7 +149,7 @@ 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 @@ 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 @@ 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
+ 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
  vue.defineComponent({
186
268
  name: "OodsCard",
@@ -575,82 +657,75 @@ vue.defineComponent({
575
657
  };
576
658
  }
577
659
  });
578
-
579
- // src/ported-core.ts
580
- var STATUS_METADATA = {
581
- subscription: {
582
- future: {
583
- description: "Subscription is scheduled but not yet active.",
584
- tone: "info",
585
- icon: "\u23F2"
586
- },
587
- trialing: {
588
- description: "Subscription is in a trial period before billing begins.",
589
- tone: "accent",
590
- icon: "\u2605"
591
- },
592
- active: {
593
- description: "Subscription is paid and service is provisioned.",
594
- tone: "success",
595
- icon: "\u2714\uFE0E"
596
- },
597
- paused: {
598
- description: "Billing paused temporarily by finance or customer request.",
599
- tone: "neutral",
600
- icon: "\u23F8"
601
- },
602
- pending_cancellation: {
603
- description: "Subscription will cancel at period end but remains active now.",
604
- tone: "info",
605
- icon: "\u2026"
606
- },
607
- past_due: {
608
- description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
609
- tone: "critical",
610
- icon: "\u26A0\uFE0E"
611
- },
612
- unpaid: {
613
- description: "Payment retries exhausted; service access revoked.",
614
- tone: "critical",
615
- icon: "\u2A2F"
616
- },
617
- terminated: {
618
- description: "Subscription ended; service access revoked and billing stopped.",
619
- tone: "neutral",
620
- icon: "\u2205"
621
- }
660
+ vue.defineComponent({
661
+ name: "OodsSwitch",
662
+ props: {
663
+ id: String,
664
+ label: String,
665
+ modelValue: { type: Boolean, default: void 0 },
666
+ checked: { type: Boolean, default: void 0 },
667
+ defaultChecked: Boolean,
668
+ required: Boolean,
669
+ disabled: Boolean,
670
+ help: String,
671
+ validation: Object,
672
+ size: { type: String, default: "md" }
622
673
  },
623
- invoice: {
624
- draft: {
625
- description: "Invoice is editable and not yet finalized.",
626
- tone: "neutral",
627
- icon: "\u270E"
628
- },
629
- posted: {
630
- description: "Invoice finalized with terms before collection.",
631
- tone: "info",
632
- icon: "\u2026"
633
- },
634
- paid: {
635
- description: "Invoice collected successfully in full.",
636
- tone: "success",
637
- icon: "\u2714\uFE0E"
638
- },
639
- past_due: {
640
- description: "Invoice is unpaid past its due date.",
641
- tone: "critical",
642
- icon: "\u2A2F"
643
- },
644
- void: {
645
- description: "Invoice canceled and no longer payable.",
646
- tone: "neutral",
647
- icon: "\u2205"
648
- }
674
+ emits: {
675
+ "update:modelValue": (_value) => true,
676
+ change: (_value) => true
677
+ },
678
+ setup(props, { emit, slots }) {
679
+ const internalChecked = vue.ref(props.defaultChecked);
680
+ const currentChecked = vue.computed(() => props.modelValue ?? props.checked ?? internalChecked.value);
681
+ const metadata = useFieldMetadata("Switch", props, slots);
682
+ const toggle = () => {
683
+ const next = !currentChecked.value;
684
+ if (props.modelValue === void 0 && props.checked === void 0) internalChecked.value = next;
685
+ emit("update:modelValue", next);
686
+ emit("change", next);
687
+ };
688
+ return () => {
689
+ const label = slots.label?.() ?? props.label;
690
+ const help = slots.help?.() ?? props.help;
691
+ const validation = slots.validation?.() ?? props.validation?.message;
692
+ const controlId = metadata.controlId.value;
693
+ return vue.h("div", {
694
+ class: "oods-field oods-switch",
695
+ style: validationStyle(props.validation),
696
+ "data-oods-component": "Switch",
697
+ "data-size": props.size,
698
+ "data-validation-state": props.validation?.state
699
+ }, [
700
+ vue.h("div", { class: "oods-switch__row" }, [
701
+ vue.h("button", {
702
+ id: controlId,
703
+ type: "button",
704
+ role: "switch",
705
+ class: "oods-switch__control",
706
+ "aria-checked": currentChecked.value ? "true" : "false",
707
+ "aria-labelledby": `${controlId}-label`,
708
+ "aria-describedby": metadata.describedBy.value,
709
+ "aria-required": props.required ? "true" : void 0,
710
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
711
+ disabled: props.disabled,
712
+ onClick: toggle
713
+ }, [vue.h("span", { class: "oods-switch__thumb", "aria-hidden": "true" })]),
714
+ vue.h("label", { id: `${controlId}-label`, class: "oods-field-label oods-switch__label", for: controlId }, [
715
+ label,
716
+ props.required ? vue.h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
717
+ ])
718
+ ]),
719
+ help ? vue.h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
720
+ validation ? vue.h("p", {
721
+ id: metadata.validationId.value,
722
+ class: "oods-field-error",
723
+ role: props.validation?.state === "error" ? "alert" : void 0
724
+ }, validation) : null
725
+ ]);
726
+ };
649
727
  }
650
- };
651
- var STATUS_ALIASES = {
652
- subscription: { delinquent: "unpaid" }
653
- };
728
+ });
654
729
  function isRecord(value) {
655
730
  return typeof value === "object" && value !== null && !Array.isArray(value);
656
731
  }
@@ -667,18 +742,13 @@ function statusLabel2(status) {
667
742
  return status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
668
743
  }
669
744
  function getPortedStatusPresentation(domain, status) {
670
- const normalizedDomain = domain.toLowerCase();
671
- const normalizedStatus = status.toLowerCase();
672
- const alias = STATUS_ALIASES[normalizedDomain]?.[normalizedStatus];
673
- const metadata = STATUS_METADATA[normalizedDomain]?.[alias ?? normalizedStatus];
674
- if (!metadata) {
675
- return {
676
- label: statusLabel2(status || "Unknown"),
677
- description: "Status not found in registry; falling back to the requested token set.",
678
- tone: "neutral"
679
- };
680
- }
681
- return { ...metadata, label: statusLabel2(status) };
745
+ const presentation = componentContracts.getStatusPresentation(domain.toLowerCase(), status);
746
+ return {
747
+ label: statusLabel2(status || "Unknown"),
748
+ description: presentation.description,
749
+ tone: presentation.tone,
750
+ icon: componentContracts.resolveStatusIcon(presentation.iconName)
751
+ };
682
752
  }
683
753
  function formatPrice(amountCents, amount, currency) {
684
754
  const normalizedAmount = amount;
@@ -822,11 +892,10 @@ var StatusBadge = vue.defineComponent({
822
892
  domain: props.domain,
823
893
  tone,
824
894
  emphasis: props.variant === "solid" ? "solid" : props.emphasis,
825
- icon: props.showIcon ? presentation.icon : void 0,
826
895
  title: stringValue(attrs.title) ?? presentation.description,
827
896
  "aria-label": ariaLabel,
828
897
  "data-oods-component": "StatusBadge"
829
- }, slots);
898
+ }, props.showIcon && presentation.icon && !slots.icon ? { ...slots, icon: () => statusIcon(presentation.icon) } : slots);
830
899
  };
831
900
  }
832
901
  });
@@ -1139,7 +1208,7 @@ var SearchInput = vue.defineComponent({
1139
1208
  "aria-label": "Clear search",
1140
1209
  disabled: props.disabled,
1141
1210
  onClick: clear
1142
- }, "\xD7") : null
1211
+ }, [statusIcon("x")]) : null
1143
1212
  ]);
1144
1213
  };
1145
1214
  }
@@ -1286,17 +1355,18 @@ var RelativeTimestamp = vue.defineComponent({
1286
1355
  props.createdAt
1287
1356
  ) ?? "";
1288
1357
  if (props.hideWhenEmpty && (!datetime || Number.isNaN(Date.parse(datetime)))) return null;
1358
+ const absolute = componentContracts.formatDateTime(datetime, { timeZone: props.timezone });
1289
1359
  const content = firstDefined(
1290
1360
  props.relative,
1291
1361
  props.label,
1292
1362
  props.text,
1293
- props.now === void 0 ? componentContracts.formatDateTime(datetime, { timeZone: props.timezone }) : relativeTimestampLabel(datetime, props.now)
1363
+ !absolute ? "Unknown time" : props.now === void 0 ? absolute : relativeTimestampLabel(datetime, props.now)
1294
1364
  ) ?? "";
1295
1365
  return vue.h("time", {
1296
1366
  ...attrs,
1297
1367
  class: mergedClass("oods-relative-timestamp", attrs.class),
1298
1368
  datetime: datetime || void 0,
1299
- title: stringValue(attrs.title) ?? componentContracts.formatDateTime(datetime, { timeZone: props.timezone }),
1369
+ title: stringValue(attrs.title) ?? absolute,
1300
1370
  "data-oods-component": "RelativeTimestamp"
1301
1371
  }, content);
1302
1372
  };
package/dist/ported.d.cts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as vue from 'vue';
2
2
  import { PropType, VNode } from 'vue';
3
+ import { StatusIconName } from '@oods/component-contracts';
3
4
 
4
5
  type ComponentTone = 'neutral' | 'info' | 'accent' | 'positive' | 'success' | 'warning' | 'critical' | 'danger';
5
6
  type ComponentEmphasis = 'subtle' | 'solid';
@@ -21,7 +22,7 @@ type PaginationItem = {
21
22
  type StatusMetadata = {
22
23
  description: string;
23
24
  tone: ComponentTone;
24
- icon?: string;
25
+ icon?: StatusIconName;
25
26
  };
26
27
  type StatusPresentation = StatusMetadata & {
27
28
  label: string;
package/dist/ported.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as vue from 'vue';
2
2
  import { PropType, VNode } from 'vue';
3
+ import { StatusIconName } from '@oods/component-contracts';
3
4
 
4
5
  type ComponentTone = 'neutral' | 'info' | 'accent' | 'positive' | 'success' | 'warning' | 'critical' | 'danger';
5
6
  type ComponentEmphasis = 'subtle' | 'solid';
@@ -21,7 +22,7 @@ type PaginationItem = {
21
22
  type StatusMetadata = {
22
23
  description: string;
23
24
  tone: ComponentTone;
24
- icon?: string;
25
+ icon?: StatusIconName;
25
26
  };
26
27
  type StatusPresentation = StatusMetadata & {
27
28
  label: string;