@adia-ai/web-components 0.2.2 → 0.2.3
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/agent-trace/agent-trace.css +24 -3
- package/components/list/list.css +45 -3
- package/components/nav-group/nav-group.a2ui.json +1 -1
- package/components/nav-group/nav-group.css +5 -5
- package/components/nav-group/nav-group.yaml +1 -1
- package/components/nav-item/nav-item.a2ui.json +1 -1
- package/components/nav-item/nav-item.css +3 -4
- package/components/nav-item/nav-item.yaml +1 -1
- package/core/icons.js +1 -0
- package/package.json +1 -1
- package/traits/_catalog.json +1 -1
- package/traits/confetti-burst.js +83 -57
- package/traits/drag-ghost.js +13 -1
- package/traits/spring-animate.js +8 -3
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
--agent-trace-padding-y: var(--a-space-2);
|
|
19
19
|
/* Component-intrinsic measurement; no --a-space-* equivalent */
|
|
20
20
|
--agent-trace-dot-size: 6px;
|
|
21
|
-
|
|
21
|
+
/* STAGE column width — `max-content` lets the longest label set the
|
|
22
|
+
track so multi-word labels ("Rows returned", "Query duration",
|
|
23
|
+
"Drift vs. SFDC") stay on one line; the `7rem` floor stops the
|
|
24
|
+
column from collapsing when only short labels are present. */
|
|
25
|
+
--agent-trace-row-label-col: minmax(7rem, max-content);
|
|
22
26
|
/* Shared across every detail DL so values tabulate at the same x. */
|
|
23
27
|
--agent-trace-detail-label-col: 9rem;
|
|
24
28
|
|
|
@@ -113,6 +117,17 @@
|
|
|
113
117
|
right of "7%"'s detail column. Subgrid pulls the track widths up
|
|
114
118
|
to the parent so every row sees the same column stops. */
|
|
115
119
|
[data-trace-rows] {
|
|
120
|
+
/* Track plan:
|
|
121
|
+
[STAGE max-content] ← label column; widened from 80px so multi-word
|
|
122
|
+
labels ("Rows returned", "Query duration",
|
|
123
|
+
"Drift vs. SFDC") stay on one line
|
|
124
|
+
[SCORE max-content] ← value column, right-aligned
|
|
125
|
+
[DETAIL 1fr] ← detail column, right-aligned content
|
|
126
|
+
The DETAIL column still takes the leftover width, but its text is
|
|
127
|
+
right-anchored ([data-trace-aux] { text-align: end }) so the
|
|
128
|
+
previously-empty right edge becomes the alignment edge for detail
|
|
129
|
+
text. The whitespace between SCORE and DETAIL reads as breathing
|
|
130
|
+
room between key-value and qualifier rather than dead air. */
|
|
116
131
|
--trace-row-cols: var(--agent-trace-row-label-col) max-content 1fr;
|
|
117
132
|
display: grid;
|
|
118
133
|
grid-template-columns: var(--trace-row-cols);
|
|
@@ -157,8 +172,9 @@
|
|
|
157
172
|
color: var(--agent-trace-fg-subtle);
|
|
158
173
|
}
|
|
159
174
|
|
|
160
|
-
[data-trace-header]:nth-of-type(2)
|
|
161
|
-
|
|
175
|
+
[data-trace-header]:nth-of-type(2),
|
|
176
|
+
[data-trace-header]:nth-of-type(3) {
|
|
177
|
+
text-align: end;
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
/* Rows */
|
|
@@ -198,10 +214,15 @@
|
|
|
198
214
|
}
|
|
199
215
|
|
|
200
216
|
[data-trace-aux] {
|
|
217
|
+
/* Right-anchor the detail text so it sits flush with the right edge
|
|
218
|
+
of the row. Without this, the 1fr detail column left-aligns its
|
|
219
|
+
content (typically a 1-3 word qualifier like "warehouse" or
|
|
220
|
+
"reconciled") and the rest of the column reads as dead width. */
|
|
201
221
|
color: var(--agent-trace-fg-muted);
|
|
202
222
|
overflow: hidden;
|
|
203
223
|
text-overflow: ellipsis;
|
|
204
224
|
white-space: nowrap;
|
|
225
|
+
text-align: end;
|
|
205
226
|
}
|
|
206
227
|
|
|
207
228
|
/* Chevron in the trailing column */
|
package/components/list/list.css
CHANGED
|
@@ -92,20 +92,62 @@
|
|
|
92
92
|
@scope (list-item-ui) {
|
|
93
93
|
:where(:scope) {
|
|
94
94
|
/* ── Layout ── */
|
|
95
|
-
--list-item-inset:
|
|
95
|
+
--list-item-inset: var(--a-inset);
|
|
96
|
+
--list-item-gap-column: var(--a-space-3);
|
|
97
|
+
--list-item-gap-row: var(--a-space-1);
|
|
96
98
|
|
|
97
99
|
/* ── Colors ── */
|
|
98
100
|
--list-item-text-color: var(--a-fg);
|
|
101
|
+
--list-item-desc-color: var(--a-fg-muted);
|
|
102
|
+
--list-item-icon-color: var(--a-fg-muted);
|
|
99
103
|
|
|
100
104
|
/* ── Typography ── */
|
|
101
|
-
--list-item-font-size:
|
|
105
|
+
--list-item-font-size: var(--a-ui-size);
|
|
106
|
+
--list-item-desc-font-size: var(--a-ui-sm);
|
|
102
107
|
}
|
|
103
108
|
|
|
109
|
+
/* Anatomy:
|
|
110
|
+
col 1 = icon (auto width; collapses to 0 when no icon)
|
|
111
|
+
col 2 = content stack (text on row 1, description on row 2)
|
|
112
|
+
The icon spans both rows + centers vertically with the stack.
|
|
113
|
+
`[slot="content"]` (used when consumer provides custom rendering)
|
|
114
|
+
spans all columns. */
|
|
104
115
|
:scope {
|
|
105
116
|
box-sizing: border-box;
|
|
106
|
-
display:
|
|
117
|
+
display: grid;
|
|
118
|
+
grid-template-columns: auto 1fr;
|
|
119
|
+
column-gap: var(--list-item-gap-column);
|
|
120
|
+
row-gap: var(--list-item-gap-row);
|
|
107
121
|
font-size: var(--list-item-font-size);
|
|
108
122
|
color: var(--list-item-text-color);
|
|
109
123
|
line-height: 1.4;
|
|
110
124
|
}
|
|
125
|
+
|
|
126
|
+
:scope > [slot="icon"] {
|
|
127
|
+
grid-column: 1;
|
|
128
|
+
grid-row: 1 / -1;
|
|
129
|
+
align-self: center;
|
|
130
|
+
color: var(--list-item-icon-color);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
:scope > [slot="text"] {
|
|
134
|
+
grid-column: 2;
|
|
135
|
+
grid-row: 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
:scope > [slot="description"] {
|
|
139
|
+
grid-column: 2;
|
|
140
|
+
grid-row: 2;
|
|
141
|
+
color: var(--list-item-desc-color);
|
|
142
|
+
font-size: var(--list-item-desc-font-size);
|
|
143
|
+
line-height: 1.3;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Custom-content escape hatch — consumer authored a [slot="content"]
|
|
147
|
+
child, the auto-stamp early-returned, and the consumer owns the
|
|
148
|
+
full body. Span all columns so the consumer's layout isn't boxed
|
|
149
|
+
into the content column. */
|
|
150
|
+
:scope > [slot="content"] {
|
|
151
|
+
grid-column: 1 / -1;
|
|
152
|
+
}
|
|
111
153
|
}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"default": ""
|
|
43
43
|
},
|
|
44
44
|
"variant": {
|
|
45
|
-
"description": "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children
|
|
45
|
+
"description": "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children. When the parent <nav-ui> carries variant=\"section\", this group inherits it via CSS cascade unless an explicit variant is set on the group.",
|
|
46
46
|
"type": "string",
|
|
47
47
|
"enum": [
|
|
48
48
|
"",
|
|
@@ -264,11 +264,11 @@ nav-group-ui [slot="popover"] [role="option"][aria-selected="true"]::before {
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
/* ── Section variant — groups render as kicker labels ──
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
267
|
+
Primary variant defaults to collapsible:true (caret-toggleable).
|
|
268
|
+
Section variant flips to collapsible:false-shape — header is a
|
|
269
|
+
static kicker label, children always visible — so subnav rails
|
|
270
|
+
show their full structure without requiring an explicit [open]
|
|
271
|
+
toggle.
|
|
272
272
|
|
|
273
273
|
Two ways to enable: either
|
|
274
274
|
(a) `<nav-group-ui variant="section">` directly, or
|
|
@@ -39,7 +39,7 @@ props:
|
|
|
39
39
|
type: string
|
|
40
40
|
default: ''
|
|
41
41
|
enum: ['', section]
|
|
42
|
-
description: "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children
|
|
42
|
+
description: "Visual treatment. Default ('') renders as a primary-rail group (icon row, caret, collapsible). 'section' renders the header as a static kicker label with always-visible children. When the parent <nav-ui> carries variant=\"section\", this group inherits it via CSS cascade unless an explicit variant is set on the group."
|
|
43
43
|
|
|
44
44
|
events:
|
|
45
45
|
group-toggle:
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"default": ""
|
|
48
48
|
},
|
|
49
49
|
"variant": {
|
|
50
|
-
"description": "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected
|
|
50
|
+
"description": "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected. When the parent <nav-ui> carries variant=\"section\", this item inherits it via CSS cascade unless an explicit variant is set.",
|
|
51
51
|
"type": "string",
|
|
52
52
|
"enum": [
|
|
53
53
|
"",
|
|
@@ -162,10 +162,9 @@ nav-item-ui[selected] [slot="icon"]:empty::before {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
/* ── Section variant — items render flat (no icon space, no indent) ──
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
row with a hidden icon slot.
|
|
165
|
+
Section-variant rails read as plain links rather than primary-style
|
|
166
|
+
rows: no reserved icon space when absent, left-edge accent bar for
|
|
167
|
+
the selected state instead of an in-icon accent.
|
|
169
168
|
|
|
170
169
|
Two ways to enable: either
|
|
171
170
|
(a) `<nav-item-ui variant="section">` directly, or
|
|
@@ -43,7 +43,7 @@ props:
|
|
|
43
43
|
type: string
|
|
44
44
|
default: ''
|
|
45
45
|
enum: ['', section]
|
|
46
|
-
description: "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected
|
|
46
|
+
description: "Visual treatment. Default ('') renders as a primary-rail item (reserved icon space, in-icon selected accent). 'section' renders flat — no icon space when absent, left-edge accent bar for selected. When the parent <nav-ui> carries variant=\"section\", this item inherits it via CSS cascade unless an explicit variant is set."
|
|
47
47
|
|
|
48
48
|
events:
|
|
49
49
|
nav-select:
|
package/core/icons.js
CHANGED
|
@@ -224,6 +224,7 @@ const ICON_ALIASES = {
|
|
|
224
224
|
'attachment': 'paperclip', 'attach': 'paperclip',
|
|
225
225
|
'expand': 'arrows-out', 'collapse': 'arrows-in',
|
|
226
226
|
'fullscreen': 'arrows-out', 'exit-fullscreen': 'arrows-in',
|
|
227
|
+
'fade': 'gradient', 'fade-presence': 'gradient',
|
|
227
228
|
'notification': 'bell', 'notifications': 'bell',
|
|
228
229
|
'bookmark': 'bookmark-simple', 'favorite': 'heart', 'like': 'heart',
|
|
229
230
|
'comment': 'chat-circle', 'chat': 'chat-circle',
|
package/package.json
CHANGED
package/traits/_catalog.json
CHANGED
|
@@ -230,7 +230,7 @@
|
|
|
230
230
|
{
|
|
231
231
|
"name": "confetti-burst",
|
|
232
232
|
"category": "interaction-delight",
|
|
233
|
-
"description": "Upward fountain particle burst",
|
|
233
|
+
"description": "Upward fountain particle burst — fires on each `press` event",
|
|
234
234
|
"attributes": [
|
|
235
235
|
"data-confetti-burst-active"
|
|
236
236
|
],
|
package/traits/confetti-burst.js
CHANGED
|
@@ -4,88 +4,114 @@ import { prefersReducedMotion } from './_motion.js';
|
|
|
4
4
|
export const confettiBurst = defineTrait({
|
|
5
5
|
name: 'confetti-burst',
|
|
6
6
|
category: 'interaction-delight',
|
|
7
|
-
description: 'Upward fountain particle burst',
|
|
7
|
+
description: 'Upward fountain particle burst — fires on each `press` event',
|
|
8
8
|
attributes: ['data-confetti-burst-active'],
|
|
9
9
|
events: ['confetti-burst-done'],
|
|
10
10
|
config: [],
|
|
11
11
|
setup({ host }) {
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
12
|
+
// Per-burst state — separate from the trait's lifecycle so multiple
|
|
13
|
+
// presses queue up cleanly. Each `fireBurst()` spawns its own canvas +
|
|
14
|
+
// raf loop and tears them down 2s later. Listening for `press` lets
|
|
15
|
+
// declarative usage (`<button-ui traits="confetti-burst">`) fire on
|
|
16
|
+
// every click; programmatic usage (animation page's
|
|
17
|
+
// `confettiBurst().connect(el)` per-click) gets the immediate burst
|
|
18
|
+
// it expects via the on-connect call below.
|
|
19
|
+
const inflight = new Set();
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
canvas.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:9999;';
|
|
24
|
-
host.style.position = host.style.position || 'relative';
|
|
25
|
-
host.appendChild(canvas);
|
|
26
|
-
|
|
27
|
-
const ctx = canvas.getContext('2d');
|
|
28
|
-
let rafId = null;
|
|
29
|
-
|
|
30
|
-
// Bail gracefully when canvas 2D isn't available (SSR, JSDOM, happy-dom).
|
|
31
|
-
if (!ctx) {
|
|
21
|
+
function reducedMotionBurst() {
|
|
32
22
|
host.setAttribute('data-confetti-burst-active', '');
|
|
33
|
-
// Fire the done event on next tick so listeners still hear it.
|
|
34
23
|
queueMicrotask(() => {
|
|
35
24
|
host.removeAttribute('data-confetti-burst-active');
|
|
36
25
|
host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
|
|
37
26
|
});
|
|
38
|
-
return () => {
|
|
39
|
-
canvas.remove();
|
|
40
|
-
host.removeAttribute('data-confetti-burst-active');
|
|
41
|
-
};
|
|
42
27
|
}
|
|
43
28
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
29
|
+
function fireBurst() {
|
|
30
|
+
if (prefersReducedMotion()) {
|
|
31
|
+
reducedMotionBurst();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const canvas = document.createElement('canvas');
|
|
36
|
+
canvas.style.cssText = 'position:absolute;inset:0;pointer-events:none;z-index:9999;';
|
|
37
|
+
host.style.position = host.style.position || 'relative';
|
|
38
|
+
host.appendChild(canvas);
|
|
54
39
|
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
const ctx = canvas.getContext('2d');
|
|
41
|
+
let rafId = null;
|
|
57
42
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
// Bail gracefully when canvas 2D isn't available (SSR, JSDOM, happy-dom).
|
|
44
|
+
if (!ctx) {
|
|
45
|
+
host.setAttribute('data-confetti-burst-active', '');
|
|
46
|
+
queueMicrotask(() => {
|
|
47
|
+
host.removeAttribute('data-confetti-burst-active');
|
|
48
|
+
host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
|
|
49
|
+
});
|
|
61
50
|
canvas.remove();
|
|
62
|
-
host.removeAttribute('data-confetti-burst-active');
|
|
63
|
-
host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
|
|
64
51
|
return;
|
|
65
52
|
}
|
|
66
53
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
54
|
+
const colors = ['#f44', '#4a4', '#44f', '#ff4', '#f4f', '#4ff'];
|
|
55
|
+
const particles = Array.from({ length: 80 }, () => ({
|
|
56
|
+
x: 0.5,
|
|
57
|
+
y: 0.5,
|
|
58
|
+
vx: (Math.random() - 0.5) * 8,
|
|
59
|
+
vy: (Math.random() - 0.5) * 8 - 2,
|
|
60
|
+
size: Math.random() * 4 + 2,
|
|
61
|
+
color: colors[Math.floor(Math.random() * colors.length)],
|
|
62
|
+
life: 1,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
host.setAttribute('data-confetti-burst-active', '');
|
|
66
|
+
const startTime = performance.now();
|
|
67
|
+
const burstHandle = { canvas, cancel: () => { if (rafId) cancelAnimationFrame(rafId); canvas.remove(); } };
|
|
68
|
+
inflight.add(burstHandle);
|
|
69
|
+
|
|
70
|
+
function tick(now) {
|
|
71
|
+
const elapsed = now - startTime;
|
|
72
|
+
if (elapsed > 2000) {
|
|
73
|
+
inflight.delete(burstHandle);
|
|
74
|
+
canvas.remove();
|
|
75
|
+
if (inflight.size === 0) host.removeAttribute('data-confetti-burst-active');
|
|
76
|
+
host.dispatchEvent(new CustomEvent('confetti-burst-done', { bubbles: true }));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
canvas.width = host.offsetWidth;
|
|
81
|
+
canvas.height = host.offsetHeight;
|
|
82
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
for (const p of particles) {
|
|
85
|
+
p.x += p.vx * 0.004;
|
|
86
|
+
p.y += p.vy * 0.004;
|
|
87
|
+
p.vy += 0.15;
|
|
88
|
+
p.life = Math.max(0, 1 - elapsed / 2000);
|
|
89
|
+
ctx.globalAlpha = p.life;
|
|
90
|
+
ctx.fillStyle = p.color;
|
|
91
|
+
ctx.fillRect(p.x * canvas.width, p.y * canvas.height, p.size, p.size);
|
|
92
|
+
}
|
|
93
|
+
ctx.globalAlpha = 1;
|
|
94
|
+
rafId = requestAnimationFrame(tick);
|
|
79
95
|
}
|
|
80
|
-
|
|
96
|
+
|
|
81
97
|
rafId = requestAnimationFrame(tick);
|
|
82
98
|
}
|
|
83
99
|
|
|
84
|
-
|
|
100
|
+
// Fire one burst at connect-time. Preserves the existing animation
|
|
101
|
+
// page semantic (`confettiBurst().connect(burstEl)` on every click of
|
|
102
|
+
// a separate trigger button).
|
|
103
|
+
fireBurst();
|
|
104
|
+
|
|
105
|
+
// Listen for `press` so the declarative case
|
|
106
|
+
// (`<button-ui traits="confetti-burst">`) fires a fresh burst on
|
|
107
|
+
// each click. button-ui dispatches `press` from its #onClick.
|
|
108
|
+
const onPress = () => fireBurst();
|
|
109
|
+
host.addEventListener('press', onPress);
|
|
85
110
|
|
|
86
111
|
return () => {
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
host.removeEventListener('press', onPress);
|
|
113
|
+
for (const handle of inflight) handle.cancel();
|
|
114
|
+
inflight.clear();
|
|
89
115
|
host.removeAttribute('data-confetti-burst-active');
|
|
90
116
|
};
|
|
91
117
|
},
|
package/traits/drag-ghost.js
CHANGED
|
@@ -9,15 +9,25 @@ export const dragGhost = defineTrait({
|
|
|
9
9
|
config: [],
|
|
10
10
|
setup({ host }) {
|
|
11
11
|
let ghost = null;
|
|
12
|
+
let clickOffsetX = 0;
|
|
13
|
+
let clickOffsetY = 0;
|
|
14
|
+
|
|
15
|
+
function onPointerDown(e) {
|
|
16
|
+
const rect = host.getBoundingClientRect();
|
|
17
|
+
clickOffsetX = e.clientX - rect.left;
|
|
18
|
+
clickOffsetY = e.clientY - rect.top;
|
|
19
|
+
}
|
|
12
20
|
|
|
13
21
|
function onDragStart(e) {
|
|
14
22
|
ghost = host.cloneNode(true);
|
|
23
|
+
const rect = host.getBoundingClientRect();
|
|
15
24
|
ghost.style.cssText = `
|
|
16
25
|
position: fixed; top: -9999px; left: -9999px;
|
|
26
|
+
width: ${rect.width}px; height: ${rect.height}px;
|
|
17
27
|
pointer-events: none; opacity: 0.8; z-index: 99999;
|
|
18
28
|
`;
|
|
19
29
|
document.body.appendChild(ghost);
|
|
20
|
-
e.dataTransfer?.setDragImage(ghost,
|
|
30
|
+
e.dataTransfer?.setDragImage(ghost, clickOffsetX, clickOffsetY);
|
|
21
31
|
host.setAttribute('data-drag-ghost-active', '');
|
|
22
32
|
}
|
|
23
33
|
|
|
@@ -27,10 +37,12 @@ export const dragGhost = defineTrait({
|
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
host.setAttribute('draggable', 'true');
|
|
40
|
+
host.addEventListener('pointerdown', onPointerDown);
|
|
30
41
|
host.addEventListener('dragstart', onDragStart);
|
|
31
42
|
host.addEventListener('dragend', onDragEnd);
|
|
32
43
|
|
|
33
44
|
return () => {
|
|
45
|
+
host.removeEventListener('pointerdown', onPointerDown);
|
|
34
46
|
host.removeEventListener('dragstart', onDragStart);
|
|
35
47
|
host.removeEventListener('dragend', onDragEnd);
|
|
36
48
|
if (ghost) { ghost.remove(); ghost = null; }
|
package/traits/spring-animate.js
CHANGED
|
@@ -14,10 +14,15 @@ export const springAnimate = defineTrait({
|
|
|
14
14
|
let rafId = null;
|
|
15
15
|
const target = 0;
|
|
16
16
|
|
|
17
|
-
// Read current translate as starting position
|
|
17
|
+
// Read current translate as starting position. The trait writes to
|
|
18
|
+
// `style.translate` (not `transform`), so read that first; fall back to
|
|
19
|
+
// the transform matrix for callers that nudged via `transform`.
|
|
18
20
|
const cs = getComputedStyle(host);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
let position = parseFloat(cs.translate) || 0;
|
|
22
|
+
if (!position) {
|
|
23
|
+
const matrix = new DOMMatrixReadOnly(cs.transform);
|
|
24
|
+
position = matrix.m41 || 0;
|
|
25
|
+
}
|
|
21
26
|
let velocity = 0;
|
|
22
27
|
|
|
23
28
|
host.setAttribute('data-spring-animate-active', '');
|