@adia-ai/web-components 0.0.18 → 0.0.20
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/components/accordion/accordion.css +101 -102
- package/components/agent-feedback-bar/agent-feedback-bar.js +8 -8
- package/components/agent-questions/agent-questions.css +2 -1
- package/components/agent-questions/agent-questions.js +6 -6
- package/components/agent-reasoning/agent-reasoning.js +20 -5
- package/components/agent-trace/agent-trace.a2ui.json +5 -5
- package/components/agent-trace/agent-trace.js +7 -5
- package/components/agent-trace/agent-trace.yaml +2 -2
- package/components/alert/alert.a2ui.json +1 -2
- package/components/alert/alert.css +4 -4
- package/components/alert/alert.yaml +1 -2
- package/components/avatar/avatar.a2ui.json +3 -3
- package/components/avatar/avatar.js +10 -0
- package/components/avatar/avatar.yaml +6 -6
- package/components/button/button.a2ui.json +14 -2
- package/components/button/button.css +19 -2
- package/components/button/button.js +1 -0
- package/components/button/button.yaml +20 -2
- package/components/calendar-picker/calendar-picker.css +2 -1
- package/components/calendar-picker/calendar-picker.js +12 -1
- package/components/chart/chart.css +11 -11
- package/components/chart/chart.js +26 -18
- package/components/chart-legend/chart-legend.a2ui.json +2 -2
- package/components/chart-legend/chart-legend.js +4 -1
- package/components/chart-legend/chart-legend.yaml +2 -2
- package/components/chat/chat-input.js +13 -5
- package/components/chat/chat.a2ui.json +2 -2
- package/components/chat/chat.js +14 -3
- package/components/chat/chat.yaml +2 -2
- package/components/code/code.css +16 -6
- package/components/command/command.js +9 -1
- package/components/field/field.a2ui.json +0 -5
- package/components/field/field.css +2 -2
- package/components/field/field.js +53 -5
- package/components/field/field.yaml +5 -8
- package/components/heatmap/heatmap.css +32 -23
- package/components/input/input.js +30 -1
- package/components/kbd/kbd.a2ui.json +5 -1
- package/components/kbd/kbd.yaml +5 -1
- package/components/menu/menu.css +20 -8
- package/components/menu/menu.js +9 -1
- package/components/modal/modal.css +101 -108
- package/components/noodles/noodles.js +25 -8
- package/components/pipeline-status/pipeline-status.css +4 -4
- package/components/pipeline-status/pipeline-status.js +6 -4
- package/components/popover/popover.js +4 -0
- package/components/progress-row/progress-row.a2ui.json +3 -2
- package/components/progress-row/progress-row.yaml +2 -1
- package/components/range/range.js +7 -0
- package/components/richtext/richtext.css +2 -2
- package/components/richtext/richtext.js +4 -1
- package/components/segment/segment.css +1 -1
- package/components/segmented/segmented.js +7 -1
- package/components/select/select.css +7 -4
- package/components/slider/slider.js +15 -8
- package/components/stepper/stepper.css +181 -144
- package/components/stepper/stepper.js +5 -2
- package/components/swiper/swiper.a2ui.json +3 -3
- package/components/swiper/swiper.css +11 -77
- package/components/swiper/swiper.js +6 -5
- package/components/swiper/swiper.yaml +3 -3
- package/components/switch/switch.a2ui.json +8 -1
- package/components/switch/switch.yaml +8 -1
- package/components/table/table.js +9 -1
- package/components/table-toolbar/table-toolbar.a2ui.json +21 -21
- package/components/table-toolbar/table-toolbar.css +32 -91
- package/components/table-toolbar/table-toolbar.js +219 -86
- package/components/table-toolbar/table-toolbar.yaml +21 -12
- package/components/tabs/tabs.css +3 -2
- package/components/tabs/tabs.js +7 -1
- package/components/tag/tag.a2ui.json +2 -2
- package/components/tag/tag.yaml +2 -2
- package/components/timeline/timeline.css +244 -204
- package/components/timeline/timeline.js +1 -3
- package/components/toast/toast.a2ui.json +2 -3
- package/components/toast/toast.yaml +5 -3
- package/components/toolbar/toolbar.css +6 -1
- package/components/toolbar/toolbar.js +10 -2
- package/components/tooltip/tooltip.css +8 -2
- package/components/tooltip/tooltip.js +12 -14
- package/components/tree/tree.css +21 -0
- package/core/icons.js +14 -0
- package/core/polyfills.js +17 -7
- package/package.json +1 -1
- package/patterns/a2ui-root/a2ui-root.js +21 -14
- package/patterns/app-shell/css/app-shell.main.css +30 -1
- package/patterns/app-shell/css/app-shell.tokens.css +1 -0
- package/patterns/gen-ui/gen-ui.js +1 -1
- package/styles/colors/semantics.css +59 -2
- package/styles/tokens.css +16 -12
|
@@ -38,11 +38,21 @@ class AdiaField extends AdiaElement {
|
|
|
38
38
|
static properties = {
|
|
39
39
|
label: { type: String, default: '', reflect: true },
|
|
40
40
|
hint: { type: String, default: '', reflect: true },
|
|
41
|
-
error: { type: String, default: '', reflect: true },
|
|
42
41
|
required: { type: Boolean, default: false, reflect: true },
|
|
43
42
|
inline: { type: Boolean, default: false, reflect: true },
|
|
44
43
|
};
|
|
45
44
|
|
|
45
|
+
// The validation message lives on the child control (AdiaFormElement.error
|
|
46
|
+
// is the single source of truth — validity is a property of the value, not
|
|
47
|
+
// of the layout wrapper). field-ui mirrors the child's error string into
|
|
48
|
+
// its own [data-field-error] slot so the validation message renders below
|
|
49
|
+
// the control. See docs/specs/component-token-contract.md "Toggle state
|
|
50
|
+
// naming" / open decisions resolution log.
|
|
51
|
+
get error() {
|
|
52
|
+
const ctrl = this.#findControl();
|
|
53
|
+
return ctrl?.error ?? '';
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
static template = () => null;
|
|
47
57
|
|
|
48
58
|
#labelEl = null;
|
|
@@ -69,17 +79,22 @@ class AdiaField extends AdiaElement {
|
|
|
69
79
|
this.#ensureHintElement();
|
|
70
80
|
this.#ensureErrorElement();
|
|
71
81
|
this.#bindForToControl();
|
|
82
|
+
this.#bindErrorListener();
|
|
72
83
|
this.#labelEl?.addEventListener('click', this.#onLabelClick);
|
|
73
84
|
this.#mo = new MutationObserver(() => {
|
|
74
85
|
this.#bindForToControl();
|
|
86
|
+
this.#bindErrorListener();
|
|
75
87
|
this.#wireAriaDescribedBy();
|
|
88
|
+
this.#syncErrorMessage();
|
|
76
89
|
});
|
|
77
90
|
this.#mo.observe(this, { childList: true });
|
|
91
|
+
this.#syncErrorMessage();
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
disconnected() {
|
|
81
95
|
this.#mo?.disconnect();
|
|
82
96
|
this.#mo = null;
|
|
97
|
+
this.#unbindErrorListener();
|
|
83
98
|
this.#labelEl?.removeEventListener('click', this.#onLabelClick);
|
|
84
99
|
this.#labelEl = null;
|
|
85
100
|
this.#labelMark = null;
|
|
@@ -96,11 +111,44 @@ class AdiaField extends AdiaElement {
|
|
|
96
111
|
this.#hintEl.textContent = this.hint || '';
|
|
97
112
|
this.#hintEl.hidden = !this.hint || Boolean(this.error);
|
|
98
113
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
this.#syncErrorMessage();
|
|
115
|
+
this.#wireAriaDescribedBy();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Mirror child.error → #errorEl. Called on render, MutationObserver
|
|
119
|
+
// changes, and the child's `invalid`/`change`/`input` events.
|
|
120
|
+
#syncErrorMessage() {
|
|
121
|
+
if (!this.#errorEl) return;
|
|
122
|
+
const msg = this.error;
|
|
123
|
+
this.#errorEl.textContent = msg || '';
|
|
124
|
+
this.#errorEl.hidden = !msg;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#onChildEvent = () => {
|
|
128
|
+
this.#syncErrorMessage();
|
|
103
129
|
this.#wireAriaDescribedBy();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
#boundErrorTarget = null;
|
|
133
|
+
|
|
134
|
+
#bindErrorListener() {
|
|
135
|
+
const ctrl = this.#findControl();
|
|
136
|
+
if (ctrl === this.#boundErrorTarget) return;
|
|
137
|
+
this.#unbindErrorListener();
|
|
138
|
+
if (!ctrl) return;
|
|
139
|
+
this.#boundErrorTarget = ctrl;
|
|
140
|
+
ctrl.addEventListener('invalid', this.#onChildEvent, true);
|
|
141
|
+
ctrl.addEventListener('change', this.#onChildEvent);
|
|
142
|
+
ctrl.addEventListener('input', this.#onChildEvent);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#unbindErrorListener() {
|
|
146
|
+
const ctrl = this.#boundErrorTarget;
|
|
147
|
+
if (!ctrl) return;
|
|
148
|
+
ctrl.removeEventListener('invalid', this.#onChildEvent, true);
|
|
149
|
+
ctrl.removeEventListener('change', this.#onChildEvent);
|
|
150
|
+
ctrl.removeEventListener('input', this.#onChildEvent);
|
|
151
|
+
this.#boundErrorTarget = null;
|
|
104
152
|
}
|
|
105
153
|
|
|
106
154
|
// ── Private ───────────────────────────────────────────────────────
|
|
@@ -28,14 +28,11 @@ props:
|
|
|
28
28
|
type: string
|
|
29
29
|
default: ""
|
|
30
30
|
reflect: true
|
|
31
|
-
error
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
type: string
|
|
37
|
-
default: ""
|
|
38
|
-
reflect: true
|
|
31
|
+
# `error` is mirrored from the slotted control's `error` property.
|
|
32
|
+
# AdiaFormElement.error is the source of truth; field-ui renders the
|
|
33
|
+
# message below the control in danger style, role="alert" so screen
|
|
34
|
+
# readers announce changes. There is no field-level error attribute —
|
|
35
|
+
# set the message on the control: `<field-ui><input-ui error="…"></input-ui></field-ui>`.
|
|
39
36
|
required:
|
|
40
37
|
description: >-
|
|
41
38
|
Renders a "*" marker on the label. Does not itself enforce
|
|
@@ -14,14 +14,15 @@
|
|
|
14
14
|
--heatmap-label: var(--a-fg-subtle);
|
|
15
15
|
|
|
16
16
|
/* ── Data ramp (5 buckets) ──
|
|
17
|
-
Sequential tint scale from the
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
--heatmap-bucket-
|
|
22
|
-
--heatmap-bucket-
|
|
23
|
-
--heatmap-bucket-
|
|
24
|
-
--heatmap-bucket-
|
|
17
|
+
Sequential tint scale, sourced from the L2 `--a-{family}-bucket-N`
|
|
18
|
+
semantic aliases (see styles/colors/semantics.css §BUCKETS). The
|
|
19
|
+
L2 layer handles light-dark() polarity so step 0 reads as
|
|
20
|
+
low-intensity in either scheme without per-component math. */
|
|
21
|
+
--heatmap-bucket-0: var(--a-accent-bucket-0);
|
|
22
|
+
--heatmap-bucket-1: var(--a-accent-bucket-1);
|
|
23
|
+
--heatmap-bucket-2: var(--a-accent-bucket-2);
|
|
24
|
+
--heatmap-bucket-3: var(--a-accent-bucket-3);
|
|
25
|
+
--heatmap-bucket-4: var(--a-accent-bucket-4);
|
|
25
26
|
|
|
26
27
|
/* ── Legend ── */
|
|
27
28
|
--heatmap-legend-size: var(--a-caption-size);
|
|
@@ -121,26 +122,34 @@
|
|
|
121
122
|
/* ── Color-scheme variants (token overrides only) ── */
|
|
122
123
|
|
|
123
124
|
:scope[color-scheme="success"] {
|
|
124
|
-
--heatmap-bucket-0: var(--a-success-
|
|
125
|
-
--heatmap-bucket-1: var(--a-success-
|
|
126
|
-
--heatmap-bucket-2: var(--a-success-
|
|
127
|
-
--heatmap-bucket-3: var(--a-success-
|
|
128
|
-
--heatmap-bucket-4: var(--a-success-
|
|
125
|
+
--heatmap-bucket-0: var(--a-success-bucket-0);
|
|
126
|
+
--heatmap-bucket-1: var(--a-success-bucket-1);
|
|
127
|
+
--heatmap-bucket-2: var(--a-success-bucket-2);
|
|
128
|
+
--heatmap-bucket-3: var(--a-success-bucket-3);
|
|
129
|
+
--heatmap-bucket-4: var(--a-success-bucket-4);
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
:scope[color-scheme="warning"] {
|
|
132
|
-
--heatmap-bucket-0: var(--a-warning-
|
|
133
|
-
--heatmap-bucket-1: var(--a-warning-
|
|
134
|
-
--heatmap-bucket-2: var(--a-warning-
|
|
135
|
-
--heatmap-bucket-3: var(--a-warning-
|
|
136
|
-
--heatmap-bucket-4: var(--a-warning-
|
|
133
|
+
--heatmap-bucket-0: var(--a-warning-bucket-0);
|
|
134
|
+
--heatmap-bucket-1: var(--a-warning-bucket-1);
|
|
135
|
+
--heatmap-bucket-2: var(--a-warning-bucket-2);
|
|
136
|
+
--heatmap-bucket-3: var(--a-warning-bucket-3);
|
|
137
|
+
--heatmap-bucket-4: var(--a-warning-bucket-4);
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
:scope[color-scheme="danger"] {
|
|
140
|
-
--heatmap-bucket-0: var(--a-danger-
|
|
141
|
-
--heatmap-bucket-1: var(--a-danger-
|
|
142
|
-
--heatmap-bucket-2: var(--a-danger-
|
|
143
|
-
--heatmap-bucket-3: var(--a-danger-
|
|
144
|
-
--heatmap-bucket-4: var(--a-danger-
|
|
141
|
+
--heatmap-bucket-0: var(--a-danger-bucket-0);
|
|
142
|
+
--heatmap-bucket-1: var(--a-danger-bucket-1);
|
|
143
|
+
--heatmap-bucket-2: var(--a-danger-bucket-2);
|
|
144
|
+
--heatmap-bucket-3: var(--a-danger-bucket-3);
|
|
145
|
+
--heatmap-bucket-4: var(--a-danger-bucket-4);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
:scope[color-scheme="info"] {
|
|
149
|
+
--heatmap-bucket-0: var(--a-info-bucket-0);
|
|
150
|
+
--heatmap-bucket-1: var(--a-info-bucket-1);
|
|
151
|
+
--heatmap-bucket-2: var(--a-info-bucket-2);
|
|
152
|
+
--heatmap-bucket-3: var(--a-info-bucket-3);
|
|
153
|
+
--heatmap-bucket-4: var(--a-info-bucket-4);
|
|
145
154
|
}
|
|
146
155
|
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { AdiaFormElement } from '../../core/form.js';
|
|
19
|
-
import { isIconName } from '../../core/icons.js';
|
|
19
|
+
import { isIconName, whenIconRegistryReady } from '../../core/icons.js';
|
|
20
20
|
|
|
21
21
|
const renderAffix = (v) => isIconName(v)
|
|
22
22
|
? `<icon-ui name="${v}"></icon-ui>`
|
|
@@ -86,6 +86,35 @@ class AdiaInput extends AdiaFormElement {
|
|
|
86
86
|
this.#textEl.addEventListener('blur', this.#onBlur);
|
|
87
87
|
this.#textEl.addEventListener('paste', this.#onPaste);
|
|
88
88
|
}
|
|
89
|
+
|
|
90
|
+
// In non-Vite static deploys, the icon registry loads asynchronously
|
|
91
|
+
// after the manifest fetch resolves. If our prefix/suffix were checked
|
|
92
|
+
// by isIconName() during that window, kebab-case icon names like
|
|
93
|
+
// "magnifying-glass" got baked into the DOM as literal text. Re-evaluate
|
|
94
|
+
// once the registry is ready and promote text-rendered affixes to
|
|
95
|
+
// <icon-ui>. (No-op on Vite dev where the promise resolves synchronously.)
|
|
96
|
+
if (this.prefix || this.suffix) {
|
|
97
|
+
whenIconRegistryReady.then(() => this.#promoteAffixes());
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#promoteAffixes() {
|
|
102
|
+
if (!this.isConnected) return;
|
|
103
|
+
for (const which of ['prefix', 'suffix']) {
|
|
104
|
+
const value = this[which];
|
|
105
|
+
if (!value) continue;
|
|
106
|
+
const slot = this.querySelector(`:scope [slot="${which}"]`);
|
|
107
|
+
if (!slot) continue;
|
|
108
|
+
// Already an <icon-ui> — nothing to do.
|
|
109
|
+
if (slot.querySelector(':scope > icon-ui')) continue;
|
|
110
|
+
// Was rendered as text and the value is now a known icon — replace.
|
|
111
|
+
if (isIconName(value)) {
|
|
112
|
+
slot.replaceChildren();
|
|
113
|
+
const icon = document.createElement('icon-ui');
|
|
114
|
+
icon.setAttribute('name', value);
|
|
115
|
+
slot.appendChild(icon);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
89
118
|
}
|
|
90
119
|
|
|
91
120
|
render() {
|
package/components/kbd/kbd.yaml
CHANGED
|
@@ -9,9 +9,13 @@ version: 1
|
|
|
9
9
|
description: Keyboard key cap. Purely decorative, content from innerHTML.
|
|
10
10
|
props:
|
|
11
11
|
size:
|
|
12
|
-
description:
|
|
12
|
+
description: Sizing scale (compact tier — sm / md only).
|
|
13
13
|
type: string
|
|
14
14
|
default: ""
|
|
15
|
+
enum:
|
|
16
|
+
- sm
|
|
17
|
+
- md
|
|
18
|
+
reflect: true
|
|
15
19
|
events: {}
|
|
16
20
|
slots:
|
|
17
21
|
default:
|
package/components/menu/menu.css
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
@scope (menu-ui) {
|
|
2
|
+
:where(:scope) {
|
|
3
|
+
--menu-popover-padding: var(--a-space-1);
|
|
4
|
+
--menu-popover-border: var(--a-border-subtle);
|
|
5
|
+
--menu-popover-radius: var(--a-radius-lg);
|
|
6
|
+
--menu-popover-bg: var(--a-bg-subtle);
|
|
7
|
+
--menu-popover-shadow: var(--a-shadow-lg);
|
|
8
|
+
--menu-popover-min-width: 10rem;
|
|
9
|
+
--menu-popover-font-size: var(--a-ui-size);
|
|
10
|
+
--menu-popover-fg: var(--a-fg);
|
|
11
|
+
}
|
|
12
|
+
|
|
2
13
|
:scope {
|
|
3
14
|
box-sizing: border-box;
|
|
4
15
|
display: inline-flex;
|
|
5
16
|
position: relative;
|
|
6
17
|
}
|
|
7
|
-
}
|
|
8
18
|
|
|
9
|
-
/* Items/dividers in Light DOM are hidden unless they've been adopted
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
/* Items/dividers in Light DOM are hidden unless they've been adopted
|
|
20
|
+
into the popover on open. Popover API also hides the popover itself
|
|
21
|
+
when closed. */
|
|
22
|
+
:scope > menu-item-ui,
|
|
23
|
+
:scope > menu-divider-ui {
|
|
24
|
+
display: none;
|
|
25
|
+
}
|
|
14
26
|
}
|
|
15
27
|
|
|
16
28
|
/* ── Top-layer: cannot inherit component tokens ── */
|
|
@@ -41,10 +53,10 @@ menu-ui [data-menu-popover] {
|
|
|
41
53
|
--menu-item-fg: var(--a-fg-subtle);
|
|
42
54
|
--menu-item-fg-hover: var(--a-fg);
|
|
43
55
|
--menu-item-bg-hover: var(--a-bg-muted);
|
|
44
|
-
--menu-item-fg-disabled: var(--a-fg-disabled
|
|
56
|
+
--menu-item-fg-disabled: var(--a-fg-disabled);
|
|
45
57
|
--menu-item-icon-fg: var(--a-fg-muted);
|
|
46
58
|
--menu-item-icon-fg-hover: var(--a-fg);
|
|
47
|
-
--menu-item-icon-disabled: var(--a-fg-disabled
|
|
59
|
+
--menu-item-icon-disabled: var(--a-fg-disabled);
|
|
48
60
|
--menu-item-danger-fg: var(--a-danger-bg);
|
|
49
61
|
--menu-item-danger-bg: var(--a-danger-muted);
|
|
50
62
|
|
package/components/menu/menu.js
CHANGED
|
@@ -31,6 +31,7 @@ class AdiaMenu extends AdiaElement {
|
|
|
31
31
|
#popover = null;
|
|
32
32
|
#bound = false;
|
|
33
33
|
#rafId = null;
|
|
34
|
+
#focusRaf = null;
|
|
34
35
|
|
|
35
36
|
connected() {
|
|
36
37
|
if (!this.#bound) {
|
|
@@ -86,7 +87,10 @@ class AdiaMenu extends AdiaElement {
|
|
|
86
87
|
// Focus first enabled item (only when opened via keyboard).
|
|
87
88
|
if (this.#openedByKeyboard) {
|
|
88
89
|
this.#openedByKeyboard = false;
|
|
89
|
-
requestAnimationFrame(() =>
|
|
90
|
+
this.#focusRaf = requestAnimationFrame(() => {
|
|
91
|
+
this.#focusRaf = null;
|
|
92
|
+
this.#focusItem(0);
|
|
93
|
+
});
|
|
90
94
|
}
|
|
91
95
|
|
|
92
96
|
this.#rafId = requestAnimationFrame(() => {
|
|
@@ -112,6 +116,10 @@ class AdiaMenu extends AdiaElement {
|
|
|
112
116
|
cancelAnimationFrame(this.#rafId);
|
|
113
117
|
this.#rafId = null;
|
|
114
118
|
}
|
|
119
|
+
if (this.#focusRaf != null) {
|
|
120
|
+
cancelAnimationFrame(this.#focusRaf);
|
|
121
|
+
this.#focusRaf = null;
|
|
122
|
+
}
|
|
115
123
|
document.removeEventListener('pointerdown', this.#onOutside);
|
|
116
124
|
document.removeEventListener('keydown', this.#onDocKey, true);
|
|
117
125
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
-
MODAL-
|
|
2
|
+
MODAL-UI — Centered dialog overlay using native <dialog>.
|
|
3
|
+
|
|
4
|
+
<dialog> content stays in DOM tree → custom properties inherit
|
|
5
|
+
normally → all rules live INSIDE @scope (modal-ui), parity with
|
|
6
|
+
drawer-ui. See docs/specs/component-token-contract.md
|
|
7
|
+
§"Top-layer rules — when to keep them inside @scope".
|
|
3
8
|
═══════════════════════════════════════════════════════════════ */
|
|
4
9
|
|
|
5
10
|
@scope (modal-ui) {
|
|
@@ -33,121 +38,109 @@
|
|
|
33
38
|
:scope[size="sm"] { --modal-width: min(24rem, 90vw); }
|
|
34
39
|
:scope[size="md"] { --modal-width: min(32rem, 90vw); }
|
|
35
40
|
:scope[size="lg"] { --modal-width: min(48rem, 90vw); }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/* ═══════ Closed ═══════ */
|
|
39
|
-
|
|
40
|
-
modal-ui > [slot="dialog"]:not([open]) { display: none; }
|
|
41
|
-
|
|
42
|
-
/* ═══════ Full-viewport dialog shell ═══════ */
|
|
43
|
-
|
|
44
|
-
modal-ui > [slot="dialog"][open] {
|
|
45
|
-
box-sizing: border-box;
|
|
46
|
-
position: fixed;
|
|
47
|
-
inset: 0;
|
|
48
|
-
width: 100%;
|
|
49
|
-
height: 100%;
|
|
50
|
-
max-width: none;
|
|
51
|
-
max-height: none;
|
|
52
|
-
margin: 0;
|
|
53
|
-
padding: 0;
|
|
54
|
-
border: none;
|
|
55
|
-
background: transparent;
|
|
56
|
-
overflow: clip;
|
|
57
|
-
display: flex;
|
|
58
|
-
align-items: center;
|
|
59
|
-
justify-content: center;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/* ═══════ Backdrop ═══════ */
|
|
63
|
-
|
|
64
|
-
modal-ui > [slot="dialog"]::backdrop {
|
|
65
|
-
background: var(--modal-backdrop);
|
|
66
|
-
opacity: 0;
|
|
67
|
-
transition: opacity var(--modal-duration) var(--modal-easing);
|
|
68
|
-
}
|
|
69
|
-
modal-ui > [slot="dialog"][data-open]::backdrop { opacity: 1; }
|
|
70
|
-
modal-ui > [slot="dialog"][data-closing]::backdrop { opacity: 0; }
|
|
71
|
-
|
|
72
|
-
/* ── Top-layer: cannot inherit component tokens ── */
|
|
73
|
-
|
|
74
|
-
/* ═══════ Content panel ═══════ */
|
|
75
|
-
|
|
76
|
-
modal-ui > [slot="dialog"] > [slot="panel"] {
|
|
77
|
-
box-sizing: border-box;
|
|
78
|
-
display: flex;
|
|
79
|
-
flex-direction: column;
|
|
80
|
-
width: var(--modal-width);
|
|
81
|
-
max-height: 85vh;
|
|
82
|
-
background: var(--modal-bg);
|
|
83
|
-
color: var(--modal-header-fg);
|
|
84
|
-
font-family: var(--a-font-family); /* top-layer: --a-* required */
|
|
85
|
-
border: 1px solid var(--modal-border);
|
|
86
|
-
border-radius: var(--modal-radius);
|
|
87
|
-
box-shadow: var(--modal-shadow);
|
|
88
|
-
overflow: hidden;
|
|
89
|
-
opacity: 0;
|
|
90
|
-
transform: scale(0.95);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/* ═══════ Open animation ═══════ */
|
|
94
|
-
|
|
95
|
-
modal-ui > [slot="dialog"][data-open] > [slot="panel"] {
|
|
96
|
-
transition: transform var(--modal-duration) var(--modal-easing),
|
|
97
|
-
opacity var(--modal-duration) var(--modal-easing);
|
|
98
|
-
transform: scale(1);
|
|
99
|
-
opacity: 1;
|
|
100
|
-
}
|
|
101
41
|
|
|
102
|
-
/* ═══════
|
|
42
|
+
/* ═══════ Closed ═══════ */
|
|
43
|
+
:scope > [slot="dialog"]:not([open]) { display: none; }
|
|
103
44
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
45
|
+
/* ═══════ Full-viewport dialog shell ═══════ */
|
|
46
|
+
:scope > [slot="dialog"][open] {
|
|
47
|
+
box-sizing: border-box;
|
|
48
|
+
position: fixed;
|
|
49
|
+
inset: 0;
|
|
50
|
+
width: 100%;
|
|
51
|
+
height: 100%;
|
|
52
|
+
max-width: none;
|
|
53
|
+
max-height: none;
|
|
54
|
+
margin: 0;
|
|
55
|
+
padding: 0;
|
|
56
|
+
border: none;
|
|
57
|
+
background: transparent;
|
|
58
|
+
overflow: clip;
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
62
|
+
}
|
|
110
63
|
|
|
111
|
-
/* ═══════
|
|
64
|
+
/* ═══════ Backdrop ═══════ */
|
|
65
|
+
:scope > [slot="dialog"]::backdrop {
|
|
66
|
+
background: var(--modal-backdrop);
|
|
67
|
+
opacity: 0;
|
|
68
|
+
transition: opacity var(--modal-duration) var(--modal-easing);
|
|
69
|
+
}
|
|
70
|
+
:scope > [slot="dialog"][data-open]::backdrop { opacity: 1; }
|
|
71
|
+
:scope > [slot="dialog"][data-closing]::backdrop { opacity: 0; }
|
|
112
72
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
modal-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
73
|
+
/* ═══════ Content panel ═══════ */
|
|
74
|
+
:scope > [slot="dialog"] > [slot="panel"] {
|
|
75
|
+
box-sizing: border-box;
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
width: var(--modal-width);
|
|
79
|
+
max-height: 85vh;
|
|
80
|
+
background: var(--modal-bg);
|
|
81
|
+
color: var(--modal-header-fg);
|
|
82
|
+
font-family: var(--modal-font-family);
|
|
83
|
+
border: 1px solid var(--modal-border);
|
|
84
|
+
border-radius: var(--modal-radius);
|
|
85
|
+
box-shadow: var(--modal-shadow);
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
opacity: 0;
|
|
88
|
+
transform: scale(0.95);
|
|
89
|
+
}
|
|
128
90
|
|
|
129
|
-
/* ═══════
|
|
91
|
+
/* ═══════ Open animation ═══════ */
|
|
92
|
+
:scope > [slot="dialog"][data-open] > [slot="panel"] {
|
|
93
|
+
transition: transform var(--modal-duration) var(--modal-easing),
|
|
94
|
+
opacity var(--modal-duration) var(--modal-easing);
|
|
95
|
+
transform: scale(1);
|
|
96
|
+
opacity: 1;
|
|
97
|
+
}
|
|
130
98
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
99
|
+
/* ═══════ Close animation ═══════ */
|
|
100
|
+
:scope > [slot="dialog"][data-closing] > [slot="panel"] {
|
|
101
|
+
transition: transform var(--modal-duration) var(--modal-easing),
|
|
102
|
+
opacity var(--modal-duration) var(--modal-easing);
|
|
103
|
+
transform: scale(0.95);
|
|
104
|
+
opacity: 0;
|
|
105
|
+
}
|
|
134
106
|
|
|
135
|
-
/* ═══════
|
|
107
|
+
/* ═══════ Header ═══════ */
|
|
108
|
+
:scope [slot="header"] {
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: space-between;
|
|
112
|
+
padding: var(--modal-pad-header) var(--modal-pad-body);
|
|
113
|
+
border-bottom: 1px solid var(--modal-border);
|
|
114
|
+
font-size: var(--modal-heading-size);
|
|
115
|
+
font-weight: var(--modal-heading-weight);
|
|
116
|
+
color: var(--modal-header-fg);
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
}
|
|
119
|
+
:scope [slot="header"]::before {
|
|
120
|
+
content: attr(text);
|
|
121
|
+
flex: 1;
|
|
122
|
+
}
|
|
136
123
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
124
|
+
/* ═══════ Close button (button-ui) ═══════ */
|
|
125
|
+
:scope [slot="close"] {
|
|
126
|
+
flex-shrink: 0;
|
|
127
|
+
}
|
|
142
128
|
|
|
143
|
-
/* ═══════
|
|
129
|
+
/* ═══════ Body ═══════ */
|
|
130
|
+
:scope [slot="body"] {
|
|
131
|
+
padding: var(--modal-pad-body);
|
|
132
|
+
flex: 1;
|
|
133
|
+
overflow: auto;
|
|
134
|
+
}
|
|
144
135
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
136
|
+
/* ═══════ Footer ═══════ */
|
|
137
|
+
:scope [slot="footer"] {
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: flex-end;
|
|
141
|
+
gap: var(--modal-footer-gap);
|
|
142
|
+
padding: var(--modal-pad-footer) var(--modal-pad-body);
|
|
143
|
+
border-top: 1px solid var(--modal-border);
|
|
144
|
+
flex-shrink: 0;
|
|
145
|
+
}
|
|
153
146
|
}
|
|
@@ -38,6 +38,7 @@ class AdiaNoodles extends AdiaElement {
|
|
|
38
38
|
#bound = false;
|
|
39
39
|
#dragState = null;
|
|
40
40
|
#dragPath = null;
|
|
41
|
+
#dragCancel = null;
|
|
41
42
|
|
|
42
43
|
// ── Public API ──────────────────────────────────────────────
|
|
43
44
|
|
|
@@ -124,6 +125,9 @@ class AdiaNoodles extends AdiaElement {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
disconnected() {
|
|
128
|
+
// Flush any in-flight drag so per-port pointermove/up listeners + their
|
|
129
|
+
// closure references are released before the host detaches.
|
|
130
|
+
this.#dragCancel?.();
|
|
127
131
|
this.#resizeObs?.disconnect();
|
|
128
132
|
this.#mutationObs?.disconnect();
|
|
129
133
|
if (this.#rafId) cancelAnimationFrame(this.#rafId);
|
|
@@ -385,9 +389,6 @@ class AdiaNoodles extends AdiaElement {
|
|
|
385
389
|
|
|
386
390
|
const startPos = this.#getPortPosition(fromEl, fromSide);
|
|
387
391
|
|
|
388
|
-
this.#dragState = { fromEl, fromSide, startPos, pointerId: e.pointerId };
|
|
389
|
-
this.#dragPath = null;
|
|
390
|
-
|
|
391
392
|
const onMove = (ev) => {
|
|
392
393
|
if (!this.#dragState) return;
|
|
393
394
|
const rect = this.getBoundingClientRect();
|
|
@@ -409,23 +410,39 @@ class AdiaNoodles extends AdiaElement {
|
|
|
409
410
|
};
|
|
410
411
|
|
|
411
412
|
const onUp = (ev) => {
|
|
412
|
-
dot.releasePointerCapture(ev.pointerId);
|
|
413
|
+
try { dot.releasePointerCapture(ev.pointerId); } catch (_) { /* already released */ }
|
|
413
414
|
dot.removeAttribute('data-noodle-dragging');
|
|
414
415
|
dot.removeEventListener('pointermove', onMove);
|
|
415
416
|
dot.removeEventListener('pointerup', onUp);
|
|
416
417
|
|
|
417
|
-
// Check if dropped on a target port
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
this.connect(fromEl, target.el, fromSide, target.side);
|
|
418
|
+
// Check if dropped on a target port (skip on synthetic disconnect cancel).
|
|
419
|
+
if (ev.type === 'pointerup' && ev.clientX !== undefined) {
|
|
420
|
+
const target = this.#findDropTarget(ev.clientX, ev.clientY, fromEl);
|
|
421
|
+
if (target) this.connect(fromEl, target.el, fromSide, target.side);
|
|
421
422
|
}
|
|
422
423
|
|
|
423
424
|
this.#clearDropTargets();
|
|
424
425
|
this.#dragState = null;
|
|
425
426
|
this.#dragPath = null;
|
|
427
|
+
this.#dragCancel = null;
|
|
426
428
|
this.#scheduleUpdate();
|
|
427
429
|
};
|
|
428
430
|
|
|
431
|
+
// Capture the handlers so `disconnected()` can flush a mid-drag drag
|
|
432
|
+
// by calling the same onUp tear-down without a synthetic event.
|
|
433
|
+
this.#dragCancel = () => {
|
|
434
|
+
this.#dragState = null;
|
|
435
|
+
dot.removeAttribute('data-noodle-dragging');
|
|
436
|
+
dot.removeEventListener('pointermove', onMove);
|
|
437
|
+
dot.removeEventListener('pointerup', onUp);
|
|
438
|
+
this.#clearDropTargets();
|
|
439
|
+
this.#dragPath = null;
|
|
440
|
+
this.#dragCancel = null;
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
this.#dragState = { fromEl, fromSide, startPos, pointerId: e.pointerId };
|
|
444
|
+
this.#dragPath = null;
|
|
445
|
+
|
|
429
446
|
dot.addEventListener('pointermove', onMove);
|
|
430
447
|
dot.addEventListener('pointerup', onUp);
|
|
431
448
|
};
|