@oods/components-vue 0.4.0 → 0.4.2

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,5 @@
1
- import { defineComponent, h, useId, computed, ref, onMounted, watch, nextTick, onBeforeUnmount, isVNode, Text as Text$1, Comment, Fragment } from 'vue';
2
- import { vizPreviewLayers, svgCarriesTitle, 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, STATUS_ICON_SVG_ATTRS, STATUS_ICON_SHAPES, getStatusPresentation, resolveStatusIcon, 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
3
 
4
4
  // src/primitives.ts
5
5
  function statusIcon(name) {
@@ -185,6 +185,31 @@ var Button = defineComponent({
185
185
  }
186
186
  });
187
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
+ };
188
213
  var Dialog = defineComponent({
189
214
  name: "OodsDialog",
190
215
  props: {
@@ -206,15 +231,24 @@ var Dialog = defineComponent({
206
231
  const element = ref(null);
207
232
  const mounted = ref(false);
208
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
+ };
209
240
  const sync = () => {
210
241
  const dialog = element.value;
211
242
  if (!dialog || !managed.value) return;
212
- if (props.open && !dialog.open) dialog.showModal();
213
- else if (!props.open && dialog.open) dialog.close();
243
+ if (props.open && !dialog.open) {
244
+ dialog.showModal();
245
+ unlockScroll ??= lockPageScroll();
246
+ } else if (!props.open && dialog.open) dialog.close();
214
247
  };
215
248
  onMounted(() => {
216
249
  mounted.value = true;
217
250
  });
251
+ onBeforeUnmount(releaseScroll);
218
252
  watch([() => props.open, managed], sync, { flush: "post" });
219
253
  const requestClose = (event) => {
220
254
  event.preventDefault();
@@ -237,10 +271,23 @@ var Dialog = defineComponent({
237
271
  if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
238
272
  },
239
273
  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.
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.
242
277
  onClose: () => {
243
- if (props.open && !element.value?.open) emit("close");
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");
244
291
  }
245
292
  }, [
246
293
  h("div", { class: "oods-dialog__header" }, [
@@ -803,12 +850,15 @@ function createBadgeFamily(options) {
803
850
  };
804
851
  for (const key of options.extraProps) props[key] = String;
805
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 };
806
854
  return defineComponent({
807
855
  name: `Oods${options.component}`,
808
856
  props,
809
857
  setup(componentProps, { slots }) {
810
858
  return () => {
811
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;
812
862
  const content = authoredContent(slots.default?.());
813
863
  const scalar = scalarContent(content);
814
864
  const read = (key) => typeof record[key] === "boolean" && options.booleanProps?.includes(key) ? String(record[key]) : record[key];
@@ -1435,12 +1485,13 @@ var TagSummary = defineComponent({
1435
1485
  });
1436
1486
  var OwnershipMeta = defineComponent({
1437
1487
  name: "OodsOwnershipMeta",
1438
- 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 },
1439
1489
  setup(props, { slots }) {
1440
1490
  return () => {
1441
1491
  const content = authoredContent(slots.default?.());
1442
1492
  const terms = [["Owner Type", firstScalar(props.ownerType, props.owner_type)], ["Role", firstScalar(props.role, props.ownershipRole)]].filter((entry) => entry[1] !== void 0);
1443
- 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)] : [
1444
1495
  h("span", { "data-meta-title": "true" }, firstText(props.title, props.label, props.heading, props.name) ?? "Ownership"),
1445
1496
  ...terms.map(([term, value]) => h("span", { key: term, "data-meta-item": "true" }, [h("strong", `${term}:`), ` ${value}`]))
1446
1497
  ]);
@@ -1470,11 +1521,14 @@ var CancellationBadge = createBadgeFamily({
1470
1521
  booleanLabels: ["Cancellation scheduled", "No cancellation scheduled"]
1471
1522
  });
1472
1523
  var ArchiveSummary = defineComponent({
1524
+ // s224-m01 (#2542 ruling 6): hideWhenDefault is Boolean first, as the badges' hideWhenFalse is: a generated template writes
1525
+ // the bare attribute, which Vue casts to true only then.
1473
1526
  name: "OodsArchiveSummary",
1474
- props: { title: String, label: String, heading: String, name: String, isArchived: { type: [String, Boolean], default: void 0 }, archived: { type: [String, Boolean], default: void 0 }, status: { type: [String, Boolean], default: void 0 }, archivedAt: String, reason: String, archiveReason: String, summary: String, text: String, description: String },
1527
+ props: { title: String, label: String, heading: String, name: String, isArchived: { type: [String, Boolean], default: void 0 }, archived: { type: [String, Boolean], default: void 0 }, status: { type: [String, Boolean], default: void 0 }, archivedAt: String, reason: String, archiveReason: String, summary: String, text: String, description: String, hideWhenDefault: { type: [Boolean, String], default: void 0 } },
1475
1528
  setup(props, { slots }) {
1476
1529
  return () => {
1477
1530
  const content = authoredContent(slots.default?.());
1531
+ if (props.hideWhenDefault === true && (props.isArchived ?? props.archived ?? props.status) === false && !props.archivedAt && !firstText(props.reason, props.archiveReason) && !content.length) return null;
1478
1532
  const terms = [["Archived", summaryValue(props.isArchived ?? props.archived ?? props.status)], ["Archived At", props.archivedAt ? formatDateTime(props.archivedAt) : void 0], ["Reason", firstText(props.reason, props.archiveReason)]].filter((entry) => entry[1] !== void 0);
1479
1533
  const fallback = firstText(props.summary, props.text, props.description);
1480
1534
  return h("section", { class: "oods-archive-summary", "data-oods-component": "ArchiveSummary", "data-summary-type": "archive" }, [
@@ -2130,13 +2184,16 @@ var Tabs = defineComponent({
2130
2184
  onClick: () => selectTab(item.id),
2131
2185
  onKeydown: (event) => onTabKeydown(event, item.id)
2132
2186
  }, slots.itemLabel?.({ item, selected: item.id === activeId.value }) ?? [item.label])),
2133
- overflowItems.value.length > 0 ? h("div", { class: "oods-tabs-overflow" }, [
2187
+ // s224-m01 (#2542 ruling 2): the overflow markup is React's too: both class families, the trigger's data
2188
+ // attribute and aria-current="true" on an item.
2189
+ overflowItems.value.length > 0 ? h("div", { class: "oods-tabs-overflow oods-tabs__overflow" }, [
2134
2190
  h("button", {
2135
2191
  type: "button",
2136
2192
  class: "oods-tabs-overflow-trigger",
2137
2193
  "aria-label": `${props.overflowLabel} tabs`,
2138
2194
  "aria-haspopup": "menu",
2139
2195
  "aria-expanded": overflowOpen.value,
2196
+ "data-tabs-overflow-trigger": "true",
2140
2197
  onClick: () => {
2141
2198
  overflowOpen.value = !overflowOpen.value;
2142
2199
  }
@@ -2146,7 +2203,7 @@ var Tabs = defineComponent({
2146
2203
  type: "button",
2147
2204
  role: "menuitem",
2148
2205
  disabled: itemDisabled(item),
2149
- "aria-current": item.id === activeId.value ? "page" : void 0,
2206
+ "aria-current": item.id === activeId.value ? "true" : void 0,
2150
2207
  onClick: () => {
2151
2208
  selectTab(item.id);
2152
2209
  overflowOpen.value = false;
@@ -2166,6 +2223,63 @@ var Tabs = defineComponent({
2166
2223
  ]);
2167
2224
  }
2168
2225
  });
2226
+ var SegmentedControl = defineComponent({
2227
+ name: "OodsSegmentedControl",
2228
+ props: {
2229
+ id: String,
2230
+ label: String,
2231
+ modelValue: String,
2232
+ value: String,
2233
+ defaultValue: { type: String, default: "" },
2234
+ name: String,
2235
+ size: { type: String, default: "md" },
2236
+ disabled: Boolean,
2237
+ options: { type: Array, default: () => [] }
2238
+ },
2239
+ emits: {
2240
+ "update:modelValue": (_value) => true,
2241
+ change: (_value) => true
2242
+ },
2243
+ setup(props, { emit, slots }) {
2244
+ const generatedId = useId();
2245
+ const internalValue = ref(props.defaultValue);
2246
+ const currentValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
2247
+ const select = (event) => {
2248
+ const next = event.target.value;
2249
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = next;
2250
+ emit("update:modelValue", next);
2251
+ emit("change", next);
2252
+ };
2253
+ return () => {
2254
+ const id = props.id || `oods-segmentedcontrol-${generatedId}`;
2255
+ const label = slots.label?.() ?? props.label;
2256
+ return h("div", {
2257
+ class: "oods-field oods-segmented-control",
2258
+ "data-oods-component": "SegmentedControl",
2259
+ "data-size": props.size
2260
+ }, [
2261
+ h("span", { id: `${id}-label`, class: "oods-field-label" }, [label]),
2262
+ h("div", {
2263
+ id,
2264
+ role: "radiogroup",
2265
+ "aria-labelledby": `${id}-label`,
2266
+ class: "oods-segmented-control__track"
2267
+ }, props.options.map((option) => h("label", { key: option.value, class: "oods-segmented-control__option" }, [
2268
+ h("input", {
2269
+ type: "radio",
2270
+ class: "oods-segmented-control__input",
2271
+ name: props.name || id,
2272
+ value: option.value,
2273
+ checked: option.value === currentValue.value,
2274
+ disabled: props.disabled || option.disabled,
2275
+ onChange: select
2276
+ }),
2277
+ h("span", { class: "oods-segmented-control__label" }, option.label)
2278
+ ])))
2279
+ ]);
2280
+ };
2281
+ }
2282
+ });
2169
2283
  function isRecord2(value) {
2170
2284
  return typeof value === "object" && value !== null && !Array.isArray(value);
2171
2285
  }
@@ -2503,12 +2617,15 @@ var CancellationSummary = defineComponent({
2503
2617
  cancelAtPeriodEndField: String,
2504
2618
  requestedAtField: String,
2505
2619
  reasonField: String,
2506
- codeField: String
2620
+ codeField: String,
2621
+ // s224-m01 (#2542 ruling 6): Boolean first, as the badges' hideWhenFalse is (a bare attribute casts to true).
2622
+ hideWhenDefault: { type: [Boolean, String], default: void 0 }
2507
2623
  },
2508
2624
  setup(props, { attrs, slots }) {
2509
2625
  return () => {
2510
2626
  const slotValue = slotSummaryValue(slots);
2511
2627
  const cancellationValue = props.cancelAtPeriodEnd ?? slotValue;
2628
+ if (props.hideWhenDefault === true && props.cancelAtPeriodEnd === false && !props.requestedAt && !props.reason && !props.code && slotValue === void 0) return null;
2512
2629
  const rows = [];
2513
2630
  if (cancellationValue !== void 0) {
2514
2631
  rows.push(summaryEntry(
@@ -2983,7 +3100,8 @@ var array = Array;
2983
3100
  function eventRecipe(id, kind, title) {
2984
3101
  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) => () => {
2985
3102
  const rows = traitEventRows(kind, props);
2986
- 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.")]);
3103
+ if (!rows.length) return null;
3104
+ 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}`)])))]);
2987
3105
  } });
2988
3106
  }
2989
3107
  var ArchiveEvent = eventRecipe("ArchiveEvent", "archive", "Archive events");
@@ -3084,5 +3202,210 @@ var VizLinePreview = preview("VizLinePreview", "line");
3084
3202
  var VizMarkPreview = preview("VizMarkPreview", "bar");
3085
3203
  var VizPointPreview = preview("VizPointPreview", "scatter");
3086
3204
  var VizScatterPreview = preview("VizScatterPreview", "scatter");
3205
+ var Combobox = defineComponent({
3206
+ name: "OodsCombobox",
3207
+ props: {
3208
+ id: String,
3209
+ label: String,
3210
+ options: { type: Array, default: () => [] },
3211
+ modelValue: String,
3212
+ value: String,
3213
+ defaultValue: { type: String, default: "" },
3214
+ placeholder: String,
3215
+ name: String,
3216
+ required: Boolean,
3217
+ disabled: Boolean,
3218
+ help: String,
3219
+ validation: Object,
3220
+ /** The control height, as Button, Input and Select: xs 24, sm 28, md 32 (default) or lg 40. */
3221
+ size: { type: String, default: "md" }
3222
+ },
3223
+ emits: {
3224
+ "update:modelValue": (_value) => true,
3225
+ change: (_value) => true
3226
+ },
3227
+ setup(props, { emit, slots }) {
3228
+ const generatedId = useId();
3229
+ const controlId = computed(() => props.id || `oods-combobox-${generatedId}`);
3230
+ const internalValue = ref(props.defaultValue);
3231
+ const query = ref(null);
3232
+ const open = ref(false);
3233
+ const active = ref(-1);
3234
+ const root = ref(null);
3235
+ const listbox = ref(null);
3236
+ const chosenValue = computed(() => props.modelValue ?? props.value ?? internalValue.value);
3237
+ const chosen = computed(() => props.options.find((option) => option.value === chosenValue.value));
3238
+ const text = computed(() => query.value ?? chosen.value?.label ?? "");
3239
+ const matches = computed(() => {
3240
+ const needle = (query.value ?? "").trim().toLowerCase();
3241
+ return props.options.map((option) => needle === "" || option.label.toLowerCase().includes(needle));
3242
+ });
3243
+ const reachable = computed(() => props.options.flatMap((option, index) => matches.value[index] && !option.disabled ? [index] : []));
3244
+ const optionId = (index) => `${controlId.value}-option-${index}`;
3245
+ const close = () => {
3246
+ open.value = false;
3247
+ active.value = -1;
3248
+ };
3249
+ const commit = (next) => {
3250
+ if (props.modelValue === void 0 && props.value === void 0) internalValue.value = next;
3251
+ emit("update:modelValue", next);
3252
+ emit("change", next);
3253
+ };
3254
+ const pick = (index) => {
3255
+ const option = props.options[index];
3256
+ if (!option || option.disabled) return;
3257
+ query.value = null;
3258
+ close();
3259
+ if (option.value !== chosenValue.value) commit(option.value);
3260
+ };
3261
+ const outside = (event) => {
3262
+ if (!root.value?.contains(event.target)) close();
3263
+ };
3264
+ watch(open, (isOpen) => {
3265
+ if (isOpen) document.addEventListener("pointerdown", outside);
3266
+ else document.removeEventListener("pointerdown", outside);
3267
+ });
3268
+ onBeforeUnmount(() => {
3269
+ if (typeof document !== "undefined") document.removeEventListener("pointerdown", outside);
3270
+ });
3271
+ watch([open, active], async () => {
3272
+ if (!open.value || active.value < 0) return;
3273
+ await nextTick();
3274
+ const element = listbox.value?.children[active.value];
3275
+ element?.scrollIntoView?.({ block: "nearest" });
3276
+ });
3277
+ const keydown = (event) => {
3278
+ if (event.defaultPrevented || props.disabled || event.isComposing) return;
3279
+ const order = reachable.value;
3280
+ const first = order[0] ?? -1;
3281
+ const last = order[order.length - 1] ?? -1;
3282
+ const at = order.indexOf(active.value);
3283
+ switch (event.key) {
3284
+ case "ArrowDown":
3285
+ event.preventDefault();
3286
+ active.value = !open.value || at === -1 ? first : order[(at + 1) % order.length];
3287
+ open.value = true;
3288
+ return;
3289
+ case "ArrowUp":
3290
+ event.preventDefault();
3291
+ active.value = !open.value || at <= 0 ? last : order[at - 1];
3292
+ open.value = true;
3293
+ return;
3294
+ case "Home":
3295
+ case "End":
3296
+ if (!open.value) return;
3297
+ event.preventDefault();
3298
+ active.value = event.key === "Home" ? first : last;
3299
+ return;
3300
+ case "Enter":
3301
+ if (!open.value) return;
3302
+ event.preventDefault();
3303
+ if (active.value >= 0) pick(active.value);
3304
+ return;
3305
+ case "Escape":
3306
+ if (open.value) {
3307
+ event.preventDefault();
3308
+ close();
3309
+ return;
3310
+ }
3311
+ if (text.value === "" && chosenValue.value === "") return;
3312
+ event.preventDefault();
3313
+ query.value = null;
3314
+ if (chosenValue.value !== "") commit("");
3315
+ return;
3316
+ case "Tab":
3317
+ if (open.value) close();
3318
+ return;
3319
+ }
3320
+ };
3321
+ return () => {
3322
+ const id = controlId.value;
3323
+ const label = slots.label?.() ?? props.label;
3324
+ const help = slots.help?.() ?? props.help;
3325
+ const validation = slots.validation?.() ?? props.validation?.message;
3326
+ const tone = props.validation ? props.validation.state === "error" ? "critical" : props.validation.state : void 0;
3327
+ const describedBy = [help ? `${id}-help` : void 0, validation ? `${id}-validation` : void 0].filter(Boolean).join(" ") || void 0;
3328
+ const noMatches = open.value && !matches.value.some(Boolean);
3329
+ return h("div", {
3330
+ ref: root,
3331
+ class: "oods-field oods-combobox",
3332
+ ...tone ? { style: { "--cmp-input-message-border": `var(--sys-status-${tone}-border)`, "--cmp-input-message-text": `var(--sys-status-${tone}-text)` } } : {},
3333
+ "data-oods-component": "Combobox",
3334
+ "data-size": props.size,
3335
+ "data-validation-state": props.validation?.state
3336
+ }, [
3337
+ h("label", { id: `${id}-label`, class: "oods-field-label", for: id }, [
3338
+ label,
3339
+ props.required ? h("span", { class: "oods-field-required", "aria-hidden": "true" }, "*") : null
3340
+ ]),
3341
+ h("div", { class: "oods-combobox__control" }, [
3342
+ h("input", {
3343
+ id,
3344
+ type: "text",
3345
+ role: "combobox",
3346
+ class: "oods-field-control oods-combobox__input",
3347
+ value: text.value,
3348
+ placeholder: props.placeholder,
3349
+ autocomplete: "off",
3350
+ "aria-autocomplete": "list",
3351
+ "aria-expanded": open.value ? "true" : "false",
3352
+ "aria-controls": `${id}-listbox`,
3353
+ "aria-activedescendant": open.value && active.value >= 0 ? optionId(active.value) : void 0,
3354
+ "aria-describedby": describedBy,
3355
+ "aria-invalid": props.validation?.state === "error" ? "true" : void 0,
3356
+ required: props.required,
3357
+ disabled: props.disabled,
3358
+ onInput: (event) => {
3359
+ query.value = event.target.value;
3360
+ open.value = true;
3361
+ active.value = -1;
3362
+ },
3363
+ onKeydown: keydown,
3364
+ onClick: () => {
3365
+ if (!props.disabled) open.value = true;
3366
+ },
3367
+ onBlur: () => {
3368
+ query.value = null;
3369
+ close();
3370
+ }
3371
+ }),
3372
+ h("span", { class: "oods-combobox__chevron", "aria-hidden": "true" }, [statusIcon("chevron-down")]),
3373
+ h("div", {
3374
+ class: "oods-combobox__popup",
3375
+ hidden: !open.value,
3376
+ onMousedown: (event) => event.preventDefault()
3377
+ }, [
3378
+ h(
3379
+ "ul",
3380
+ { ref: listbox, id: `${id}-listbox`, class: "oods-combobox__listbox", role: "listbox", "aria-labelledby": `${id}-label` },
3381
+ props.options.map((option, index) => h("li", {
3382
+ key: option.value,
3383
+ id: optionId(index),
3384
+ class: "oods-combobox__option",
3385
+ role: "option",
3386
+ "aria-selected": option.value === chosenValue.value ? "true" : "false",
3387
+ "aria-disabled": option.disabled ? "true" : void 0,
3388
+ "data-value": option.value,
3389
+ "data-active": index === active.value ? "true" : void 0,
3390
+ hidden: !matches.value[index],
3391
+ onClick: () => pick(index),
3392
+ onPointermove: () => {
3393
+ if (!option.disabled && index !== active.value) active.value = index;
3394
+ }
3395
+ }, [
3396
+ h("span", { class: "oods-combobox__option-label" }, option.label),
3397
+ h("span", { class: "oods-combobox__check", "aria-hidden": "true" }, [statusIcon("check")])
3398
+ ]))
3399
+ ),
3400
+ h("p", { class: "oods-combobox__empty", role: "status" }, noMatches ? "No matches" : "")
3401
+ ])
3402
+ ]),
3403
+ props.name ? h("input", { type: "hidden", name: props.name, value: chosenValue.value }) : null,
3404
+ help ? h("p", { id: `${id}-help`, class: "oods-field-help" }, help) : null,
3405
+ validation ? h("p", { id: `${id}-validation`, class: "oods-field-error", role: props.validation?.state === "error" ? "alert" : void 0 }, validation) : null
3406
+ ]);
3407
+ };
3408
+ }
3409
+ });
3087
3410
 
3088
- 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, 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, 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 };
3411
+ 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 };
package/dist/ported.cjs CHANGED
@@ -187,6 +187,31 @@ vue.defineComponent({
187
187
  }
188
188
  });
189
189
  var canShowModal = () => typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
190
+ var pageLocks = 0;
191
+ var pageStyle;
192
+ function lockPageScroll() {
193
+ const page = document.documentElement;
194
+ if (pageLocks++ === 0) {
195
+ const scrollbar = window.innerWidth - page.clientWidth;
196
+ pageStyle = { overflow: page.style.overflow, paddingRight: page.style.paddingRight };
197
+ if (scrollbar > 0) page.style.paddingRight = `${parseFloat(getComputedStyle(page).paddingRight) + scrollbar}px`;
198
+ page.style.overflow = "hidden";
199
+ }
200
+ let held = true;
201
+ return () => {
202
+ if (!held) return;
203
+ held = false;
204
+ if (--pageLocks === 0 && pageStyle) {
205
+ page.style.overflow = pageStyle.overflow;
206
+ page.style.paddingRight = pageStyle.paddingRight;
207
+ pageStyle = void 0;
208
+ }
209
+ };
210
+ }
211
+ var outsideBox = (event, dialog) => {
212
+ const box = dialog.getBoundingClientRect();
213
+ return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
214
+ };
190
215
  vue.defineComponent({
191
216
  name: "OodsDialog",
192
217
  props: {
@@ -208,15 +233,24 @@ vue.defineComponent({
208
233
  const element = vue.ref(null);
209
234
  const mounted = vue.ref(false);
210
235
  const managed = vue.computed(() => props.modal && mounted.value && canShowModal());
236
+ let unlockScroll;
237
+ let pressedBackdrop = false;
238
+ const releaseScroll = () => {
239
+ unlockScroll?.();
240
+ unlockScroll = void 0;
241
+ };
211
242
  const sync = () => {
212
243
  const dialog = element.value;
213
244
  if (!dialog || !managed.value) return;
214
- if (props.open && !dialog.open) dialog.showModal();
215
- else if (!props.open && dialog.open) dialog.close();
245
+ if (props.open && !dialog.open) {
246
+ dialog.showModal();
247
+ unlockScroll ??= lockPageScroll();
248
+ } else if (!props.open && dialog.open) dialog.close();
216
249
  };
217
250
  vue.onMounted(() => {
218
251
  mounted.value = true;
219
252
  });
253
+ vue.onBeforeUnmount(releaseScroll);
220
254
  vue.watch([() => props.open, managed], sync, { flush: "post" });
221
255
  const requestClose = (event) => {
222
256
  event.preventDefault();
@@ -239,10 +273,23 @@ vue.defineComponent({
239
273
  if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
240
274
  },
241
275
  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.
276
+ // A close event from moving a mounted dialog into the top layer finds it open again, and is not reported. Any other
277
+ // close releases the page; when the browser closed it (a close request it would not let be cancelled) while open is
278
+ // still true, it is reported.
244
279
  onClose: () => {
245
- if (props.open && !element.value?.open) emit("close");
280
+ if (element.value?.open) return;
281
+ releaseScroll();
282
+ if (props.open) emit("close");
283
+ },
284
+ onMousedown: (event) => {
285
+ pressedBackdrop = managed.value && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
286
+ },
287
+ // s223-m02 (#2527 ruling 13b): a click on the backdrop emits close, as the close control does. A press that began
288
+ // inside the dialog (text selected and dragged out) is not a backdrop click.
289
+ onClick: (event) => {
290
+ const backdrop = pressedBackdrop && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
291
+ pressedBackdrop = false;
292
+ if (backdrop) emit("close");
246
293
  }
247
294
  }, [
248
295
  vue.h("div", { class: "oods-dialog__header" }, [
@@ -1063,12 +1110,15 @@ var CancellationSummary = vue.defineComponent({
1063
1110
  cancelAtPeriodEndField: String,
1064
1111
  requestedAtField: String,
1065
1112
  reasonField: String,
1066
- codeField: String
1113
+ codeField: String,
1114
+ // s224-m01 (#2542 ruling 6): Boolean first, as the badges' hideWhenFalse is (a bare attribute casts to true).
1115
+ hideWhenDefault: { type: [Boolean, String], default: void 0 }
1067
1116
  },
1068
1117
  setup(props, { attrs, slots }) {
1069
1118
  return () => {
1070
1119
  const slotValue = slotSummaryValue(slots);
1071
1120
  const cancellationValue = props.cancelAtPeriodEnd ?? slotValue;
1121
+ if (props.hideWhenDefault === true && props.cancelAtPeriodEnd === false && !props.requestedAt && !props.reason && !props.code && slotValue === void 0) return null;
1072
1122
  const rows = [];
1073
1123
  if (cancellationValue !== void 0) {
1074
1124
  rows.push(summaryEntry(
package/dist/ported.d.cts CHANGED
@@ -401,9 +401,13 @@ declare const CancellationSummary: vue.DefineComponent<vue.ExtractPropTypes<{
401
401
  requestedAtField: StringConstructor;
402
402
  reasonField: StringConstructor;
403
403
  codeField: StringConstructor;
404
+ hideWhenDefault: {
405
+ type: (StringConstructor | BooleanConstructor)[];
406
+ default: undefined;
407
+ };
404
408
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
405
409
  [key: string]: any;
406
- }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
410
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
407
411
  title: StringConstructor;
408
412
  label: StringConstructor;
409
413
  cancelAtPeriodEnd: {
@@ -418,8 +422,13 @@ declare const CancellationSummary: vue.DefineComponent<vue.ExtractPropTypes<{
418
422
  requestedAtField: StringConstructor;
419
423
  reasonField: StringConstructor;
420
424
  codeField: StringConstructor;
425
+ hideWhenDefault: {
426
+ type: (StringConstructor | BooleanConstructor)[];
427
+ default: undefined;
428
+ };
421
429
  }>> & Readonly<{}>, {
422
430
  cancelAtPeriodEnd: boolean;
431
+ hideWhenDefault: string | boolean;
423
432
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
424
433
  declare const SearchInput: vue.DefineComponent<vue.ExtractPropTypes<{
425
434
  id: StringConstructor;
package/dist/ported.d.ts CHANGED
@@ -401,9 +401,13 @@ declare const CancellationSummary: vue.DefineComponent<vue.ExtractPropTypes<{
401
401
  requestedAtField: StringConstructor;
402
402
  reasonField: StringConstructor;
403
403
  codeField: StringConstructor;
404
+ hideWhenDefault: {
405
+ type: (StringConstructor | BooleanConstructor)[];
406
+ default: undefined;
407
+ };
404
408
  }>, () => VNode<vue.RendererNode, vue.RendererElement, {
405
409
  [key: string]: any;
406
- }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
410
+ }> | null, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
407
411
  title: StringConstructor;
408
412
  label: StringConstructor;
409
413
  cancelAtPeriodEnd: {
@@ -418,8 +422,13 @@ declare const CancellationSummary: vue.DefineComponent<vue.ExtractPropTypes<{
418
422
  requestedAtField: StringConstructor;
419
423
  reasonField: StringConstructor;
420
424
  codeField: StringConstructor;
425
+ hideWhenDefault: {
426
+ type: (StringConstructor | BooleanConstructor)[];
427
+ default: undefined;
428
+ };
421
429
  }>> & Readonly<{}>, {
422
430
  cancelAtPeriodEnd: boolean;
431
+ hideWhenDefault: string | boolean;
423
432
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
424
433
  declare const SearchInput: vue.DefineComponent<vue.ExtractPropTypes<{
425
434
  id: StringConstructor;