@oods/components-vue 0.3.2 → 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.js CHANGED
@@ -1,7 +1,12 @@
1
- import { dateTimeInputValue, formatDateTime, formatReadOnlyValue, currencyMinorUnits } from '@oods/component-contracts';
2
- import { defineComponent, h, ref, computed, useId, onBeforeUnmount } from 'vue';
1
+ import { dateTimeInputValue, formatDateTime, formatReadOnlyValue, STATUS_ICON_SVG_ATTRS, STATUS_ICON_SHAPES, getStatusPresentation, resolveStatusIcon, currencyMinorUnits } from '@oods/component-contracts';
2
+ import { defineComponent, h, useId, computed, ref, onMounted, watch, onBeforeUnmount } from 'vue';
3
3
 
4
4
  // src/ported.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
+ }
8
+
9
+ // src/primitives.ts
5
10
  var toneProp = String;
6
11
  var emphasisProp = String;
7
12
  var sizeProp = String;
@@ -89,8 +94,8 @@ var Badge = defineComponent({
89
94
  "--cmp-badge-text": `var(--sys-status-${palette}-text)`
90
95
  }
91
96
  }, [
92
- props.icon || slots.icon ? h("span", { class: "oods-badge-icon", "aria-hidden": "true" }, [slotOrValue(slots.icon, props.icon)]) : null,
93
- slotOrValue(slots.default, props.content ?? statusLabel(props.status))
97
+ props.icon || slots.icon ? h("span", { class: "oods-badge__icon", "aria-hidden": "true" }, [slotOrValue(slots.icon, props.icon)]) : null,
98
+ h("span", { class: "oods-badge__label" }, [slotOrValue(slots.default, props.content ?? statusLabel(props.status))])
94
99
  ]);
95
100
  };
96
101
  }
@@ -114,7 +119,7 @@ defineComponent({
114
119
  return () => {
115
120
  const tone = resolveTone(props.tone, props.status, props.domain);
116
121
  const palette = tokenTone(tone);
117
- return h("section", {
122
+ return h("div", {
118
123
  class: "oods-banner",
119
124
  "data-oods-component": "Banner",
120
125
  "data-status": props.status,
@@ -129,20 +134,20 @@ defineComponent({
129
134
  "--cmp-banner-text": `var(--sys-status-${palette}-text)`
130
135
  }
131
136
  }, [
132
- h("div", { class: "oods-banner-content" }, [
133
- props.title || props.status || slots.title ? h("strong", { class: "oods-banner-title" }, [
137
+ h("div", { class: "oods-banner__content" }, [
138
+ props.title || props.status || slots.title ? h("strong", { class: "oods-banner__title" }, [
134
139
  slotOrValue(slots.title, props.title || statusLabel(props.status))
135
140
  ]) : null,
136
- props.detail ? h("p", { class: "oods-banner-detail" }, props.detail) : null,
137
- slots.default || props.content ? h(slots.default ? "div" : "p", { class: "oods-banner-body" }, [slotOrValue(slots.default, props.content)]) : null,
138
- slots.actions ? h("div", { class: "oods-banner-actions" }, slots.actions()) : null
141
+ props.detail ? h("p", { class: "oods-banner__detail" }, props.detail) : null,
142
+ slots.default || props.content ? h(slots.default ? "div" : "p", { class: "oods-banner__body" }, [slotOrValue(slots.default, props.content)]) : null,
143
+ slots.actions ? h("div", { class: "oods-banner__actions" }, slots.actions()) : null
139
144
  ]),
140
145
  props.dismissLabel ? h("button", {
141
146
  type: "button",
142
- class: "oods-banner-dismiss",
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 @@ 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,83 @@ defineComponent({
179
184
  }, [slotOrValue(slots.default, props.content)]);
180
185
  }
181
186
  });
187
+ var canShowModal = () => typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
188
+ defineComponent({
189
+ name: "OodsDialog",
190
+ props: {
191
+ id: String,
192
+ open: Boolean,
193
+ modal: { type: Boolean, default: true },
194
+ title: { type: String, default: "" },
195
+ description: String,
196
+ dismissLabel: { type: String, default: "Close" },
197
+ size: { type: String, default: "md" },
198
+ actions: { type: [String, Number, Object, Array], default: void 0 }
199
+ },
200
+ emits: {
201
+ close: () => true
202
+ },
203
+ setup(props, { emit, slots }) {
204
+ const generatedId = useId().replace(/[^\w-]/g, "");
205
+ const dialogId = computed(() => props.id || `oods-dialog-${generatedId}`);
206
+ const element = ref(null);
207
+ const mounted = ref(false);
208
+ const managed = computed(() => props.modal && mounted.value && canShowModal());
209
+ const sync = () => {
210
+ const dialog = element.value;
211
+ if (!dialog || !managed.value) return;
212
+ if (props.open && !dialog.open) dialog.showModal();
213
+ else if (!props.open && dialog.open) dialog.close();
214
+ };
215
+ onMounted(() => {
216
+ mounted.value = true;
217
+ });
218
+ watch([() => props.open, managed], sync, { flush: "post" });
219
+ const requestClose = (event) => {
220
+ event.preventDefault();
221
+ emit("close");
222
+ };
223
+ return () => {
224
+ const titleId = `${dialogId.value}-title`;
225
+ const descriptionId = `${dialogId.value}-description`;
226
+ const actions = slotOrValue(slots.actions, props.actions);
227
+ return h("dialog", {
228
+ ref: element,
229
+ id: dialogId.value,
230
+ class: "oods-dialog",
231
+ "data-oods-component": "Dialog",
232
+ "data-size": props.size,
233
+ "aria-labelledby": titleId,
234
+ "aria-describedby": props.description ? descriptionId : void 0,
235
+ open: managed.value ? void 0 : props.open,
236
+ onKeydown: (event) => {
237
+ if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
238
+ },
239
+ onCancel: requestClose,
240
+ // The browser closed it (a close request it would not let be cancelled) while open is still true. A close event
241
+ // from moving a mounted dialog into the top layer finds it open again, and is not reported.
242
+ onClose: () => {
243
+ if (props.open && !element.value?.open) emit("close");
244
+ }
245
+ }, [
246
+ h("div", { class: "oods-dialog__header" }, [
247
+ h("div", { class: "oods-dialog__heading" }, [
248
+ h("h2", { id: titleId, class: "oods-dialog__title" }, props.title),
249
+ props.description ? h("p", { id: descriptionId, class: "oods-dialog__description" }, props.description) : null
250
+ ]),
251
+ h("button", {
252
+ type: "button",
253
+ class: "oods-dialog__close",
254
+ "aria-label": props.dismissLabel,
255
+ onClick: () => emit("close")
256
+ }, [statusIcon("x")])
257
+ ]),
258
+ slots.default ? h("div", { class: "oods-dialog__body" }, slots.default()) : null,
259
+ actions !== void 0 && actions !== null && actions !== "" ? h("div", { class: "oods-dialog__footer" }, [actions]) : null
260
+ ]);
261
+ };
262
+ }
263
+ });
182
264
  var SAFE_CARD_ELEMENTS = /* @__PURE__ */ new Set(["div", "article", "section", "aside"]);
183
265
  defineComponent({
184
266
  name: "OodsCard",
@@ -353,7 +435,9 @@ var Input = defineComponent({
353
435
  name: "OodsInput",
354
436
  props: {
355
437
  ...textFieldProps,
356
- type: { type: String, default: "text" }
438
+ type: { type: String, default: "text" },
439
+ /** A class for the input element itself, as React's inputClassName; the field's own class stays on its frame. */
440
+ inputClass: String
357
441
  },
358
442
  emits: {
359
443
  "update:modelValue": (_value) => true,
@@ -371,7 +455,7 @@ var Input = defineComponent({
371
455
  };
372
456
  return () => renderField("Input", props, slots, metadata, h("input", {
373
457
  id: metadata.controlId.value,
374
- class: "oods-field-control",
458
+ class: props.inputClass ? ["oods-field-control", props.inputClass] : "oods-field-control",
375
459
  type: props.type,
376
460
  name: props.name,
377
461
  value: props.type === "datetime-local" ? dateTimeInputValue(currentValue.value) : currentValue.value,
@@ -571,82 +655,75 @@ defineComponent({
571
655
  };
572
656
  }
573
657
  });
574
-
575
- // src/ported-core.ts
576
- var STATUS_METADATA = {
577
- subscription: {
578
- future: {
579
- description: "Subscription is scheduled but not yet active.",
580
- tone: "info",
581
- icon: "\u23F2"
582
- },
583
- trialing: {
584
- description: "Subscription is in a trial period before billing begins.",
585
- tone: "accent",
586
- icon: "\u2605"
587
- },
588
- active: {
589
- description: "Subscription is paid and service is provisioned.",
590
- tone: "success",
591
- icon: "\u2714\uFE0E"
592
- },
593
- paused: {
594
- description: "Billing paused temporarily by finance or customer request.",
595
- tone: "neutral",
596
- icon: "\u23F8"
597
- },
598
- pending_cancellation: {
599
- description: "Subscription will cancel at period end but remains active now.",
600
- tone: "info",
601
- icon: "\u2026"
602
- },
603
- past_due: {
604
- description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
605
- tone: "critical",
606
- icon: "\u26A0\uFE0E"
607
- },
608
- unpaid: {
609
- description: "Payment retries exhausted; service access revoked.",
610
- tone: "critical",
611
- icon: "\u2A2F"
612
- },
613
- terminated: {
614
- description: "Subscription ended; service access revoked and billing stopped.",
615
- tone: "neutral",
616
- icon: "\u2205"
617
- }
658
+ defineComponent({
659
+ name: "OodsSwitch",
660
+ props: {
661
+ id: String,
662
+ label: String,
663
+ modelValue: { type: Boolean, default: void 0 },
664
+ checked: { type: Boolean, default: void 0 },
665
+ defaultChecked: Boolean,
666
+ required: Boolean,
667
+ disabled: Boolean,
668
+ help: String,
669
+ validation: Object,
670
+ size: { type: String, default: "md" }
618
671
  },
619
- invoice: {
620
- draft: {
621
- description: "Invoice is editable and not yet finalized.",
622
- tone: "neutral",
623
- icon: "\u270E"
624
- },
625
- posted: {
626
- description: "Invoice finalized with terms before collection.",
627
- tone: "info",
628
- icon: "\u2026"
629
- },
630
- paid: {
631
- description: "Invoice collected successfully in full.",
632
- tone: "success",
633
- icon: "\u2714\uFE0E"
634
- },
635
- past_due: {
636
- description: "Invoice is unpaid past its due date.",
637
- tone: "critical",
638
- icon: "\u2A2F"
639
- },
640
- void: {
641
- description: "Invoice canceled and no longer payable.",
642
- tone: "neutral",
643
- icon: "\u2205"
644
- }
672
+ emits: {
673
+ "update:modelValue": (_value) => true,
674
+ change: (_value) => true
675
+ },
676
+ setup(props, { emit, slots }) {
677
+ const internalChecked = ref(props.defaultChecked);
678
+ const currentChecked = computed(() => props.modelValue ?? props.checked ?? internalChecked.value);
679
+ const metadata = useFieldMetadata("Switch", props, slots);
680
+ const toggle = () => {
681
+ const next = !currentChecked.value;
682
+ if (props.modelValue === void 0 && props.checked === void 0) internalChecked.value = next;
683
+ emit("update:modelValue", next);
684
+ emit("change", next);
685
+ };
686
+ return () => {
687
+ const label = slots.label?.() ?? props.label;
688
+ const help = slots.help?.() ?? props.help;
689
+ const validation = slots.validation?.() ?? props.validation?.message;
690
+ const controlId = metadata.controlId.value;
691
+ return h("div", {
692
+ class: "oods-field oods-switch",
693
+ style: validationStyle(props.validation),
694
+ "data-oods-component": "Switch",
695
+ "data-size": props.size,
696
+ "data-validation-state": props.validation?.state
697
+ }, [
698
+ h("div", { class: "oods-switch__row" }, [
699
+ h("button", {
700
+ id: controlId,
701
+ type: "button",
702
+ role: "switch",
703
+ class: "oods-switch__control",
704
+ "aria-checked": currentChecked.value ? "true" : "false",
705
+ "aria-labelledby": `${controlId}-label`,
706
+ "aria-describedby": metadata.describedBy.value,
707
+ "aria-required": props.required ? "true" : void 0,
708
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
709
+ disabled: props.disabled,
710
+ onClick: toggle
711
+ }, [h("span", { class: "oods-switch__thumb", "aria-hidden": "true" })]),
712
+ h("label", { id: `${controlId}-label`, class: "oods-field-label oods-switch__label", for: controlId }, [
713
+ label,
714
+ props.required ? h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
715
+ ])
716
+ ]),
717
+ help ? h("p", { id: metadata.helpId.value, class: "oods-field-help" }, help) : null,
718
+ validation ? h("p", {
719
+ id: metadata.validationId.value,
720
+ class: "oods-field-error",
721
+ role: props.validation?.state === "error" ? "alert" : void 0
722
+ }, validation) : null
723
+ ]);
724
+ };
645
725
  }
646
- };
647
- var STATUS_ALIASES = {
648
- subscription: { delinquent: "unpaid" }
649
- };
726
+ });
650
727
  function isRecord(value) {
651
728
  return typeof value === "object" && value !== null && !Array.isArray(value);
652
729
  }
@@ -663,18 +740,13 @@ function statusLabel2(status) {
663
740
  return status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
664
741
  }
665
742
  function getPortedStatusPresentation(domain, status) {
666
- const normalizedDomain = domain.toLowerCase();
667
- const normalizedStatus = status.toLowerCase();
668
- const alias = STATUS_ALIASES[normalizedDomain]?.[normalizedStatus];
669
- const metadata = STATUS_METADATA[normalizedDomain]?.[alias ?? normalizedStatus];
670
- if (!metadata) {
671
- return {
672
- label: statusLabel2(status || "Unknown"),
673
- description: "Status not found in registry; falling back to the requested token set.",
674
- tone: "neutral"
675
- };
676
- }
677
- return { ...metadata, label: statusLabel2(status) };
743
+ const presentation = getStatusPresentation(domain.toLowerCase(), status);
744
+ return {
745
+ label: statusLabel2(status || "Unknown"),
746
+ description: presentation.description,
747
+ tone: presentation.tone,
748
+ icon: resolveStatusIcon(presentation.iconName)
749
+ };
678
750
  }
679
751
  function formatPrice(amountCents, amount, currency) {
680
752
  const normalizedAmount = amount;
@@ -818,11 +890,10 @@ var StatusBadge = defineComponent({
818
890
  domain: props.domain,
819
891
  tone,
820
892
  emphasis: props.variant === "solid" ? "solid" : props.emphasis,
821
- icon: props.showIcon ? presentation.icon : void 0,
822
893
  title: stringValue(attrs.title) ?? presentation.description,
823
894
  "aria-label": ariaLabel,
824
895
  "data-oods-component": "StatusBadge"
825
- }, slots);
896
+ }, props.showIcon && presentation.icon && !slots.icon ? { ...slots, icon: () => statusIcon(presentation.icon) } : slots);
826
897
  };
827
898
  }
828
899
  });
@@ -905,12 +976,12 @@ var timelineProps = {
905
976
  };
906
977
  function renderTimelineEvent(event, index, showActorId, showReason) {
907
978
  return h("li", { key: `${event.timestamp ?? "event"}-${index}` }, [
908
- h("article", { "data-timeline-event": "true" }, [
909
- h("p", { "data-timeline-label": "true" }, event.label),
979
+ h("article", { class: "oods-timeline__event", "data-timeline-event": "true" }, [
980
+ h("p", { class: "oods-timeline__label", "data-timeline-label": "true" }, event.label),
910
981
  event.timestamp ? h("time", { "data-timeline-time": "true", datetime: event.timestamp }, formatDateTime(event.timestamp)) : null,
911
- event.detail ? h("p", { "data-timeline-detail": "true" }, event.detail) : null,
912
- showActorId && event.actorId ? h("p", { "data-timeline-actor": "true" }, `Actor: ${event.actorId}`) : null,
913
- showReason && event.reason ? h("p", { "data-timeline-reason": "true" }, `Reason: ${event.reason}`) : null
982
+ event.detail ? h("p", { class: "oods-timeline__detail", "data-timeline-detail": "true" }, event.detail) : null,
983
+ showActorId && event.actorId ? h("p", { class: "oods-timeline__actor", "data-timeline-actor": "true" }, `Actor: ${event.actorId}`) : null,
984
+ showReason && event.reason ? h("p", { class: "oods-timeline__reason", "data-timeline-reason": "true" }, `Reason: ${event.reason}`) : null
914
985
  ])
915
986
  ]);
916
987
  }
@@ -928,16 +999,16 @@ function timelineComponent(configuration) {
928
999
  const slottedEvents = slots.default?.();
929
1000
  return h("div", {
930
1001
  ...attrs,
931
- class: mergedClass("oods-timeline", attrs.class),
1002
+ class: mergedClass(`oods-timeline oods-${configuration.timelineType}-timeline`, attrs.class),
932
1003
  role: "log",
933
1004
  "aria-label": stringValue(attrs["aria-label"]) ?? title,
934
1005
  "data-oods-component": configuration.componentId,
935
1006
  "data-timeline-type": configuration.timelineType
936
1007
  }, [
937
- h("h2", { "data-timeline-title": "true" }, title),
938
- props.status ? h("p", { "data-timeline-current": "true" }, `Current status: ${statusLabel2(props.status)}`) : null,
1008
+ h("h2", { class: "oods-timeline__title", "data-timeline-title": "true" }, title),
1009
+ props.status ? h("p", { class: "oods-timeline__current", "data-timeline-current": "true" }, `Current status: ${statusLabel2(props.status)}`) : null,
939
1010
  props.allowedTransitions?.length ? h("p", { "data-timeline-transitions": "true" }, `Allowed transitions: ${props.allowedTransitions.join(", ")}`) : null,
940
- h("ol", { "data-timeline-events": "true" }, slottedEvents?.length ? slottedEvents : events.length ? events.map((event, index) => renderTimelineEvent(
1011
+ h("ol", { class: "oods-timeline__events", "data-timeline-events": "true" }, slottedEvents?.length ? slottedEvents : events.length ? events.map((event, index) => renderTimelineEvent(
941
1012
  event,
942
1013
  index,
943
1014
  props.showActorId,
@@ -1093,8 +1164,7 @@ var SearchInput = defineComponent({
1093
1164
  updateValue("");
1094
1165
  emit("clear");
1095
1166
  };
1096
- const submit = (event) => {
1097
- event.preventDefault();
1167
+ const searchNow = () => {
1098
1168
  if (!props.disabled) {
1099
1169
  if (timer !== void 0) clearTimeout(timer);
1100
1170
  emitSearch(currentValue.value);
@@ -1103,14 +1173,13 @@ var SearchInput = defineComponent({
1103
1173
  return () => {
1104
1174
  const id = props.id ?? `oods-search-${generatedId.replace(/:/g, "")}`;
1105
1175
  const ariaLabel = stringValue(attrs["aria-label"]) ?? props.label ?? props.placeholder;
1106
- return h("form", {
1176
+ return h("div", {
1107
1177
  ...attrs,
1108
1178
  class: mergedClass("oods-search-input", attrs.class),
1109
1179
  role: "search",
1110
1180
  "aria-label": ariaLabel,
1111
1181
  "data-oods-component": "SearchInput",
1112
- "data-behavioral": "search",
1113
- onSubmit: submit
1182
+ "data-behavioral": "search"
1114
1183
  }, [
1115
1184
  h(Input, {
1116
1185
  id,
@@ -1119,19 +1188,25 @@ var SearchInput = defineComponent({
1119
1188
  value: currentValue.value,
1120
1189
  placeholder: props.placeholder,
1121
1190
  disabled: props.disabled,
1191
+ inputClass: "oods-search-input__control",
1122
1192
  "aria-label": props.label ? void 0 : ariaLabel,
1123
1193
  onInput: (value) => updateValue(value),
1124
1194
  onKeydown: (event) => {
1125
1195
  if (event.key === "Escape") clear();
1196
+ if (event.key === "Enter") {
1197
+ event.preventDefault();
1198
+ searchNow();
1199
+ }
1126
1200
  }
1127
1201
  }),
1128
1202
  props.clearable && currentValue.value ? h("button", {
1129
1203
  type: "button",
1204
+ class: "oods-search-input__clear",
1130
1205
  "data-search-clear": "true",
1131
1206
  "aria-label": "Clear search",
1132
1207
  disabled: props.disabled,
1133
1208
  onClick: clear
1134
- }, "\xD7") : null
1209
+ }, [statusIcon("x")]) : null
1135
1210
  ]);
1136
1211
  };
1137
1212
  }
@@ -1278,17 +1353,18 @@ var RelativeTimestamp = defineComponent({
1278
1353
  props.createdAt
1279
1354
  ) ?? "";
1280
1355
  if (props.hideWhenEmpty && (!datetime || Number.isNaN(Date.parse(datetime)))) return null;
1356
+ const absolute = formatDateTime(datetime, { timeZone: props.timezone });
1281
1357
  const content = firstDefined(
1282
1358
  props.relative,
1283
1359
  props.label,
1284
1360
  props.text,
1285
- props.now === void 0 ? formatDateTime(datetime, { timeZone: props.timezone }) : relativeTimestampLabel(datetime, props.now)
1361
+ !absolute ? "Unknown time" : props.now === void 0 ? absolute : relativeTimestampLabel(datetime, props.now)
1286
1362
  ) ?? "";
1287
1363
  return h("time", {
1288
1364
  ...attrs,
1289
1365
  class: mergedClass("oods-relative-timestamp", attrs.class),
1290
1366
  datetime: datetime || void 0,
1291
- title: stringValue(attrs.title) ?? formatDateTime(datetime, { timeZone: props.timezone }),
1367
+ title: stringValue(attrs.title) ?? absolute,
1292
1368
  "data-oods-component": "RelativeTimestamp"
1293
1369
  }, content);
1294
1370
  };
@@ -3,7 +3,7 @@
3
3
  "mission": "s193-m05",
4
4
  "target": "vue",
5
5
  "contractVersion": "1.1.0",
6
- "derivation": "Each emissionEligible value is asserted with @oods/component-contracts evaluateEmissionEligibility. Sprint 185 adds five root exports; the original Sprint 182 row objects are preserved. Sprint 186 wave 2 appends ClassificationPanel, FilterPanel, PriceSummary, AddressCollectionPanel, MembershipPanel, PreferencePanel, TagManager, AddressSummaryBadge, MessageStatusBadge, PreferenceSummaryBadge, RoleBadgeList, TagPills, AddressValidationTimeline, AuditEvent, MembershipAuditTimeline, MessageEventTimeline, PreferenceTimeline, AddressEditor, PreferenceEditor, RoleAssignmentForm, StatusSelector, TagInput and TemplatePicker root exports with the same six evidence classes; prior rows are preserved. Sprint 186 m06 folds the former eight ported families into the root contract, exports, declarations and shared scenarios. Historical readiness files remain as evidence; readiness-ported resolved to this root document from Sprint 186 m06 until Sprint 200 m04 retired that subpath, and this root document is now the only readiness export. Sprint 187 m02 adds five naming/classification families with the same six evidence classes. Native classification controls are presentational; no save or domain-change action is claimed. Sprint 187 m03 adds OwnerBadge, OwnershipSummary, OwnershipMeta and TagSummary with the same six reference classes and explicit scalar/trait-array semantics. No ownership transfer or tagging action is claimed. Sprint 192 m03 projects passing per-root axe and executable scenario results into accessibility and interaction evidence; native unwired controls are measured without claiming a domain action. Sprint 192 m04 projects per-root six-cell browser contrast/token checks and screenshot hashes into visualThemes. Sprint 192 m05 adds the three retained roots and derives all accessibility, interaction and theme claims from passing 75-root measurements. Sprint 193 m04 adds nine declared non-Viz recipes and derives all accessibility, interaction and theme claims from passing 84-root measurements. Sprint 193 m05 adds 25 typed Viz recipes and derives all accessibility, interaction and theme claims from passing 109-root measurements.",
6
+ "derivation": "Each emissionEligible value is asserted with @oods/component-contracts evaluateEmissionEligibility. Sprint 185 adds five root exports; the original Sprint 182 row objects are preserved. Sprint 186 wave 2 appends ClassificationPanel, FilterPanel, PriceSummary, AddressCollectionPanel, MembershipPanel, PreferencePanel, TagManager, AddressSummaryBadge, MessageStatusBadge, PreferenceSummaryBadge, RoleBadgeList, TagPills, AddressValidationTimeline, AuditEvent, MembershipAuditTimeline, MessageEventTimeline, PreferenceTimeline, AddressEditor, PreferenceEditor, RoleAssignmentForm, StatusSelector, TagInput and TemplatePicker root exports with the same six evidence classes; prior rows are preserved. Sprint 186 m06 folds the former eight ported families into the root contract, exports, declarations and shared scenarios. Historical readiness files remain as evidence; readiness-ported resolved to this root document from Sprint 186 m06 until Sprint 200 m04 retired that subpath, and this root document is now the only readiness export. Sprint 187 m02 adds five naming/classification families with the same six evidence classes. Native classification controls are presentational; no save or domain-change action is claimed. Sprint 187 m03 adds OwnerBadge, OwnershipSummary, OwnershipMeta and TagSummary with the same six reference classes and explicit scalar/trait-array semantics. No ownership transfer or tagging action is claimed. Sprint 192 m03 projects passing per-root axe and executable scenario results into accessibility and interaction evidence; native unwired controls are measured without claiming a domain action. Sprint 192 m04 projects per-root six-cell browser contrast/token checks and screenshot hashes into visualThemes. Sprint 192 m05 adds the three retained roots and derives all accessibility, interaction and theme claims from passing 75-root measurements. Sprint 193 m04 adds nine declared non-Viz recipes and derives all accessibility, interaction and theme claims from passing 84-root measurements. Sprint 193 m05 adds 25 typed Viz recipes and derives all accessibility, interaction and theme claims from passing 109-root measurements. Sprint 222 m02 adds Switch and Dialog with the same six evidence classes and derives their accessibility, interaction and theme claims from passing 112-root measurements.",
7
7
  "rows": [
8
8
  {
9
9
  "componentId": "AddressCollectionPanel",
@@ -2554,6 +2554,79 @@
2554
2554
  }
2555
2555
  }
2556
2556
  },
2557
+ {
2558
+ "componentId": "Dialog",
2559
+ "state": "implemented-evidence-complete",
2560
+ "emissionEligible": true,
2561
+ "evidence": {
2562
+ "versionedContract": {
2563
+ "status": "passed",
2564
+ "refs": [
2565
+ "packages/component-contracts/src/contracts.ts#Dialog"
2566
+ ]
2567
+ },
2568
+ "targetImplementation": {
2569
+ "status": "passed",
2570
+ "refs": [
2571
+ "packages/components-vue/src/primitives.ts#Dialog"
2572
+ ]
2573
+ },
2574
+ "packageExport": {
2575
+ "status": "passed",
2576
+ "refs": [
2577
+ "packages/components-vue/src/index.ts#Dialog"
2578
+ ]
2579
+ },
2580
+ "publicDeclaration": {
2581
+ "status": "passed",
2582
+ "refs": [
2583
+ "packages/components-vue/dist/index.d.ts#Dialog"
2584
+ ]
2585
+ },
2586
+ "dependencyClosure": {
2587
+ "status": "passed",
2588
+ "refs": [
2589
+ "packages/components-vue/test/package-contract.spec.ts#dependency-closure"
2590
+ ]
2591
+ },
2592
+ "frameworkScenario": {
2593
+ "status": "passed",
2594
+ "refs": [
2595
+ "packages/components-vue/test/scenarios.spec.ts#dialog-confirm"
2596
+ ]
2597
+ },
2598
+ "accessibility": {
2599
+ "status": "passed",
2600
+ "classification": "verified",
2601
+ "refs": [
2602
+ "artifacts/product-reality/sprint-222/m02/switch-dialog/vue-measured.json#Dialog"
2603
+ ]
2604
+ },
2605
+ "interaction": {
2606
+ "status": "passed",
2607
+ "classification": "verified",
2608
+ "refs": [
2609
+ "artifacts/product-reality/sprint-222/m02/switch-dialog/vue-measured.json#Dialog"
2610
+ ]
2611
+ },
2612
+ "visualThemes": {
2613
+ "status": "passed",
2614
+ "classification": "verified",
2615
+ "cells": [
2616
+ "A-light",
2617
+ "A-dark",
2618
+ "A-hc",
2619
+ "B-light",
2620
+ "B-dark",
2621
+ "B-hc"
2622
+ ],
2623
+ "refs": [
2624
+ "artifacts/product-reality/sprint-222/m02/switch-dialog/vue-theme/report.json#Dialog"
2625
+ ]
2626
+ }
2627
+ },
2628
+ "measurementRoot": "artifacts/product-reality/sprint-222/m02/switch-dialog"
2629
+ },
2557
2630
  {
2558
2631
  "componentId": "FilterPanel",
2559
2632
  "state": "implemented-evidence-complete",
@@ -5321,6 +5394,79 @@
5321
5394
  }
5322
5395
  }
5323
5396
  },
5397
+ {
5398
+ "componentId": "Switch",
5399
+ "state": "implemented-evidence-complete",
5400
+ "emissionEligible": true,
5401
+ "evidence": {
5402
+ "versionedContract": {
5403
+ "status": "passed",
5404
+ "refs": [
5405
+ "packages/component-contracts/src/contracts.ts#Switch"
5406
+ ]
5407
+ },
5408
+ "targetImplementation": {
5409
+ "status": "passed",
5410
+ "refs": [
5411
+ "packages/components-vue/src/fields.ts#Switch"
5412
+ ]
5413
+ },
5414
+ "packageExport": {
5415
+ "status": "passed",
5416
+ "refs": [
5417
+ "packages/components-vue/src/index.ts#Switch"
5418
+ ]
5419
+ },
5420
+ "publicDeclaration": {
5421
+ "status": "passed",
5422
+ "refs": [
5423
+ "packages/components-vue/dist/index.d.ts#Switch"
5424
+ ]
5425
+ },
5426
+ "dependencyClosure": {
5427
+ "status": "passed",
5428
+ "refs": [
5429
+ "packages/components-vue/test/package-contract.spec.ts#dependency-closure"
5430
+ ]
5431
+ },
5432
+ "frameworkScenario": {
5433
+ "status": "passed",
5434
+ "refs": [
5435
+ "packages/components-vue/test/scenarios.spec.ts#switch-controlled"
5436
+ ]
5437
+ },
5438
+ "accessibility": {
5439
+ "status": "passed",
5440
+ "classification": "verified",
5441
+ "refs": [
5442
+ "artifacts/product-reality/sprint-222/m02/switch-dialog/vue-measured.json#Switch"
5443
+ ]
5444
+ },
5445
+ "interaction": {
5446
+ "status": "passed",
5447
+ "classification": "verified",
5448
+ "refs": [
5449
+ "artifacts/product-reality/sprint-222/m02/switch-dialog/vue-measured.json#Switch"
5450
+ ]
5451
+ },
5452
+ "visualThemes": {
5453
+ "status": "passed",
5454
+ "classification": "verified",
5455
+ "cells": [
5456
+ "A-light",
5457
+ "A-dark",
5458
+ "A-hc",
5459
+ "B-light",
5460
+ "B-dark",
5461
+ "B-hc"
5462
+ ],
5463
+ "refs": [
5464
+ "artifacts/product-reality/sprint-222/m02/switch-dialog/vue-theme/report.json#Switch"
5465
+ ]
5466
+ }
5467
+ },
5468
+ "measurementRoot": "artifacts/product-reality/sprint-222/m02/switch-dialog"
5469
+ },
5324
5470
  {
5325
5471
  "componentId": "Table",
5326
5472
  "state": "implemented-evidence-complete",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oods/components-vue",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Vue 3 components for OODS Foundry: the controls, layouts and domain views its generated screens use.",
5
5
  "homepage": "https://oods-foundry.com/",
6
6
  "bugs": {
@@ -53,8 +53,8 @@
53
53
  "**/*.css"
54
54
  ],
55
55
  "dependencies": {
56
- "@oods/component-contracts": "0.3.2",
57
- "@oods/component-styles": "0.3.2"
56
+ "@oods/component-contracts": "0.4.0",
57
+ "@oods/component-styles": "0.4.0"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "vue": "^3.5.0"