@adia-ai/web-components 0.6.36 → 0.6.37

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/components/badge/badge.a2ui.json +10 -0
  3. package/components/badge/badge.css +70 -0
  4. package/components/badge/badge.yaml +20 -0
  5. package/components/blockquote/blockquote.a2ui.json +121 -0
  6. package/components/blockquote/blockquote.class.js +68 -0
  7. package/components/blockquote/blockquote.css +46 -0
  8. package/components/blockquote/blockquote.d.ts +31 -0
  9. package/components/blockquote/blockquote.js +17 -0
  10. package/components/blockquote/blockquote.yaml +124 -0
  11. package/components/button/button.css +11 -3
  12. package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
  13. package/components/calendar-picker/calendar-picker.class.js +7 -1
  14. package/components/calendar-picker/calendar-picker.yaml +14 -0
  15. package/components/color-input/color-input.a2ui.json +2 -2
  16. package/components/color-input/color-input.class.js +9 -2
  17. package/components/color-input/color-input.yaml +2 -2
  18. package/components/combobox/combobox.class.js +4 -0
  19. package/components/context-menu/context-menu.a2ui.json +159 -0
  20. package/components/context-menu/context-menu.class.js +275 -0
  21. package/components/context-menu/context-menu.css +56 -0
  22. package/components/context-menu/context-menu.d.ts +70 -0
  23. package/components/context-menu/context-menu.js +17 -0
  24. package/components/context-menu/context-menu.yaml +136 -0
  25. package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
  26. package/components/date-range-picker/date-range-picker.class.js +2 -0
  27. package/components/date-range-picker/date-range-picker.yaml +14 -0
  28. package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
  29. package/components/datetime-picker/datetime-picker.class.js +3 -1
  30. package/components/datetime-picker/datetime-picker.d.ts +2 -0
  31. package/components/datetime-picker/datetime-picker.yaml +14 -0
  32. package/components/empty-state/empty-state.class.js +2 -0
  33. package/components/feed/feed.class.js +13 -5
  34. package/components/feed/feed.css +14 -0
  35. package/components/index.js +9 -0
  36. package/components/integration-card/integration-card.class.js +9 -0
  37. package/components/integration-card/integration-card.test.js +4 -3
  38. package/components/nav-group/nav-group.css +7 -1
  39. package/components/number-format/number-format.a2ui.json +180 -0
  40. package/components/number-format/number-format.class.js +96 -0
  41. package/components/number-format/number-format.css +18 -0
  42. package/components/number-format/number-format.d.ts +68 -0
  43. package/components/number-format/number-format.js +17 -0
  44. package/components/number-format/number-format.yaml +204 -0
  45. package/components/pagination/pagination.a2ui.json +19 -2
  46. package/components/pagination/pagination.class.js +90 -37
  47. package/components/pagination/pagination.css +32 -127
  48. package/components/pagination/pagination.d.ts +8 -2
  49. package/components/pagination/pagination.test.js +195 -0
  50. package/components/pagination/pagination.yaml +22 -1
  51. package/components/password-strength/password-strength.a2ui.json +152 -0
  52. package/components/password-strength/password-strength.class.js +157 -0
  53. package/components/password-strength/password-strength.css +80 -0
  54. package/components/password-strength/password-strength.d.ts +59 -0
  55. package/components/password-strength/password-strength.js +17 -0
  56. package/components/password-strength/password-strength.yaml +153 -0
  57. package/components/popover/popover.css +43 -23
  58. package/components/popover/popover.yaml +8 -4
  59. package/components/qr-code/QR-TEST.svg +4 -0
  60. package/components/qr-code/qr-code.a2ui.json +154 -0
  61. package/components/qr-code/qr-code.class.js +129 -0
  62. package/components/qr-code/qr-code.css +41 -0
  63. package/components/qr-code/qr-code.d.ts +83 -0
  64. package/components/qr-code/qr-code.js +17 -0
  65. package/components/qr-code/qr-code.yaml +203 -0
  66. package/components/qr-code/qr-encoder.js +633 -0
  67. package/components/relative-time/relative-time.a2ui.json +120 -0
  68. package/components/relative-time/relative-time.class.js +136 -0
  69. package/components/relative-time/relative-time.css +22 -0
  70. package/components/relative-time/relative-time.d.ts +51 -0
  71. package/components/relative-time/relative-time.js +17 -0
  72. package/components/relative-time/relative-time.yaml +133 -0
  73. package/components/segmented/segmented.class.js +5 -1
  74. package/components/select/select.class.js +4 -0
  75. package/components/skip-nav/skip-nav.a2ui.json +92 -0
  76. package/components/skip-nav/skip-nav.class.js +45 -0
  77. package/components/skip-nav/skip-nav.css +54 -0
  78. package/components/skip-nav/skip-nav.d.ts +27 -0
  79. package/components/skip-nav/skip-nav.js +12 -0
  80. package/components/skip-nav/skip-nav.yaml +68 -0
  81. package/components/slider/slider.a2ui.json +16 -1
  82. package/components/slider/slider.class.js +264 -122
  83. package/components/slider/slider.css +82 -2
  84. package/components/slider/slider.d.ts +19 -3
  85. package/components/slider/slider.test.js +55 -0
  86. package/components/slider/slider.yaml +28 -6
  87. package/components/table/table.class.js +29 -6
  88. package/components/table/table.css +31 -4
  89. package/components/table-toolbar/table-toolbar.class.js +3 -1
  90. package/components/tag/tag.a2ui.json +3 -2
  91. package/components/tag/tag.css +35 -11
  92. package/components/tag/tag.d.ts +14 -0
  93. package/components/tag/tag.test.js +35 -11
  94. package/components/tag/tag.yaml +13 -7
  95. package/components/toast/toast.class.js +12 -4
  96. package/components/toc/toc.a2ui.json +159 -0
  97. package/components/toc/toc.class.js +222 -0
  98. package/components/toc/toc.css +92 -0
  99. package/components/toc/toc.d.ts +61 -0
  100. package/components/toc/toc.js +17 -0
  101. package/components/toc/toc.yaml +180 -0
  102. package/components/toolbar/toolbar.class.js +3 -0
  103. package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
  104. package/components/visually-hidden/visually-hidden.class.js +14 -0
  105. package/components/visually-hidden/visually-hidden.css +25 -0
  106. package/components/visually-hidden/visually-hidden.d.ts +26 -0
  107. package/components/visually-hidden/visually-hidden.js +12 -0
  108. package/components/visually-hidden/visually-hidden.yaml +54 -0
  109. package/core/anchor.js +19 -3
  110. package/dist/web-components.min.css +1 -1
  111. package/dist/web-components.min.js +100 -89
  112. package/package.json +1 -1
  113. package/styles/colors/semantics.css +11 -2
  114. package/styles/components.css +9 -0
  115. package/styles/resets.css +10 -0
@@ -0,0 +1,136 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: UIContextMenu
3
+ tag: context-menu-ui
4
+ status: stable
5
+ component: ContextMenu
6
+ category: container
7
+ version: 1
8
+ description: |
9
+ Right-click activated menu — the OS-native context-menu pattern as a
10
+ web component. Distinct from `menu-ui` (which is button-triggered):
11
+ same item shape (`menu-item-ui` children), different trigger surface
12
+ (`contextmenu` event), and pointer-anchored positioning instead of
13
+ element-anchored. Pattern: WAI-APG Menu.
14
+
15
+ Two binding modes:
16
+ **A. Wrap.** Default-slot child becomes the target:
17
+ `<context-menu-ui><my-table>...</my-table>...items</context-menu-ui>`.
18
+ **B. Selector.** Point at one or more existing elements via [for]:
19
+ `<context-menu-ui for="#my-table">...items</context-menu-ui>`.
20
+
21
+ On `contextmenu` event on a target: `preventDefault()`, position the
22
+ menu at the pointer coords, show via Popover API. Touch long-press
23
+ (configurable via [long-press-ms]) does the same. Shift+F10 / Menu
24
+ key opens at the focused target's center for keyboard users.
25
+ composes:
26
+ - menu-item-ui
27
+ props:
28
+ for:
29
+ description: CSS selector(s) for target element(s). Empty = use default-slot child.
30
+ type: string
31
+ default: ""
32
+ reflect: true
33
+ open:
34
+ description: Programmatic open state. Set true to open at target center.
35
+ type: boolean
36
+ default: false
37
+ reflect: true
38
+ long-press-ms:
39
+ description: Long-press duration (ms) on touch devices to open the menu.
40
+ type: number
41
+ default: 500
42
+ reflect: false
43
+ attribute: long-press-ms
44
+ events:
45
+ context-menu-open:
46
+ description: Fired when the menu opens (right-click / long-press / keyboard).
47
+ detail:
48
+ target:
49
+ type: Element
50
+ description: The target element the menu was opened on.
51
+ x:
52
+ type: number
53
+ description: Pointer x coord (viewport-relative); null for keyboard activation.
54
+ y:
55
+ type: number
56
+ description: Pointer y coord; null for keyboard activation.
57
+ context-menu-close:
58
+ description: Fired when the menu closes (item-select / outside-click / Escape).
59
+ detail:
60
+ reason:
61
+ type: string
62
+ description: '"select" | "outside" | "escape"'
63
+ context-menu-select:
64
+ description: Fired when an item is activated. Same shape as menu-ui's `action` event.
65
+ detail:
66
+ value:
67
+ type: string
68
+ description: Selected item's value.
69
+ text:
70
+ type: string
71
+ description: Selected item's text.
72
+ slots:
73
+ default:
74
+ description: |
75
+ Two-purpose slot: the wrapped target element (mode A — first
76
+ non-menu-item-ui child) AND the menu-item-ui items. Items are
77
+ promoted to the popover surface on open.
78
+ states:
79
+ - name: idle
80
+ description: Default. Menu closed; trigger listeners attached.
81
+ - name: open
82
+ description: Menu visible at pointer position; focus inside.
83
+ attribute: open
84
+ traits: []
85
+ tokens:
86
+ --context-menu-bg:
87
+ description: Menu surface background color.
88
+ default: var(--a-bg-subtle)
89
+ --context-menu-border:
90
+ description: Menu surface border color.
91
+ default: var(--a-border-subtle)
92
+ --context-menu-radius:
93
+ description: Menu surface border radius.
94
+ default: var(--a-radius-md)
95
+ --context-menu-shadow:
96
+ description: Menu surface shadow.
97
+ default: var(--a-shadow-lg)
98
+ a2ui:
99
+ rules:
100
+ - rule: 'Use <context-menu-ui> for right-click menus on a target (table row, file item, canvas object). For button-triggered menus use <menu-ui>; for popover content that is not a menu use <popover-ui> directly.'
101
+ reason: 'Trigger surface boundary.'
102
+ - rule: 'Items are <menu-item-ui> children inside the default slot — same shape as <menu-ui> items.'
103
+ reason: 'Single menu vocabulary.'
104
+ - rule: 'Bind target via wrap (default-slot first non-menu-item-ui child) OR [for] selector. The selector form is useful for whole-table or whole-canvas menus where wrapping isn''t practical.'
105
+ reason: 'Two binding shapes.'
106
+ anti_patterns:
107
+ - wrong: '<context-menu-ui>...just items...</context-menu-ui>'
108
+ why: 'No target binding — the menu never opens.'
109
+ fix: 'Wrap a target: `<context-menu-ui><my-target></my-target>...items</context-menu-ui>` OR point at one: `<context-menu-ui for="#my-target">...items</context-menu-ui>`.'
110
+ examples:
111
+ - name: file-actions
112
+ description: Right-click a file row for Open / Rename / Delete.
113
+ a2ui: |
114
+ [
115
+ { "id": "root", "component": "ContextMenu", "children": ["target", "item-open", "item-rename", "div", "item-delete"] },
116
+ { "id": "target", "component": "Text", "textContent": "Right-click me" },
117
+ { "id": "item-open", "component": "MenuItem", "value": "open", "text": "Open" },
118
+ { "id": "item-rename", "component": "MenuItem", "value": "rename", "text": "Rename" },
119
+ { "id": "div", "component": "MenuDivider" },
120
+ { "id": "item-delete", "component": "MenuItem", "value": "delete", "text": "Delete", "variant": "danger" }
121
+ ]
122
+ keywords:
123
+ - context-menu
124
+ - right-click
125
+ - menu
126
+ - popup-menu
127
+ synonyms:
128
+ right-click:
129
+ - context-menu
130
+ popup-menu:
131
+ - menu
132
+ - context-menu
133
+ related:
134
+ - menu
135
+ - menu-item
136
+ - popover
@@ -75,6 +75,21 @@
75
75
  "type": "string",
76
76
  "default": "Select range"
77
77
  },
78
+ "placement": {
79
+ "description": "Popover placement relative to the trigger. Default `bottom` centers the two-calendar panel under the trigger (ADR-0034 Rule 2 — ~800px panel >> trigger).",
80
+ "type": "string",
81
+ "enum": [
82
+ "top",
83
+ "bottom",
84
+ "left",
85
+ "right",
86
+ "top-start",
87
+ "top-end",
88
+ "bottom-start",
89
+ "bottom-end"
90
+ ],
91
+ "default": "bottom"
92
+ },
78
93
  "readonly": {
79
94
  "description": "Block edits; allow keyboard navigation for screen-reader inspection.",
80
95
  "type": "boolean",
@@ -144,6 +144,8 @@ export class UIDateRangePicker extends UIFormElement {
144
144
  placeholder: { type: String, default: 'Select range', reflect: true },
145
145
  format: { type: String, default: 'short', reflect: true },
146
146
  noPresets: { type: Boolean, default: false, attribute: 'no-presets', reflect: true },
147
+ // Popover placement — yaml documented reflect:true since v1.
148
+ placement: { type: String, default: 'bottom', reflect: true },
147
149
  };
148
150
  }
149
151
 
@@ -91,6 +91,20 @@ props:
91
91
  default: false
92
92
  attribute: no-presets
93
93
  reflect: true
94
+ placement:
95
+ description: Popover placement relative to the trigger. Default `bottom` centers the two-calendar panel under the trigger (ADR-0034 Rule 2 — ~800px panel >> trigger).
96
+ type: string
97
+ default: bottom
98
+ reflect: true
99
+ enum:
100
+ - top
101
+ - bottom
102
+ - left
103
+ - right
104
+ - top-start
105
+ - top-end
106
+ - bottom-start
107
+ - bottom-end
94
108
  events:
95
109
  change:
96
110
  description: Fired when the range commits (both `from` AND `to` selected, OR a preset clicked).
@@ -76,6 +76,21 @@
76
76
  "type": "string",
77
77
  "default": "Select date and time"
78
78
  },
79
+ "placement": {
80
+ "description": "Popover placement relative to the trigger. Default `bottom` centers the calendar+time panel under the trigger (ADR-0034 Rule 2 — ~600px panel >> trigger).",
81
+ "type": "string",
82
+ "enum": [
83
+ "top",
84
+ "bottom",
85
+ "left",
86
+ "right",
87
+ "top-start",
88
+ "top-end",
89
+ "bottom-start",
90
+ "bottom-end"
91
+ ],
92
+ "default": "bottom"
93
+ },
79
94
  "precision": {
80
95
  "description": "Time-pane precision. `minute` (default) emits `HH:mm`; `second` exposes the seconds segment and emits `HH:mm:ss`.",
81
96
  "type": "string",
@@ -136,6 +136,8 @@ export class UIDatetimePicker extends UIFormElement {
136
136
  placeholder: { type: String, default: 'Select date and time', reflect: false },
137
137
  format: { type: String, default: 'short', reflect: true },
138
138
  locale: { type: String, default: '', reflect: false },
139
+ // Popover placement — yaml documented reflect:true since v1.
140
+ placement: { type: String, default: 'bottom', reflect: true },
139
141
  };
140
142
  }
141
143
 
@@ -439,7 +441,7 @@ export class UIDatetimePicker extends UIFormElement {
439
441
  // helper. Without this, the popover renders at viewport (0,0).
440
442
  this.#anchorCleanup?.();
441
443
  this.#anchorCleanup = anchorPopover(this.#triggerRef, this.#popoverRef, {
442
- placement: this.getAttribute('placement') || 'bottom-start',
444
+ placement: this.getAttribute('placement') || 'bottom', // ADR-0034 Rule 2: panel >> trigger
443
445
  gap: 4,
444
446
  });
445
447
  document.addEventListener('pointerdown', this.#onOutside);
@@ -64,6 +64,8 @@ document locale. `h12` forces a 12-hour cycle with an AM/PM
64
64
  open: boolean;
65
65
  /** Text shown in the trigger when the value is empty. */
66
66
  placeholder: string;
67
+ /** Popover placement relative to the trigger. Default `bottom` centers the calendar+time panel under the trigger (ADR-0034 Rule 2 — ~600px panel >> trigger). */
68
+ placement: 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
67
69
  /** Time-pane precision. `minute` (default) emits `HH:mm`; `second` exposes the seconds segment and emits `HH:mm:ss`. */
68
70
  precision: 'minute' | 'second';
69
71
  /** Block edits; allow keyboard navigation for screen-reader inspection. */
@@ -104,6 +104,20 @@ props:
104
104
  description: BCP-47 locale tag used to derive hour-cycle when `hour-cycle` is empty. Falls back to `<html lang>` then to browser default.
105
105
  type: string
106
106
  default: ''
107
+ placement:
108
+ description: Popover placement relative to the trigger. Default `bottom` centers the calendar+time panel under the trigger (ADR-0034 Rule 2 — ~600px panel >> trigger).
109
+ type: string
110
+ default: bottom
111
+ reflect: true
112
+ enum:
113
+ - top
114
+ - bottom
115
+ - left
116
+ - right
117
+ - top-start
118
+ - top-end
119
+ - bottom-start
120
+ - bottom-end
107
121
  events:
108
122
  change:
109
123
  description: Fired when the value commits (date picked + time edited, OR Apply clicked in explicit-commit mode).
@@ -29,6 +29,8 @@ export class UIEmptyState extends UIElement {
29
29
  icon: { type: String, default: '', reflect: true },
30
30
  heading: { type: String, default: '', reflect: true },
31
31
  description: { type: String, default: '', reflect: true },
32
+ // Semantic variant — yaml documented reflect:true since v1.
33
+ variant: { type: String, default: '', reflect: true },
32
34
  // §223 (v0.5.9): minimal layout — single-line muted (no centered column,
33
35
  // no padding bump, no icon-size lg). Use for inline empty-table-row /
34
36
  // inline placeholder cells where the canvas placeholder is too loud.
@@ -296,9 +296,14 @@ export class UIFeed {
296
296
  * @param {string} [opts.position='bottom-right']
297
297
  * @param {boolean} [opts.dismissible] override default (true for sticky, false for auto)
298
298
  * @param {string} [opts.id]
299
- * @param {string} [opts.action] Phase 2 — action button label. When set,
300
- * duration is forced to null (sticky); the
301
- * item gets role=alertdialog + focus trap.
299
+ * @param {string} [opts.action] Phase 2 — action button label. When set
300
+ * without an explicit duration, the item is
301
+ * sticky (role=alertdialog + focus trap).
302
+ * Pass an explicit numeric `duration` to
303
+ * build the "undo" pattern: the action
304
+ * button shows, but the item auto-dismisses
305
+ * after the timeout. Negative-action UX
306
+ * (delete/archive) typically uses 6000-8000.
302
307
  * @param {function} [opts.onAction] Phase 2 — callback invoked when the
303
308
  * action button is pressed. Item dismisses
304
309
  * after the callback returns.
@@ -330,8 +335,11 @@ export class UIFeed {
330
335
  item.heading = heading;
331
336
  item.icon = icon;
332
337
  item.variant = v;
333
- // Action-required forces sticky.
334
- item.duration = action ? 0 : duration;
338
+ // Action-required default is sticky; explicit duration honored so callers
339
+ // can build the "undo" pattern (action + timed auto-dismiss). Default
340
+ // (duration unspecified) preserves the original alertdialog sticky behavior.
341
+ const explicitDuration = 'duration' in opts;
342
+ item.duration = action && !explicitDuration ? 0 : duration;
335
343
  if (dismissible != null) item.dismissible = !!dismissible;
336
344
  if (action) item.action = action;
337
345
  if (visibleCount >= max) item.setAttribute('data-queued', '');
@@ -40,6 +40,10 @@ feed-item-ui[data-closing] {
40
40
  pointer-events: none; /* Items re-enable pointer-events. */
41
41
  width: max-content;
42
42
  max-width: var(--feed-max-width, var(--feed-max-width-default));
43
+ /* Items size to their own content (see feed-item-ui width: max-content);
44
+ align them to the lane's anchored edge so short items hug the side
45
+ rather than left-aligning inside a wider container. */
46
+ align-items: flex-end;
43
47
  }
44
48
  /* Reset native popover defaults. UA stylesheet sets `margin: auto;
45
49
  inset: 0; border: solid; padding: 0.25em` which would center the
@@ -61,11 +65,13 @@ feed-item-ui[data-closing] {
61
65
  }
62
66
  :scope[position="bottom-left"] {
63
67
  bottom: var(--feed-offset, var(--feed-offset-default)); left: var(--feed-offset, var(--feed-offset-default)); right: auto;
68
+ align-items: flex-start;
64
69
  }
65
70
  :scope[position="bottom-center"] {
66
71
  bottom: var(--feed-offset, var(--feed-offset-default));
67
72
  left: 50%; right: auto;
68
73
  transform: translateX(-50%);
74
+ align-items: center;
69
75
  }
70
76
  :scope[position="top-right"] {
71
77
  top: var(--feed-offset, var(--feed-offset-default)); right: var(--feed-offset, var(--feed-offset-default)); bottom: auto;
@@ -74,12 +80,14 @@ feed-item-ui[data-closing] {
74
80
  :scope[position="top-left"] {
75
81
  top: var(--feed-offset, var(--feed-offset-default)); left: var(--feed-offset, var(--feed-offset-default)); right: auto; bottom: auto;
76
82
  flex-direction: column-reverse;
83
+ align-items: flex-start;
77
84
  }
78
85
  :scope[position="top-center"] {
79
86
  top: var(--feed-offset, var(--feed-offset-default)); bottom: auto;
80
87
  left: 50%; right: auto;
81
88
  transform: translateX(-50%);
82
89
  flex-direction: column-reverse;
90
+ align-items: center;
83
91
  }
84
92
  :scope[position="inline"] {
85
93
  position: relative;
@@ -119,6 +127,12 @@ feed-item-ui[data-closing] {
119
127
  display: flex;
120
128
  align-items: center;
121
129
  gap: var(--feed-item-gap);
130
+ /* Size to own content (a one-line "OK" stays narrow), capped at
131
+ max-width (long messages wrap at the bound). The lane container's
132
+ align-items (per [position]) re-aligns short items to the anchored
133
+ edge so they hug the corner instead of left-aligning under a
134
+ wider sibling. */
135
+ width: max-content;
122
136
  max-width: var(--feed-item-max-width);
123
137
  padding: var(--feed-item-py) var(--feed-item-px);
124
138
  background: var(--feed-item-bg);
@@ -79,11 +79,20 @@ export { UIChartLegend } from './chart-legend/chart-legend.js';
79
79
  export { UIPopover } from './popover/popover.js';
80
80
  export { UIAccordion, UIAccordionItem } from './accordion/accordion.js';
81
81
  export { UIDivider } from './divider/divider.js';
82
+ export { UIBlockquote } from './blockquote/blockquote.js';
83
+ export { UIRelativeTime } from './relative-time/relative-time.js';
84
+ export { UINumberFormat } from './number-format/number-format.js';
85
+ export { UIPasswordStrength } from './password-strength/password-strength.js';
86
+ export { UITableOfContents } from './toc/toc.js';
87
+ export { UIQRCode } from './qr-code/qr-code.js';
82
88
  export { UIPagination } from './pagination/pagination.js';
83
89
  export { UICode } from './code/code.js';
84
90
  export { UIList, UIListItem } from './list/list.js';
85
91
  export { UIListWindow } from './list-window/list-window.js';
86
92
  export { UIMenu, UIMenuItem, UIMenuDivider } from './menu/menu.js';
93
+ export { UIContextMenu } from './context-menu/context-menu.js';
94
+ export { UIVisuallyHidden } from './visually-hidden/visually-hidden.js';
95
+ export { UISkipNav } from './skip-nav/skip-nav.js';
87
96
  export { UIToolbar, UIToolbarGroup } from './toolbar/toolbar.js';
88
97
  export { UINav } from './nav/nav.js';
89
98
  export { UINavGroup } from './nav-group/nav-group.js';
@@ -229,6 +229,15 @@ export class UIIntegrationCard extends UIElement {
229
229
  if (!this.#logoEl) return;
230
230
  const logo = (this.logo || '').trim();
231
231
 
232
+ // Guard against unresolved AdiaUI template binding descriptors
233
+ // ({{p:N}} placeholders). The integration-card's connected() + render()
234
+ // fires synchronously when the parent template stamps the element, BEFORE
235
+ // the parent's reconciliation pass replaces {{p:N}} with the real value.
236
+ // Skipping here lets the second render (triggered by the reconciliation's
237
+ // property set) render the real logo. Without this guard, icon-ui fires
238
+ // a "not found" warn for every {{p:4}} it receives on first connect.
239
+ if (logo.startsWith('{{p:')) return;
240
+
232
241
  // No logo → strip any prior content and hide.
233
242
  if (!logo) {
234
243
  this.#logoEl.replaceChildren();
@@ -275,9 +275,10 @@ describe('integration-card-ui — CSS contract (source-grep)', () => {
275
275
 
276
276
  expect(CSS).toMatch(/@scope\s*\(\s*integration-card-ui\s*\)/);
277
277
  expect(CSS).toMatch(/:where\(:scope\)\s*\{/);
278
- expect(CSS).toMatch(/--integration-card-bg:/);
279
- expect(CSS).toMatch(/--integration-card-border:/);
280
- expect(CSS).toMatch(/--integration-card-radius:/);
278
+ // OD-5 sweep: token declarations use -default suffix per component-token-contract.md
279
+ expect(CSS).toMatch(/--integration-card-bg-default:/);
280
+ expect(CSS).toMatch(/--integration-card-border-default:/);
281
+ expect(CSS).toMatch(/--integration-card-radius-default:/);
281
282
  });
282
283
 
283
284
  it('keeps status-driven border tint (status="connected") inside @scope', async () => {
@@ -26,7 +26,13 @@
26
26
  box-sizing: border-box;
27
27
  display: flex;
28
28
  flex-direction: column;
29
- gap: var(--nav-gap);
29
+ /* Fallback to --nav-gap-default (cascaded from the ancestor nav-ui
30
+ where it's defined) so child nav-items get the spec spacing even
31
+ when the override token --nav-gap isn't explicitly set. Without
32
+ the fallback `var(--nav-gap)` resolved to empty → invalid gap →
33
+ items packed at 0 px stride. Reported 2026-05-25; latent since
34
+ Apr-20 snapshot. */
35
+ gap: var(--nav-gap, var(--nav-gap-default, var(--a-space-1)));
30
36
  position: relative;
31
37
  font-weight: var(--nav-group-text-weight);
32
38
  }
@@ -0,0 +1,180 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://adiaui.dev/a2ui/v0_9/components/NumberFormat.json",
4
+ "title": "NumberFormat",
5
+ "description": "Display a numeric value with locale-aware formatting — currency,\npercentage, compact (1.2K / 3.4M), unit, or plain decimal. Wraps\n`Intl.NumberFormat`. Distinct from `<input-ui type=\"number\">`\n(an INPUT primitive); this is a DISPLAY primitive — read-only, no\nform participation, no keyboard handling. Pair with `<stat-ui>` for\nKPI surfaces or use standalone inline within prose.\n",
6
+ "type": "object",
7
+ "allOf": [
8
+ {
9
+ "$ref": "common_types.json#/$defs/ComponentCommon"
10
+ },
11
+ {
12
+ "$ref": "common_types.json#/$defs/CatalogComponentCommon"
13
+ }
14
+ ],
15
+ "properties": {
16
+ "compactDisplay": {
17
+ "description": "When [notation=\"compact\"], controls the compact-form length.\n`short` = \"1.2M\" (default); `long` = \"1.2 million\".\n",
18
+ "type": "string",
19
+ "enum": [
20
+ "short",
21
+ "long"
22
+ ],
23
+ "default": "short"
24
+ },
25
+ "component": {
26
+ "const": "NumberFormat"
27
+ },
28
+ "currency": {
29
+ "description": "ISO 4217 currency code (e.g. \"USD\", \"EUR\", \"JPY\"). Required when\n[number-style=\"currency\"]; ignored otherwise.\n",
30
+ "type": "string",
31
+ "default": ""
32
+ },
33
+ "locale": {
34
+ "description": "BCP-47 locale tag for the formatter. Empty defaults to the\ndocument locale (`<html lang>`) then to browser default.\n",
35
+ "type": "string",
36
+ "default": ""
37
+ },
38
+ "maximumFractionDigits": {
39
+ "description": "Maximum fractional digits (0–20). Default `2` for decimal/\ncurrency/percent, `1` for compact notation.\n",
40
+ "type": "number",
41
+ "default": 2
42
+ },
43
+ "minimumFractionDigits": {
44
+ "description": "Minimum fractional digits (0–20). Padded with trailing zeros.\nUseful for currency display to force \".00\" suffix.\n",
45
+ "type": "number",
46
+ "default": 0
47
+ },
48
+ "notation": {
49
+ "description": "`Intl.NumberFormat` `notation` option. `standard` is the\nthousands-grouped form (1,234,567); `compact` is the abbreviated\nform (1.2M); `scientific` and `engineering` are the exponent\nforms. Defaults to `standard`.\n",
50
+ "type": "string",
51
+ "enum": [
52
+ "standard",
53
+ "compact",
54
+ "scientific",
55
+ "engineering"
56
+ ],
57
+ "default": "standard"
58
+ },
59
+ "numberStyle": {
60
+ "description": "`Intl.NumberFormat` `style` option. `decimal` (default) renders a\nplain number with locale-aware grouping. `currency` requires\n[currency] to be set. `percent` formats 0–1 as 0%–100%. `unit`\nrequires [unit] to be set (e.g. \"kilobyte\", \"celsius\").\n",
61
+ "type": "string",
62
+ "enum": [
63
+ "decimal",
64
+ "currency",
65
+ "percent",
66
+ "unit"
67
+ ],
68
+ "default": "decimal"
69
+ },
70
+ "signDisplay": {
71
+ "description": "`Intl.NumberFormat` `signDisplay` option. `auto` (default) shows\n\"−\" for negatives only; `always` shows \"+\" / \"−\"; `exceptZero`\nshows sign for non-zero only; `never` hides signs.\n",
72
+ "type": "string",
73
+ "enum": [
74
+ "auto",
75
+ "always",
76
+ "exceptZero",
77
+ "never"
78
+ ],
79
+ "default": "auto"
80
+ },
81
+ "unit": {
82
+ "description": "`Intl.NumberFormat` unit identifier (e.g. \"kilometer-per-hour\",\n\"celsius\", \"byte\"). Required when [number-style=\"unit\"]; ignored\notherwise. See MDN's NumberFormat docs for the valid set.\n",
83
+ "type": "string",
84
+ "default": ""
85
+ },
86
+ "value": {
87
+ "description": "The numeric value to format. Empty string or non-numeric value\nrenders nothing.\n",
88
+ "type": "number",
89
+ "default": 0
90
+ }
91
+ },
92
+ "required": [
93
+ "component"
94
+ ],
95
+ "unevaluatedProperties": false,
96
+ "x-adiaui": {
97
+ "anti_patterns": [
98
+ {
99
+ "fix": "<number-format-ui value=\"0.5\" number-style=\"percent\"></number-format-ui>\n",
100
+ "why": "Renders \"5,000%\" — Intl.NumberFormat percent style multiplies the\ninput by 100. value=50 means \"5000%\".\n",
101
+ "wrong": "<number-format-ui value=\"50\" number-style=\"percent\"></number-format-ui>\n"
102
+ },
103
+ {
104
+ "fix": "<number-format-ui value=\"9.99\" number-style=\"currency\" currency=\"USD\"></number-format-ui>\n",
105
+ "why": "Missing [currency] code. Renders nothing rather than producing\nmalformed currency output.\n",
106
+ "wrong": "<number-format-ui value=\"9.99\" number-style=\"currency\"></number-format-ui>\n"
107
+ }
108
+ ],
109
+ "category": "display",
110
+ "composes": [],
111
+ "events": {},
112
+ "examples": [
113
+ {
114
+ "description": "Plain locale-grouped number — default style.",
115
+ "a2ui": "[\n {\n \"id\": \"n\",\n \"component\": \"NumberFormat\",\n \"value\": 1234567.89\n }\n]\n",
116
+ "name": "decimal"
117
+ },
118
+ {
119
+ "description": "USD currency display.",
120
+ "a2ui": "[\n {\n \"id\": \"n\",\n \"component\": \"NumberFormat\",\n \"value\": 1299.99,\n \"numberStyle\": \"currency\",\n \"currency\": \"USD\"\n }\n]\n",
121
+ "name": "currency"
122
+ },
123
+ {
124
+ "description": "Fraction → percent (0.876 → 87.6%).",
125
+ "a2ui": "[\n {\n \"id\": \"n\",\n \"component\": \"NumberFormat\",\n \"value\": 0.876,\n \"numberStyle\": \"percent\",\n \"maximumFractionDigits\": 1\n }\n]\n",
126
+ "name": "percent"
127
+ },
128
+ {
129
+ "description": "Compact-form (1.2M, 3.4K).",
130
+ "a2ui": "[\n {\n \"id\": \"n\",\n \"component\": \"NumberFormat\",\n \"value\": 1234567,\n \"notation\": \"compact\"\n }\n]\n",
131
+ "name": "compact"
132
+ }
133
+ ],
134
+ "keywords": [
135
+ "number-format",
136
+ "format",
137
+ "currency",
138
+ "percent",
139
+ "percentage",
140
+ "compact",
141
+ "number",
142
+ "locale",
143
+ "intl"
144
+ ],
145
+ "name": "UINumberFormat",
146
+ "related": [
147
+ "stat",
148
+ "text",
149
+ "badge",
150
+ "input"
151
+ ],
152
+ "slots": {},
153
+ "states": [
154
+ {
155
+ "description": "Default, displaying the formatted value.",
156
+ "name": "idle"
157
+ }
158
+ ],
159
+ "status": "stable",
160
+ "synonyms": {
161
+ "currency": [
162
+ "number-format",
163
+ "money",
164
+ "price"
165
+ ],
166
+ "number": [
167
+ "number-format",
168
+ "format"
169
+ ],
170
+ "percent": [
171
+ "number-format",
172
+ "percentage"
173
+ ]
174
+ },
175
+ "tag": "number-format-ui",
176
+ "tokens": {},
177
+ "traits": [],
178
+ "version": 1
179
+ }
180
+ }