@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.
- package/CHANGELOG.md +56 -0
- package/components/badge/badge.a2ui.json +10 -0
- package/components/badge/badge.css +70 -0
- package/components/badge/badge.yaml +20 -0
- package/components/blockquote/blockquote.a2ui.json +121 -0
- package/components/blockquote/blockquote.class.js +68 -0
- package/components/blockquote/blockquote.css +46 -0
- package/components/blockquote/blockquote.d.ts +31 -0
- package/components/blockquote/blockquote.js +17 -0
- package/components/blockquote/blockquote.yaml +124 -0
- package/components/button/button.css +11 -3
- package/components/calendar-picker/calendar-picker.a2ui.json +15 -0
- package/components/calendar-picker/calendar-picker.class.js +7 -1
- package/components/calendar-picker/calendar-picker.yaml +14 -0
- package/components/color-input/color-input.a2ui.json +2 -2
- package/components/color-input/color-input.class.js +9 -2
- package/components/color-input/color-input.yaml +2 -2
- package/components/combobox/combobox.class.js +4 -0
- package/components/combobox/combobox.css +12 -0
- package/components/context-menu/context-menu.a2ui.json +159 -0
- package/components/context-menu/context-menu.class.js +275 -0
- package/components/context-menu/context-menu.css +56 -0
- package/components/context-menu/context-menu.d.ts +70 -0
- package/components/context-menu/context-menu.js +17 -0
- package/components/context-menu/context-menu.yaml +136 -0
- package/components/date-range-picker/date-range-picker.a2ui.json +15 -0
- package/components/date-range-picker/date-range-picker.class.js +3 -1
- package/components/date-range-picker/date-range-picker.css +4 -1
- package/components/date-range-picker/date-range-picker.yaml +14 -0
- package/components/datetime-picker/datetime-picker.a2ui.json +15 -0
- package/components/datetime-picker/datetime-picker.class.js +3 -1
- package/components/datetime-picker/datetime-picker.css +7 -1
- package/components/datetime-picker/datetime-picker.d.ts +2 -0
- package/components/datetime-picker/datetime-picker.yaml +14 -0
- package/components/empty-state/empty-state.class.js +2 -0
- package/components/feed/feed.class.js +13 -5
- package/components/feed/feed.css +14 -0
- package/components/index.js +9 -0
- package/components/input/input.css +15 -1
- package/components/input/input.test.js +40 -0
- package/components/integration-card/integration-card.class.js +9 -0
- package/components/integration-card/integration-card.test.js +4 -3
- package/components/nav-group/nav-group.css +7 -1
- package/components/number-format/number-format.a2ui.json +180 -0
- package/components/number-format/number-format.class.js +96 -0
- package/components/number-format/number-format.css +18 -0
- package/components/number-format/number-format.d.ts +68 -0
- package/components/number-format/number-format.js +17 -0
- package/components/number-format/number-format.yaml +204 -0
- package/components/pagination/pagination.a2ui.json +19 -2
- package/components/pagination/pagination.class.js +90 -37
- package/components/pagination/pagination.css +32 -127
- package/components/pagination/pagination.d.ts +8 -2
- package/components/pagination/pagination.test.js +195 -0
- package/components/pagination/pagination.yaml +22 -1
- package/components/password-strength/password-strength.a2ui.json +152 -0
- package/components/password-strength/password-strength.class.js +157 -0
- package/components/password-strength/password-strength.css +80 -0
- package/components/password-strength/password-strength.d.ts +59 -0
- package/components/password-strength/password-strength.js +17 -0
- package/components/password-strength/password-strength.yaml +153 -0
- package/components/popover/popover.css +43 -23
- package/components/popover/popover.yaml +8 -4
- package/components/qr-code/QR-TEST.svg +4 -0
- package/components/qr-code/qr-code.a2ui.json +154 -0
- package/components/qr-code/qr-code.class.js +129 -0
- package/components/qr-code/qr-code.css +41 -0
- package/components/qr-code/qr-code.d.ts +83 -0
- package/components/qr-code/qr-code.js +17 -0
- package/components/qr-code/qr-code.yaml +203 -0
- package/components/qr-code/qr-encoder.js +633 -0
- package/components/relative-time/relative-time.a2ui.json +120 -0
- package/components/relative-time/relative-time.class.js +136 -0
- package/components/relative-time/relative-time.css +22 -0
- package/components/relative-time/relative-time.d.ts +51 -0
- package/components/relative-time/relative-time.js +17 -0
- package/components/relative-time/relative-time.yaml +133 -0
- package/components/search/search.class.js +2 -0
- package/components/segmented/segmented.class.js +5 -1
- package/components/select/select.class.js +4 -0
- package/components/skip-nav/skip-nav.a2ui.json +92 -0
- package/components/skip-nav/skip-nav.class.js +45 -0
- package/components/skip-nav/skip-nav.css +54 -0
- package/components/skip-nav/skip-nav.d.ts +27 -0
- package/components/skip-nav/skip-nav.js +12 -0
- package/components/skip-nav/skip-nav.yaml +68 -0
- package/components/slider/slider.a2ui.json +16 -1
- package/components/slider/slider.class.js +264 -122
- package/components/slider/slider.css +82 -2
- package/components/slider/slider.d.ts +19 -3
- package/components/slider/slider.test.js +55 -0
- package/components/slider/slider.yaml +28 -6
- package/components/table/table.class.js +29 -6
- package/components/table/table.css +31 -4
- package/components/table-toolbar/table-toolbar.class.js +4 -1
- package/components/tag/tag.a2ui.json +10 -0
- package/components/tag/tag.class.js +8 -1
- package/components/tag/tag.css +108 -20
- package/components/tag/tag.d.ts +14 -0
- package/components/tag/tag.test.js +99 -1
- package/components/tag/tag.yaml +20 -0
- package/components/tags-input/tags-input.class.js +10 -3
- package/components/tags-input/tags-input.css +12 -3
- package/components/textarea/textarea.css +10 -1
- package/components/toast/toast.class.js +12 -4
- package/components/toc/toc.a2ui.json +159 -0
- package/components/toc/toc.class.js +222 -0
- package/components/toc/toc.css +92 -0
- package/components/toc/toc.d.ts +61 -0
- package/components/toc/toc.js +17 -0
- package/components/toc/toc.yaml +180 -0
- package/components/toolbar/toolbar.class.js +3 -0
- package/components/visually-hidden/visually-hidden.a2ui.json +71 -0
- package/components/visually-hidden/visually-hidden.class.js +14 -0
- package/components/visually-hidden/visually-hidden.css +25 -0
- package/components/visually-hidden/visually-hidden.d.ts +26 -0
- package/components/visually-hidden/visually-hidden.js +12 -0
- package/components/visually-hidden/visually-hidden.yaml +54 -0
- package/core/anchor.js +19 -3
- package/core/provider.js +19 -2
- package/dist/web-components.min.css +1 -1
- package/dist/web-components.min.js +101 -89
- package/package.json +1 -1
- package/styles/colors/semantics.css +11 -2
- package/styles/components.css +9 -0
- 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
|
|
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
|
|
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
|
-
*
|
|
301
|
-
*
|
|
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
|
|
334
|
-
|
|
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', '');
|
package/components/feed/feed.css
CHANGED
|
@@ -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);
|
package/components/index.js
CHANGED
|
@@ -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
|
-
|
|
279
|
-
expect(CSS).toMatch(/--integration-card-
|
|
280
|
-
expect(CSS).toMatch(/--integration-card-
|
|
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
|
|
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
|
}
|