storymapper 0.1.0
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/ARCHITECTURE.md +334 -0
- package/LICENSE +201 -0
- package/NOTICE +6 -0
- package/README.md +239 -0
- package/frontend/css/storymap.css +4637 -0
- package/frontend/js/adapters.js +423 -0
- package/frontend/js/card-animate.js +384 -0
- package/frontend/js/core/graph.js +825 -0
- package/frontend/js/core.js +3908 -0
- package/frontend/js/dialog-ticket-import.js +506 -0
- package/frontend/js/dnd.js +322 -0
- package/frontend/js/filter.js +215 -0
- package/frontend/js/main.js +2499 -0
- package/frontend/js/project-io.js +109 -0
- package/frontend/js/query-autocomplete.js +196 -0
- package/frontend/js/query-engine.js +339 -0
- package/frontend/js/query.js +280 -0
- package/frontend/js/renderer-card.js +639 -0
- package/frontend/js/renderer-dependencies.js +974 -0
- package/frontend/js/renderer-kanban.js +505 -0
- package/frontend/js/renderer-storymap.js +2530 -0
- package/frontend/js/renderer-ticket-editor.js +455 -0
- package/frontend/js/renderer-ticket-modal.js +758 -0
- package/frontend/js/save-pipeline.js +170 -0
- package/frontend/js/smartbar-autocomplete.js +162 -0
- package/frontend/js/store.js +197 -0
- package/frontend/js/ticket-editor-boot.js +24 -0
- package/frontend/js/ticket-form.js +2095 -0
- package/frontend/js/ticket-import.js +477 -0
- package/frontend/js/ui-shell.js +441 -0
- package/frontend/js/view-process-steps.js +233 -0
- package/frontend/js/view-requirements.js +361 -0
- package/frontend/js/view-settings.js +1864 -0
- package/frontend/js/view-table.js +659 -0
- package/frontend/js/wheel-pan.js +65 -0
- package/frontend/storymap.html +87 -0
- package/frontend/ticket-editor.html +29 -0
- package/package.json +76 -0
- package/server/bus.js +16 -0
- package/server/core/graph.js +10 -0
- package/server/core.js +10 -0
- package/server/identity.js +134 -0
- package/server/index.js +283 -0
- package/server/ingest.js +212 -0
- package/server/mcp.js +2510 -0
- package/server/server.js +1599 -0
- package/server/slice.js +103 -0
- package/server/storage.js +571 -0
- package/server/validation.js +225 -0
- package/shared/core/graph.js +825 -0
- package/shared/core.js +3908 -0
- package/shared/project-io.js +109 -0
- package/shared/query-autocomplete.js +196 -0
- package/shared/query-engine.js +339 -0
- package/shared/query.js +280 -0
- package/shared/ticket-import.js +477 -0
- package/skill/SKILL.md +458 -0
- package/skill/reference/anatomy.md +196 -0
- package/skill/reference/spec-evolution.md +52 -0
- package/skill/reference/tools.md +156 -0
- package/skill/reference/workflows.md +203 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card animations for MCP/WS-driven changes (E24).
|
|
3
|
+
*
|
|
4
|
+
* Used by Kanban, Story-Map, and Ticket-Modal renderers. Two effects:
|
|
5
|
+
*
|
|
6
|
+
* 1. FLIP: when a card moves between lanes / cells / sort-positions
|
|
7
|
+
* after an external commit, animate it sliding to its new spot
|
|
8
|
+
* instead of teleporting. Implementation is the classic FLIP pattern
|
|
9
|
+
* (First, Last, Invert, Play): capture rects before the rerender,
|
|
10
|
+
* then after the rerender apply an inverse transform and transition
|
|
11
|
+
* it back to identity.
|
|
12
|
+
*
|
|
13
|
+
* 2. Pulse: when a card's non-positional fields change (title, status,
|
|
14
|
+
* acceptance criteria, checklists, labels, etc.) we briefly add a
|
|
15
|
+
* .sm-card-flash class so the user sees "this card just got
|
|
16
|
+
* updated". Color comes from --accent so it stays on-theme.
|
|
17
|
+
*
|
|
18
|
+
* Local user actions don't trigger these — only remote commits (reason
|
|
19
|
+
* 'applyRemote') invoke the animation hooks. Local actions already have
|
|
20
|
+
* direct visual feedback (drag-ghost, dialog dismiss, …) and animating
|
|
21
|
+
* them on top would feel laggy.
|
|
22
|
+
*
|
|
23
|
+
* Pure helpers (`diffNonPositionChanges`) are testable in JSDOM. The
|
|
24
|
+
* timing-sensitive FLIP and pulse helpers are no-ops in environments
|
|
25
|
+
* without real layout (JSDOM returns 0×0 rects), so calling them in
|
|
26
|
+
* tests is safe.
|
|
27
|
+
*
|
|
28
|
+
* UMD-wrapped — usable in browser and Node tests.
|
|
29
|
+
*/
|
|
30
|
+
(function (root, factory) {
|
|
31
|
+
if (typeof module === "object" && module.exports) {
|
|
32
|
+
module.exports = factory();
|
|
33
|
+
} else {
|
|
34
|
+
(root.STORYMAP = root.STORYMAP || {}).cardAnimate = factory();
|
|
35
|
+
}
|
|
36
|
+
}(typeof self !== "undefined" ? self : this, function () {
|
|
37
|
+
"use strict";
|
|
38
|
+
|
|
39
|
+
const FLIP_DURATION_MS = 320;
|
|
40
|
+
const FLIP_EASING = "cubic-bezier(0.4, 0, 0.2, 1)";
|
|
41
|
+
const PULSE_CLASS = "sm-card-flash";
|
|
42
|
+
const PULSE_DURATION_MS = 900;
|
|
43
|
+
const PULSE_EPSILON_PX = 0.5; // ignore sub-pixel jitter from layout
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Snapshot the position of every visible ticket card inside `hostEl`.
|
|
47
|
+
* Keyed by ticket id (`data-ticket-id` attribute). Used as the "First"
|
|
48
|
+
* pass of FLIP — call this BEFORE you wipe and rerender the host.
|
|
49
|
+
*/
|
|
50
|
+
function captureCardRects(hostEl) {
|
|
51
|
+
return captureRectsBySelector(hostEl, ".sm-story-card[data-ticket-id]", "ticketId");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Generic version of captureCardRects (E24.E): walks an arbitrary
|
|
56
|
+
* selector inside `hostEl` and keys the bounding-rects by the named
|
|
57
|
+
* dataset property. Used to animate ticket cards, epic cards, process-
|
|
58
|
+
* step columns, release rows — anything with a stable data-* id.
|
|
59
|
+
*/
|
|
60
|
+
function captureRectsBySelector(hostEl, selector, idAttr) {
|
|
61
|
+
const out = new Map();
|
|
62
|
+
if (!hostEl || typeof hostEl.querySelectorAll !== "function") return out;
|
|
63
|
+
const nodes = hostEl.querySelectorAll(selector);
|
|
64
|
+
for (const node of nodes) {
|
|
65
|
+
const id = node.dataset && node.dataset[idAttr];
|
|
66
|
+
if (!id) continue;
|
|
67
|
+
out.set(id, node.getBoundingClientRect());
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* After the host has been rerendered, find each card that existed in
|
|
74
|
+
* `prevRects` and animate it from its old position to the new one.
|
|
75
|
+
* Newly inserted cards (no entry in prevRects) just appear in place.
|
|
76
|
+
* Returns the count of cards that actually moved (useful in tests).
|
|
77
|
+
*/
|
|
78
|
+
function flipFromCaptured(hostEl, prevRects, opts) {
|
|
79
|
+
return flipFromCapturedSelector(hostEl, prevRects, ".sm-story-card[data-ticket-id]", "ticketId", opts);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Generic FLIP (E24.E): same idea as flipFromCaptured but parametrised
|
|
84
|
+
* on selector + idAttr. Powers epic / process-step / release animations.
|
|
85
|
+
*/
|
|
86
|
+
function flipFromCapturedSelector(hostEl, prevRects, selector, idAttr, opts) {
|
|
87
|
+
if (!hostEl || !prevRects || prevRects.size === 0) return 0;
|
|
88
|
+
opts = opts || {};
|
|
89
|
+
const duration = opts.duration || FLIP_DURATION_MS;
|
|
90
|
+
const easing = opts.easing || FLIP_EASING;
|
|
91
|
+
let animated = 0;
|
|
92
|
+
const nodes = hostEl.querySelectorAll(selector);
|
|
93
|
+
for (const node of nodes) {
|
|
94
|
+
const id = node.dataset && node.dataset[idAttr];
|
|
95
|
+
if (!id) continue;
|
|
96
|
+
const prev = prevRects.get(id);
|
|
97
|
+
if (!prev) continue;
|
|
98
|
+
const next = node.getBoundingClientRect();
|
|
99
|
+
const dx = prev.left - next.left;
|
|
100
|
+
const dy = prev.top - next.top;
|
|
101
|
+
if (Math.abs(dx) < PULSE_EPSILON_PX && Math.abs(dy) < PULSE_EPSILON_PX) continue;
|
|
102
|
+
node.style.transition = "none";
|
|
103
|
+
node.style.transform = "translate(" + dx + "px, " + dy + "px)";
|
|
104
|
+
// eslint-disable-next-line no-unused-expressions
|
|
105
|
+
node.getBoundingClientRect();
|
|
106
|
+
node.style.transition = "transform " + duration + "ms " + easing;
|
|
107
|
+
node.style.transform = "";
|
|
108
|
+
setTimeout(function () {
|
|
109
|
+
node.style.transition = "";
|
|
110
|
+
node.style.transform = "";
|
|
111
|
+
}, duration + 60);
|
|
112
|
+
animated += 1;
|
|
113
|
+
}
|
|
114
|
+
return animated;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Briefly add the flash class to every card whose id is in `ticketIds`.
|
|
119
|
+
* The class triggers an --accent-toned keyframe in CSS. Caller must
|
|
120
|
+
* make sure the keyframe rule is loaded (frontend/css/storymap.css).
|
|
121
|
+
*/
|
|
122
|
+
function pulseTickets(hostEl, ticketIds, opts) {
|
|
123
|
+
pulseEntities(hostEl, ticketIds, '[data-ticket-id="', '"]', opts);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Generic pulse (E24.E): briefly add the flash class to any element
|
|
128
|
+
* matching `[data-<idAttr>="<id>"]` for each id. `selectorPrefix`/
|
|
129
|
+
* `selectorSuffix` let callers narrow the selector if they want to
|
|
130
|
+
* only highlight cards (e.g. exclude container wrappers).
|
|
131
|
+
*
|
|
132
|
+
* For typical usage just pass the data-attribute selector pieces:
|
|
133
|
+
* pulseEntities(host, ids, '[data-ticket-id="', '"]')
|
|
134
|
+
*/
|
|
135
|
+
function pulseEntities(hostEl, ids, selectorPrefix, selectorSuffix, opts) {
|
|
136
|
+
if (!hostEl || !ids) return;
|
|
137
|
+
opts = opts || {};
|
|
138
|
+
const cls = opts.className || PULSE_CLASS;
|
|
139
|
+
const duration = opts.duration || PULSE_DURATION_MS;
|
|
140
|
+
const list = (typeof ids.forEach === "function") ? ids : Array.from(ids);
|
|
141
|
+
list.forEach(function (id) {
|
|
142
|
+
if (!id) return;
|
|
143
|
+
const sel = selectorPrefix + cssEscape(id) + selectorSuffix;
|
|
144
|
+
const nodes = hostEl.querySelectorAll(sel);
|
|
145
|
+
for (const node of nodes) {
|
|
146
|
+
node.classList.remove(cls);
|
|
147
|
+
// eslint-disable-next-line no-unused-expressions
|
|
148
|
+
void node.offsetWidth; // force reflow → restart animation
|
|
149
|
+
node.classList.add(cls);
|
|
150
|
+
setTimeout(function () { node.classList.remove(cls); }, duration);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function cssEscape(s) {
|
|
156
|
+
return String(s).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Pure helper. Compare two snapshots and return the set of ticket ids
|
|
161
|
+
* whose non-positional fields changed. "Non-positional" = anything that
|
|
162
|
+
* doesn't affect which lane/cell a card lives in. Position changes are
|
|
163
|
+
* handled by the FLIP pass and don't need a pulse.
|
|
164
|
+
*/
|
|
165
|
+
function diffNonPositionChanges(prevSnap, nextSnap) {
|
|
166
|
+
const out = new Set();
|
|
167
|
+
if (!prevSnap || !nextSnap) return out;
|
|
168
|
+
const prevById = new Map((prevSnap.tickets || []).map(function (t) { return [t.id, t]; }));
|
|
169
|
+
for (const nextT of (nextSnap.tickets || [])) {
|
|
170
|
+
const prevT = prevById.get(nextT.id);
|
|
171
|
+
if (!prevT) continue; // newly added; the render itself draws attention
|
|
172
|
+
if (nonPositionHash(prevT) !== nonPositionHash(nextT)) out.add(nextT.id);
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Pure helper. Returns the set of ticket ids that exist in nextSnap
|
|
179
|
+
* but NOT in prevSnap (and aren't tombstoned). When `prevSnap` is null
|
|
180
|
+
* (e.g. very first commit observed on mount), every visible ticket
|
|
181
|
+
* counts as new — matches the user's perspective of "I just opened
|
|
182
|
+
* this and these cards appeared".
|
|
183
|
+
*
|
|
184
|
+
* Used by the animation hook to pulse arriving cards so they don't
|
|
185
|
+
* silently materialise where the user wouldn't notice them.
|
|
186
|
+
*/
|
|
187
|
+
function diffNewTickets(prevSnap, nextSnap) {
|
|
188
|
+
const out = new Set();
|
|
189
|
+
if (!nextSnap) return out;
|
|
190
|
+
const prevIds = new Set(prevSnap ? (prevSnap.tickets || []).map(function (t) { return t.id; }) : []);
|
|
191
|
+
for (const t of (nextSnap.tickets || [])) {
|
|
192
|
+
if (t.isDeleted) continue;
|
|
193
|
+
if (!prevIds.has(t.id)) out.add(t.id);
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Pure helper. Returns the set of ticket ids that were "touched" by
|
|
200
|
+
* the commit between prevSnap and nextSnap. Touched = the ticket is
|
|
201
|
+
* new, OR its `updatedAt`/`version` advanced, OR a structural compare
|
|
202
|
+
* sees any field change. This is the broadest "something happened to
|
|
203
|
+
* this ticket" signal — exactly what the user asked for: every MCP
|
|
204
|
+
* change to a ticket should pulse, no matter which field changed.
|
|
205
|
+
* Lane animation is handled separately by FLIP on rect-deltas.
|
|
206
|
+
*/
|
|
207
|
+
function diffTouchedTickets(prevSnap, nextSnap) {
|
|
208
|
+
const out = new Set();
|
|
209
|
+
if (!nextSnap) return out;
|
|
210
|
+
const prevById = new Map();
|
|
211
|
+
if (prevSnap) {
|
|
212
|
+
for (const t of (prevSnap.tickets || [])) prevById.set(t.id, t);
|
|
213
|
+
}
|
|
214
|
+
for (const next of (nextSnap.tickets || [])) {
|
|
215
|
+
if (next.isDeleted) continue;
|
|
216
|
+
const prev = prevById.get(next.id);
|
|
217
|
+
if (!prev) { out.add(next.id); continue; } // new ticket
|
|
218
|
+
// updatedAt / version cover almost every server-side mutation;
|
|
219
|
+
// fall back to structural compare for the (rare) case where a
|
|
220
|
+
// mutation didn't bump them.
|
|
221
|
+
if (prev.updatedAt !== next.updatedAt) { out.add(next.id); continue; }
|
|
222
|
+
if (prev.version !== next.version) { out.add(next.id); continue; }
|
|
223
|
+
if (touchedHash(prev) !== touchedHash(next)) out.add(next.id);
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Wide hash for change detection — includes position too, since the
|
|
229
|
+
* user wants every change to highlight (lane-move + glow combined). */
|
|
230
|
+
function touchedHash(t) {
|
|
231
|
+
return JSON.stringify({
|
|
232
|
+
type: t.type,
|
|
233
|
+
title: t.title,
|
|
234
|
+
description: t.description,
|
|
235
|
+
status: t.status,
|
|
236
|
+
position: t.position,
|
|
237
|
+
labels: t.labels,
|
|
238
|
+
acceptanceCriteria: t.acceptanceCriteria,
|
|
239
|
+
definitionOfReady: t.definitionOfReady,
|
|
240
|
+
definitionOfDone: t.definitionOfDone,
|
|
241
|
+
links: t.links,
|
|
242
|
+
comments: t.comments,
|
|
243
|
+
isDeleted: t.isDeleted
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Generic "touched ids in this entity list" diff (E24.E). `listKey`
|
|
249
|
+
* picks the snapshot array (tickets/releases/processSteps). Same
|
|
250
|
+
* heuristic as diffTouchedTickets: new id, updatedAt bump, version
|
|
251
|
+
* bump, or structural diff. Used by the Story-Map renderer to
|
|
252
|
+
* highlight changed process-steps and releases on top of tickets.
|
|
253
|
+
*/
|
|
254
|
+
function diffTouchedEntities(prevSnap, nextSnap, listKey) {
|
|
255
|
+
const out = new Set();
|
|
256
|
+
if (!nextSnap) return out;
|
|
257
|
+
const prevList = (prevSnap && prevSnap[listKey]) || [];
|
|
258
|
+
const nextList = (nextSnap && nextSnap[listKey]) || [];
|
|
259
|
+
const prevById = new Map();
|
|
260
|
+
for (const e of prevList) prevById.set(e.id, e);
|
|
261
|
+
for (const next of nextList) {
|
|
262
|
+
if (next.isDeleted) continue;
|
|
263
|
+
const prev = prevById.get(next.id);
|
|
264
|
+
if (!prev) { out.add(next.id); continue; }
|
|
265
|
+
if (prev.updatedAt !== next.updatedAt) { out.add(next.id); continue; }
|
|
266
|
+
if (prev.version !== next.version) { out.add(next.id); continue; }
|
|
267
|
+
if (JSON.stringify(prev) !== JSON.stringify(next)) out.add(next.id);
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Stable serialisation of every ticket field EXCEPT position metadata.
|
|
274
|
+
* `status` IS included — a status change without a corresponding lane
|
|
275
|
+
* move (e.g. story-map view where status isn't a layout axis) should
|
|
276
|
+
* still pulse the card so the user notices.
|
|
277
|
+
*/
|
|
278
|
+
function nonPositionHash(t) {
|
|
279
|
+
return JSON.stringify({
|
|
280
|
+
type: t.type,
|
|
281
|
+
title: t.title,
|
|
282
|
+
description: t.description,
|
|
283
|
+
status: t.status,
|
|
284
|
+
labels: t.labels,
|
|
285
|
+
acceptanceCriteria: t.acceptanceCriteria,
|
|
286
|
+
definitionOfReady: t.definitionOfReady,
|
|
287
|
+
definitionOfDone: t.definitionOfDone,
|
|
288
|
+
links: t.links,
|
|
289
|
+
comments: t.comments,
|
|
290
|
+
isDeleted: t.isDeleted
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* High-level helper for renderer subscribe-callbacks. Captures rects
|
|
296
|
+
* + diff, runs the supplied rerender, then plays the animations. Call
|
|
297
|
+
* this only when the commit came from `applyRemote` — local commits
|
|
298
|
+
* already have direct visual feedback.
|
|
299
|
+
*
|
|
300
|
+
* animateExternalCommit({
|
|
301
|
+
* host,
|
|
302
|
+
* prevSnap, // store snapshot before this commit
|
|
303
|
+
* nextSnap, // current store snapshot
|
|
304
|
+
* rerender: () => renderInto(host, store, opts)
|
|
305
|
+
* })
|
|
306
|
+
*/
|
|
307
|
+
function animateExternalCommit(args) {
|
|
308
|
+
const host = args.host;
|
|
309
|
+
const rerender = args.rerender;
|
|
310
|
+
if (!host || typeof rerender !== "function") {
|
|
311
|
+
if (typeof rerender === "function") rerender();
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const prevRects = captureCardRects(host);
|
|
315
|
+
const touchedIds = diffTouchedTickets(args.prevSnap, args.nextSnap);
|
|
316
|
+
rerender();
|
|
317
|
+
flipFromCaptured(host, prevRects);
|
|
318
|
+
if (touchedIds.size > 0) pulseTickets(host, touchedIds);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Multi-entity-type version of animateExternalCommit (E24.E). Pass an
|
|
323
|
+
* array of `entityTypes` like
|
|
324
|
+
*
|
|
325
|
+
* [
|
|
326
|
+
* { listKey: "tickets", selector: "[data-ticket-id]", idAttr: "ticketId" },
|
|
327
|
+
* { listKey: "processSteps", selector: "[data-process-step-id]", idAttr: "processStepId" },
|
|
328
|
+
* { listKey: "releases", selector: "[data-release-id]", idAttr: "releaseId" }
|
|
329
|
+
* ]
|
|
330
|
+
*
|
|
331
|
+
* The renderer (Story-Map) uses this to animate epic cards + story
|
|
332
|
+
* cards + process-step columns + release labels all in one rerender
|
|
333
|
+
* cycle. Each entity-type gets its own capture/diff/flip/pulse pass.
|
|
334
|
+
*/
|
|
335
|
+
function animateExternalCommitMulti(args) {
|
|
336
|
+
const host = args.host;
|
|
337
|
+
const rerender = args.rerender;
|
|
338
|
+
const types = Array.isArray(args.entityTypes) ? args.entityTypes : [];
|
|
339
|
+
if (!host || typeof rerender !== "function") {
|
|
340
|
+
if (typeof rerender === "function") rerender();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Phase 1: capture rects + diff touched ids per entity type.
|
|
344
|
+
const passes = types.map(function (et) {
|
|
345
|
+
return {
|
|
346
|
+
et: et,
|
|
347
|
+
prevRects: captureRectsBySelector(host, et.selector, et.idAttr),
|
|
348
|
+
touched: diffTouchedEntities(args.prevSnap, args.nextSnap, et.listKey)
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
// Phase 2: rerender (DOM wipe).
|
|
352
|
+
rerender();
|
|
353
|
+
// Phase 3: FLIP + pulse per entity type.
|
|
354
|
+
for (const p of passes) {
|
|
355
|
+
flipFromCapturedSelector(host, p.prevRects, p.et.selector, p.et.idAttr);
|
|
356
|
+
if (p.touched.size > 0) {
|
|
357
|
+
const dataAttr = "data-" + camelToDashed(p.et.idAttr);
|
|
358
|
+
pulseEntities(host, p.touched, '[' + dataAttr + '="', '"]');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function camelToDashed(s) {
|
|
364
|
+
return String(s).replace(/[A-Z]/g, function (c) { return "-" + c.toLowerCase(); });
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
FLIP_DURATION_MS,
|
|
369
|
+
PULSE_CLASS,
|
|
370
|
+
PULSE_DURATION_MS,
|
|
371
|
+
captureCardRects,
|
|
372
|
+
captureRectsBySelector,
|
|
373
|
+
flipFromCaptured,
|
|
374
|
+
flipFromCapturedSelector,
|
|
375
|
+
pulseTickets,
|
|
376
|
+
pulseEntities,
|
|
377
|
+
diffNonPositionChanges,
|
|
378
|
+
diffNewTickets,
|
|
379
|
+
diffTouchedTickets,
|
|
380
|
+
diffTouchedEntities,
|
|
381
|
+
animateExternalCommit,
|
|
382
|
+
animateExternalCommitMulti
|
|
383
|
+
};
|
|
384
|
+
}));
|