@adia-ai/web-components 0.6.35 → 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 (126) hide show
  1. package/CHANGELOG.md +56 -0
  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/combobox/combobox.css +12 -0
  20. package/components/context-menu/context-menu.a2ui.json +159 -0
  21. package/components/context-menu/context-menu.class.js +275 -0
  22. package/components/context-menu/context-menu.css +56 -0
  23. package/components/context-menu/context-menu.d.ts +70 -0
  24. package/components/context-menu/context-menu.js +17 -0
  25. package/components/context-menu/context-menu.yaml +136 -0
  26. package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
  27. package/components/date-range-picker/date-range-picker.class.js +3 -1
  28. package/components/date-range-picker/date-range-picker.css +4 -1
  29. package/components/date-range-picker/date-range-picker.yaml +14 -0
  30. package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
  31. package/components/datetime-picker/datetime-picker.class.js +3 -1
  32. package/components/datetime-picker/datetime-picker.css +7 -1
  33. package/components/datetime-picker/datetime-picker.d.ts +2 -0
  34. package/components/datetime-picker/datetime-picker.yaml +14 -0
  35. package/components/empty-state/empty-state.class.js +2 -0
  36. package/components/feed/feed.class.js +13 -5
  37. package/components/feed/feed.css +14 -0
  38. package/components/index.js +9 -0
  39. package/components/input/input.css +15 -1
  40. package/components/input/input.test.js +40 -0
  41. package/components/integration-card/integration-card.class.js +9 -0
  42. package/components/integration-card/integration-card.test.js +4 -3
  43. package/components/nav-group/nav-group.css +7 -1
  44. package/components/number-format/number-format.a2ui.json +180 -0
  45. package/components/number-format/number-format.class.js +96 -0
  46. package/components/number-format/number-format.css +18 -0
  47. package/components/number-format/number-format.d.ts +68 -0
  48. package/components/number-format/number-format.js +17 -0
  49. package/components/number-format/number-format.yaml +204 -0
  50. package/components/pagination/pagination.a2ui.json +19 -2
  51. package/components/pagination/pagination.class.js +90 -37
  52. package/components/pagination/pagination.css +32 -127
  53. package/components/pagination/pagination.d.ts +8 -2
  54. package/components/pagination/pagination.test.js +195 -0
  55. package/components/pagination/pagination.yaml +22 -1
  56. package/components/password-strength/password-strength.a2ui.json +152 -0
  57. package/components/password-strength/password-strength.class.js +157 -0
  58. package/components/password-strength/password-strength.css +80 -0
  59. package/components/password-strength/password-strength.d.ts +59 -0
  60. package/components/password-strength/password-strength.js +17 -0
  61. package/components/password-strength/password-strength.yaml +153 -0
  62. package/components/popover/popover.css +43 -23
  63. package/components/popover/popover.yaml +8 -4
  64. package/components/qr-code/QR-TEST.svg +4 -0
  65. package/components/qr-code/qr-code.a2ui.json +154 -0
  66. package/components/qr-code/qr-code.class.js +129 -0
  67. package/components/qr-code/qr-code.css +41 -0
  68. package/components/qr-code/qr-code.d.ts +83 -0
  69. package/components/qr-code/qr-code.js +17 -0
  70. package/components/qr-code/qr-code.yaml +203 -0
  71. package/components/qr-code/qr-encoder.js +633 -0
  72. package/components/relative-time/relative-time.a2ui.json +120 -0
  73. package/components/relative-time/relative-time.class.js +136 -0
  74. package/components/relative-time/relative-time.css +22 -0
  75. package/components/relative-time/relative-time.d.ts +51 -0
  76. package/components/relative-time/relative-time.js +17 -0
  77. package/components/relative-time/relative-time.yaml +133 -0
  78. package/components/search/search.class.js +2 -0
  79. package/components/segmented/segmented.class.js +5 -1
  80. package/components/select/select.class.js +4 -0
  81. package/components/skip-nav/skip-nav.a2ui.json +92 -0
  82. package/components/skip-nav/skip-nav.class.js +45 -0
  83. package/components/skip-nav/skip-nav.css +54 -0
  84. package/components/skip-nav/skip-nav.d.ts +27 -0
  85. package/components/skip-nav/skip-nav.js +12 -0
  86. package/components/skip-nav/skip-nav.yaml +68 -0
  87. package/components/slider/slider.a2ui.json +16 -1
  88. package/components/slider/slider.class.js +264 -122
  89. package/components/slider/slider.css +82 -2
  90. package/components/slider/slider.d.ts +19 -3
  91. package/components/slider/slider.test.js +55 -0
  92. package/components/slider/slider.yaml +28 -6
  93. package/components/table/table.class.js +29 -6
  94. package/components/table/table.css +31 -4
  95. package/components/table-toolbar/table-toolbar.class.js +4 -1
  96. package/components/tag/tag.a2ui.json +10 -0
  97. package/components/tag/tag.class.js +8 -1
  98. package/components/tag/tag.css +108 -20
  99. package/components/tag/tag.d.ts +14 -0
  100. package/components/tag/tag.test.js +99 -1
  101. package/components/tag/tag.yaml +20 -0
  102. package/components/tags-input/tags-input.class.js +10 -3
  103. package/components/tags-input/tags-input.css +12 -3
  104. package/components/textarea/textarea.css +10 -1
  105. package/components/toast/toast.class.js +12 -4
  106. package/components/toc/toc.a2ui.json +159 -0
  107. package/components/toc/toc.class.js +222 -0
  108. package/components/toc/toc.css +92 -0
  109. package/components/toc/toc.d.ts +61 -0
  110. package/components/toc/toc.js +17 -0
  111. package/components/toc/toc.yaml +180 -0
  112. package/components/toolbar/toolbar.class.js +3 -0
  113. package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
  114. package/components/visually-hidden/visually-hidden.class.js +14 -0
  115. package/components/visually-hidden/visually-hidden.css +25 -0
  116. package/components/visually-hidden/visually-hidden.d.ts +26 -0
  117. package/components/visually-hidden/visually-hidden.js +12 -0
  118. package/components/visually-hidden/visually-hidden.yaml +54 -0
  119. package/core/anchor.js +19 -3
  120. package/core/provider.js +19 -2
  121. package/dist/web-components.min.css +1 -1
  122. package/dist/web-components.min.js +101 -89
  123. package/package.json +1 -1
  124. package/styles/colors/semantics.css +11 -2
  125. package/styles/components.css +9 -0
  126. package/styles/resets.css +10 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `<context-menu-ui>` — Right-click activated menu — the OS-native context-menu pattern as a
3
+ web component. Distinct from `menu-ui` (which is button-triggered):
4
+ same item shape (`menu-item-ui` children), different trigger surface
5
+ (`contextmenu` event), and pointer-anchored positioning instead of
6
+ element-anchored. Pattern: WAI-APG Menu.
7
+
8
+ Two binding modes:
9
+ **A. Wrap.** Default-slot child becomes the target:
10
+ `<context-menu-ui><my-table>...</my-table>...items</context-menu-ui>`.
11
+ **B. Selector.** Point at one or more existing elements via [for]:
12
+ `<context-menu-ui for="#my-table">...items</context-menu-ui>`.
13
+
14
+ On `contextmenu` event on a target: `preventDefault()`, position the
15
+ menu at the pointer coords, show via Popover API. Touch long-press
16
+ (configurable via [long-press-ms]) does the same. Shift+F10 / Menu
17
+ key opens at the focused target's center for keyboard users.
18
+
19
+ *
20
+ * @see https://ui-kit.exe.xyz/site/components/context-menu
21
+ *
22
+ * Type declarations generated by scripts/build/dts-codegen.mjs from
23
+ * the component's `.a2ui.json` sidecar(s). Edit the source `.yaml`,
24
+ * run `npm run build:components`, then `npm run codegen:dts` to
25
+ * regenerate; or hand-author this file fully if rich event types are
26
+ * needed beyond what the yaml `events:` block can express.
27
+ */
28
+
29
+ import { UIElement } from '../../core/element.js';
30
+
31
+ export interface ContextMenuCloseEventDetail {
32
+ /** "select" | "outside" | "escape" */
33
+ reason: string;
34
+ }
35
+
36
+ export type ContextMenuCloseEvent = CustomEvent<ContextMenuCloseEventDetail>;
37
+ export interface ContextMenuOpenEventDetail {
38
+ /** The target element the menu was opened on. */
39
+ target: string;
40
+ /** Pointer x coord (viewport-relative); null for keyboard activation. */
41
+ x: number;
42
+ /** Pointer y coord; null for keyboard activation. */
43
+ y: number;
44
+ }
45
+
46
+ export type ContextMenuOpenEvent = CustomEvent<ContextMenuOpenEventDetail>;
47
+ export interface ContextMenuSelectEventDetail {
48
+ /** Selected item's text. */
49
+ text: string;
50
+ /** Selected item's value. */
51
+ value: string;
52
+ }
53
+
54
+ export type ContextMenuSelectEvent = CustomEvent<ContextMenuSelectEventDetail>;
55
+
56
+ export class UIContextMenu extends UIElement {
57
+ /** CSS selector(s) for target element(s). Empty = use default-slot child. */
58
+ for: string;
59
+ /** Programmatic open state. Set true to open at target center. */
60
+ open: boolean;
61
+
62
+ addEventListener<K extends keyof HTMLElementEventMap>(
63
+ type: K,
64
+ listener: (this: UIContextMenu, ev: HTMLElementEventMap[K]) => unknown,
65
+ options?: boolean | AddEventListenerOptions,
66
+ ): void;
67
+ addEventListener(type: 'context-menu-close', listener: (ev: ContextMenuCloseEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
68
+ addEventListener(type: 'context-menu-open', listener: (ev: ContextMenuOpenEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
69
+ addEventListener(type: 'context-menu-select', listener: (ev: ContextMenuSelectEvent) => unknown, options?: boolean | AddEventListenerOptions): void;
70
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `<context-menu-ui>` — auto-registers the tag on import.
3
+ *
4
+ * For non-side-effect class import (test isolation, tag override), use
5
+ * the `class` subpath:
6
+ *
7
+ * import { UIContextMenu } from '@adia-ai/web-components/components/context-menu/class';
8
+ *
9
+ * @see ../../USAGE.md#registration--auto-vs-explicit
10
+ */
11
+
12
+ import { defineIfFree } from '../../core/register.js';
13
+ import { UIContextMenu } from './context-menu.class.js';
14
+
15
+ defineIfFree('context-menu-ui', UIContextMenu);
16
+
17
+ export { UIContextMenu };
@@ -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
 
@@ -485,7 +487,7 @@ export class UIDateRangePicker extends UIFormElement {
485
487
  // Matches select-ui + calendar-picker-ui's anchorPopover pattern.
486
488
  this.#anchorCleanup?.();
487
489
  this.#anchorCleanup = anchorPopover(this.#triggerRef, this.#popoverRef, {
488
- placement: this.getAttribute('placement') || 'bottom-start',
490
+ placement: this.getAttribute('placement') || 'bottom',
489
491
  gap: 4,
490
492
  });
491
493
  document.addEventListener('pointerdown', this.#onOutside);
@@ -62,11 +62,14 @@
62
62
  }
63
63
 
64
64
  /* ── Block 2 — BASE ── */
65
+ /* Host is a transparent inline wrapper — trigger button-ui paints its
66
+ own surface. Mirrors the datetime-picker decision (2026-05-24): a host
67
+ background paint creates an off-axis rectangle inside field-ui because
68
+ the field-ui's value-cell expands beyond the inline-block host. */
65
69
  :scope {
66
70
  box-sizing: border-box;
67
71
  position: relative;
68
72
  display: inline-block;
69
- background: var(--date-range-picker-bg, var(--date-range-picker-bg-default));
70
73
  color: var(--date-range-picker-fg, var(--date-range-picker-fg-default));
71
74
  font-size: var(--a-ui-size);
72
75
  }
@@ -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);
@@ -38,11 +38,17 @@
38
38
  }
39
39
 
40
40
  /* ── Block 2 — BASE ── */
41
+ /* Host is a transparent inline wrapper. Painting `background` on the host
42
+ used to create an off-axis rectangle behind the trigger when the host
43
+ sat inside a field-ui (the field-ui's value-cell expanded but the
44
+ inline-block host only sized to its child). Trigger button-ui paints
45
+ its own surface — host stays transparent.
46
+ 2026-05-24 QA: "odd background colors and block element container for
47
+ inline trigger" — consumer report on /site/components/datetime-picker. */
41
48
  :scope {
42
49
  box-sizing: border-box;
43
50
  position: relative;
44
51
  display: inline-block;
45
- background: var(--datetime-picker-bg, var(--datetime-picker-bg-default));
46
52
  color: var(--datetime-picker-fg, var(--datetime-picker-fg-default));
47
53
  font-size: var(--a-ui-size);
48
54
  }
@@ -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';
@@ -121,6 +121,10 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
121
121
  outline: none;
122
122
  white-space: nowrap;
123
123
  overflow: hidden;
124
+ /* Positioning context for the [data-empty]::before placeholder
125
+ pseudo, which is taken out of inline flow so the caret renders
126
+ at the actual content-start (not after the pseudo box). */
127
+ position: relative;
124
128
  }
125
129
 
126
130
  /* Text (native input — password only) */
@@ -138,11 +142,21 @@ input-ui:not([disabled]) [slot="field"]:hover [slot="suffix"] {
138
142
  color: var(--input-placeholder-fg, var(--input-placeholder-fg-default));
139
143
  }
140
144
 
141
- /* Placeholder (contenteditable only) */
145
+ /* Placeholder (contenteditable only).
146
+ Out-of-flow positioning is load-bearing — an in-flow ::before with
147
+ `content: attr(...)` puts the pseudo box INLINE before the empty
148
+ text node, which means the caret-at-position-0 visually renders to
149
+ the RIGHT of the placeholder text (after the user types and deletes).
150
+ `position: absolute; inset: 0; padding: inherit` makes the pseudo
151
+ fill the host's content-box without occupying any inline-flow space,
152
+ so the caret renders at the actual content-start where it belongs. */
142
153
  span[slot="text"][data-empty]::before {
143
154
  content: attr(data-placeholder);
144
155
  color: var(--input-placeholder-fg, var(--input-placeholder-fg-default));
145
156
  pointer-events: none;
157
+ position: absolute;
158
+ inset: 0;
159
+ padding: inherit;
146
160
  }
147
161
 
148
162
  /* ── Number mode (type="number") ──
@@ -10,9 +10,15 @@
10
10
  */
11
11
 
12
12
  import { describe, it, expect, beforeEach } from 'vitest';
13
+ import { readFileSync } from 'node:fs';
14
+ import { fileURLToPath } from 'node:url';
15
+ import { dirname, resolve } from 'node:path';
13
16
  import '../../core/element.js';
14
17
  import './input.js';
15
18
 
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const INPUT_CSS = readFileSync(resolve(__dirname, 'input.css'), 'utf8');
21
+
16
22
  const tick = () => new Promise((r) => queueMicrotask(r));
17
23
 
18
24
  function mount(html) {
@@ -220,3 +226,37 @@ describe('input-ui — §220 (v0.5.9) throttle parity', () => {
220
226
  expect(events).toEqual([]);
221
227
  });
222
228
  });
229
+
230
+ // ── Placeholder caret-position regression guard ─────────────────────
231
+ //
232
+ // Per the v0.6.35 bug report: with the `::before { content: attr(data-placeholder) }`
233
+ // pseudo rendered in inline flow, typing-then-deleting left the caret
234
+ // visually at the END of the placeholder text (not at position 0) because
235
+ // the in-flow pseudo box pushed the caret rendering position to its right.
236
+ // Fix: pseudo is `position: absolute` so it doesn't occupy inline-flow
237
+ // space; the host carries `position: relative` as its anchor.
238
+ // These tests assert the CSS-source contract that prevents accidental
239
+ // revert to the in-flow pseudo shape.
240
+
241
+ describe('input-ui — CSS source contract: placeholder pseudo is out of flow', () => {
242
+ it('host [slot="text"] declares position: relative (pseudo anchor)', () => {
243
+ expect(INPUT_CSS).toMatch(
244
+ /\[slot="text"\]\s*\{[^}]*position:\s*relative/s
245
+ );
246
+ });
247
+
248
+ it('[data-empty]::before is position: absolute (not inline-flow)', () => {
249
+ expect(INPUT_CSS).toMatch(
250
+ /span\[slot="text"\]\[data-empty\]::before\s*\{[^}]*position:\s*absolute/s
251
+ );
252
+ });
253
+
254
+ it('[data-empty]::before fills the host content-box (inset: 0 + padding: inherit)', () => {
255
+ expect(INPUT_CSS).toMatch(
256
+ /span\[slot="text"\]\[data-empty\]::before\s*\{[^}]*inset:\s*0/s
257
+ );
258
+ expect(INPUT_CSS).toMatch(
259
+ /span\[slot="text"\]\[data-empty\]::before\s*\{[^}]*padding:\s*inherit/s
260
+ );
261
+ });
262
+ });
@@ -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
  }