@wcstack/view-transition 1.31.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.
@@ -0,0 +1,753 @@
1
+ const _config = {
2
+ tagNames: {
3
+ viewTransition: "wcs-view-transition",
4
+ },
5
+ };
6
+ function deepFreeze(obj) {
7
+ if (obj === null || typeof obj !== "object")
8
+ return obj;
9
+ Object.freeze(obj);
10
+ for (const key of Object.keys(obj)) {
11
+ deepFreeze(obj[key]);
12
+ }
13
+ return obj;
14
+ }
15
+ function deepClone(obj) {
16
+ if (obj === null || typeof obj !== "object")
17
+ return obj;
18
+ const clone = {};
19
+ for (const key of Object.keys(obj)) {
20
+ clone[key] = deepClone(obj[key]);
21
+ }
22
+ return clone;
23
+ }
24
+ let frozenConfig = null;
25
+ // Note: this is the live, mutable internal config. It is not part of the public
26
+ // package exports (see exports.ts) — only `getConfig()` (a frozen snapshot) is
27
+ // surfaced. `setConfig()` is applied internally via `bootstrapViewTransition()` and
28
+ // is not re-exported from the package root, though a deep path import
29
+ // (`.../src/config.js`) can still reach and mutate it. Accepted as-is for
30
+ // cross-package consistency: every @wcstack package follows this same shape.
31
+ // Use `getConfig()` for a frozen, safe read.
32
+ const config = _config;
33
+ function getConfig() {
34
+ if (!frozenConfig) {
35
+ frozenConfig = deepFreeze(deepClone(_config));
36
+ }
37
+ return frozenConfig;
38
+ }
39
+ function setConfig(partialConfig) {
40
+ if (partialConfig.tagNames) {
41
+ Object.assign(_config.tagNames, partialConfig.tagNames);
42
+ }
43
+ frozenConfig = null;
44
+ }
45
+
46
+ // ===========================================================================
47
+ // AUTO-GENERATED FILE - DO NOT EDIT.
48
+ // Generated from /protocol/transition-runner.ts by scripts/sync-protocol-types.mjs.
49
+ // Run `node scripts/sync-protocol-types.mjs` after editing the source.
50
+ // ===========================================================================
51
+ // transition-runner protocol — how a package that mutates the DOM hands that
52
+ // mutation to whoever is arbitrating view transitions on the page.
53
+ //
54
+ // @wcstack/state and @wcstack/router must not depend on @wcstack/view-transition
55
+ // (zero runtime dependencies, independently publishable), so the arbiter installs
56
+ // itself on a well-known global symbol and the participants look it up lazily.
57
+ // No arbiter installed means the mutation is invoked directly, synchronously —
58
+ // byte-for-byte the behavior these packages had before the protocol existed.
59
+ //
60
+ // docs/view-transition-design.md §4 is the normative description.
61
+ //
62
+ // SINGLE SOURCE OF TRUTH: edit only this file (/protocol/transition-runner.ts), then run
63
+ // `node scripts/sync-protocol-types.mjs` to regenerate the per-package copies
64
+ // (packages/<pkg>/src/protocol/transitionRunner.ts). Those copies are generated — do not edit them.
65
+ /**
66
+ * Global key the arbiter installs itself under. `Symbol.for` so independently
67
+ * loaded copies of this file (two CDN bundles on one page) still agree.
68
+ */
69
+ const TRANSITION_RUNNER_KEY = Symbol.for("wcstack.transition-runner");
70
+ /**
71
+ * The installed arbiter, or null when there is none, it speaks a version this
72
+ * reader does not, or it does not accept this participant.
73
+ *
74
+ * Looked up on every call rather than cached: the tag can be added, removed, or
75
+ * reconfigured at any point in a page's life, and a stale cache would either
76
+ * animate what the author just switched off or miss what they switched on.
77
+ */
78
+ function getTransitionRunner(source) {
79
+ const candidate = globalThis[TRANSITION_RUNNER_KEY];
80
+ if (candidate === undefined || candidate === null)
81
+ return null;
82
+ if (candidate.protocol !== "wcs-transition-runner")
83
+ return null;
84
+ if (typeof candidate.version !== "number" || candidate.version < 1)
85
+ return null;
86
+ if (typeof candidate.run !== "function")
87
+ return null;
88
+ if (typeof candidate.accepts !== "function" || !candidate.accepts(source))
89
+ return null;
90
+ return candidate;
91
+ }
92
+ /**
93
+ * Run `mutate` under the installed arbiter, or directly when there is none.
94
+ *
95
+ * Returns `undefined` in the no-arbiter case instead of a resolved promise: the
96
+ * state drain calls this on every batch, and awaiting is a caller's choice, not
97
+ * an allocation the common path should pay for. `await` accepts both.
98
+ */
99
+ function runTransition(source, mutate, types) {
100
+ const runner = getTransitionRunner(source);
101
+ if (runner === null) {
102
+ mutate();
103
+ return undefined;
104
+ }
105
+ return runner.run(mutate, { source, types });
106
+ }
107
+
108
+ const DEFAULT_NAMING_LIMIT = 200;
109
+ const DEFAULT_PARTICIPANTS = ["router", "state"];
110
+ function toError(value) {
111
+ return value instanceof Error ? value : new Error(String(value));
112
+ }
113
+ /**
114
+ * Accept both the array form and the space-separated string an attribute (or a
115
+ * `data-wcs` binding) produces. The Core is a public export, so `core.types = "a b"`
116
+ * is a call an adopter can make — and without normalizing it here that string
117
+ * would degrade into single characters (`new Set("router")` contains no
118
+ * `"router"`, so `accepts()` would answer false for every participant).
119
+ */
120
+ function toStringList(value) {
121
+ if (typeof value === "string") {
122
+ return value.split(/\s+/).filter((token) => token !== "");
123
+ }
124
+ return [...value];
125
+ }
126
+ function prefersReducedMotion() {
127
+ // never-throw: matchMedia is absent in happy-dom and in non-browser hosts.
128
+ try {
129
+ const mm = globalThis.matchMedia;
130
+ if (typeof mm !== "function")
131
+ return false;
132
+ return mm.call(globalThis, "(prefers-reduced-motion: reduce)").matches === true;
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ }
138
+ /**
139
+ * Whether `startViewTransition({ update, types })` is understood. Detected on
140
+ * `ViewTransition.prototype`, because passing the object form to an
141
+ * implementation that only accepts a callback would throw at the call site —
142
+ * after the browser has already decided it has no update callback to run.
143
+ */
144
+ function supportsTypes() {
145
+ try {
146
+ const ctor = globalThis.ViewTransition;
147
+ return ctor !== undefined && "types" in ctor.prototype;
148
+ }
149
+ catch {
150
+ return false;
151
+ }
152
+ }
153
+ /**
154
+ * Headless view-transition arbiter — the single place on a page that decides
155
+ * whether a DOM mutation animates, and what happens when two of them collide.
156
+ *
157
+ * It is not an I/O node: nothing is read from a device and there is no data to
158
+ * bind. It is a *policy* node. Participants (`@wcstack/router`, `@wcstack/state`)
159
+ * never import it; they find it through the transition-runner protocol on a
160
+ * well-known global symbol and hand it a mutation to run
161
+ * (docs/view-transition-design.md §4).
162
+ *
163
+ * The one invariant everything else is subordinate to: **a mutation handed to
164
+ * `run()` is applied exactly once**, whatever is decided about animating it. An
165
+ * unsupported browser, a hidden tab, reduced motion, a colliding transition and a
166
+ * `startViewTransition` that throws all end in the mutation running — the page
167
+ * must never be left showing stale DOM because an animation could not be played.
168
+ */
169
+ class ViewTransitionCore extends EventTarget {
170
+ static wcBindable = {
171
+ protocol: "wc-bindable",
172
+ version: 1,
173
+ properties: [
174
+ { name: "active", event: "wcs-view-transition:active-changed", semantics: "state" },
175
+ { name: "error", event: "wcs-view-transition:error", semantics: "state" },
176
+ ],
177
+ commands: [
178
+ { name: "skip" },
179
+ ],
180
+ };
181
+ _target;
182
+ _mode = "latest";
183
+ _naming = "manual";
184
+ _namingLimit = DEFAULT_NAMING_LIMIT;
185
+ _reducedMotion = "skip";
186
+ _types = [];
187
+ _disabled = false;
188
+ _participants = new Set(DEFAULT_PARTICIPANTS);
189
+ _active = false;
190
+ _error = null;
191
+ /** Requests waiting for the microtask flush that starts a transition. */
192
+ _pending = null;
193
+ _flushScheduled = false;
194
+ /**
195
+ * The batch handed to the running transition while its update callback has not
196
+ * fired yet. Non-null means "capturing": a request arriving now still joins this
197
+ * batch, which is both the coalescing window and the only ordering guarantee
198
+ * that keeps a later `exhaust`/`latest` request from applying ahead of it.
199
+ */
200
+ _batch = null;
201
+ _transition = null;
202
+ _queue = [];
203
+ constructor(target) {
204
+ super();
205
+ this._target = target ?? this;
206
+ }
207
+ // --- transition-runner protocol surface ---
208
+ get protocol() {
209
+ return "wcs-transition-runner";
210
+ }
211
+ get version() {
212
+ return 1;
213
+ }
214
+ get naming() {
215
+ return this._naming;
216
+ }
217
+ set naming(value) {
218
+ this._naming = value === "auto" ? "auto" : "manual";
219
+ }
220
+ get namingLimit() {
221
+ return this._namingLimit;
222
+ }
223
+ set namingLimit(value) {
224
+ this._namingLimit = Number.isFinite(value) && value >= 0 ? Math.floor(value) : DEFAULT_NAMING_LIMIT;
225
+ }
226
+ accepts(source) {
227
+ return this._participants.has(source);
228
+ }
229
+ /**
230
+ * Install this core as the page's arbiter. Returns false (and warns) when
231
+ * another one already holds the slot — two arbiters would each think they own
232
+ * the exclusion, which is precisely the thing an arbiter exists to prevent.
233
+ */
234
+ install() {
235
+ const slot = globalThis;
236
+ const current = slot[TRANSITION_RUNNER_KEY];
237
+ if (current !== undefined && current !== null && current !== this) {
238
+ console.warn("[@wcstack/view-transition] a transition runner is already installed; " +
239
+ "this element is inert. Use one <wcs-view-transition> per document.");
240
+ return false;
241
+ }
242
+ slot[TRANSITION_RUNNER_KEY] = this;
243
+ return true;
244
+ }
245
+ /** Release the arbiter slot, but only if it is still ours. */
246
+ uninstall() {
247
+ const slot = globalThis;
248
+ if (slot[TRANSITION_RUNNER_KEY] === this) {
249
+ delete slot[TRANSITION_RUNNER_KEY];
250
+ }
251
+ }
252
+ // --- configuration ---
253
+ get mode() {
254
+ return this._mode;
255
+ }
256
+ set mode(value) {
257
+ this._mode = value === "queue" || value === "exhaust" ? value : "latest";
258
+ }
259
+ get reducedMotion() {
260
+ return this._reducedMotion;
261
+ }
262
+ set reducedMotion(value) {
263
+ this._reducedMotion = value === "animate" ? "animate" : "skip";
264
+ }
265
+ get types() {
266
+ return this._types;
267
+ }
268
+ set types(value) {
269
+ this._types = toStringList(value);
270
+ }
271
+ get disabled() {
272
+ return this._disabled;
273
+ }
274
+ set disabled(value) {
275
+ this._disabled = value === true;
276
+ }
277
+ get participants() {
278
+ return [...this._participants];
279
+ }
280
+ set participants(value) {
281
+ const list = toStringList(value);
282
+ this._participants = new Set(list.length > 0 ? list : DEFAULT_PARTICIPANTS);
283
+ }
284
+ // --- observable outputs ---
285
+ get active() {
286
+ return this._active;
287
+ }
288
+ get error() {
289
+ return this._error;
290
+ }
291
+ // --- commands ---
292
+ /**
293
+ * Finish the running transition now. Per spec the update callback still runs if
294
+ * it has not yet, so skipping loses the animation and never the DOM update.
295
+ */
296
+ skip() {
297
+ this._transition?.skipTransition();
298
+ }
299
+ // --- the protocol entry point ---
300
+ run(mutate, _options) {
301
+ if (!this._canTransition()) {
302
+ return this._applyNow(mutate);
303
+ }
304
+ return new Promise((resolve, reject) => {
305
+ const entry = { mutate, resolve, reject };
306
+ // Capturing: the running transition has not called its update callback yet,
307
+ // so this mutation can still ride along — and must, or it would be applied
308
+ // before mutations that were requested earlier.
309
+ if (this._batch !== null) {
310
+ this._batch.push(entry);
311
+ return;
312
+ }
313
+ if (this._transition !== null && this._mode === "exhaust") {
314
+ this._settle(entry);
315
+ return;
316
+ }
317
+ (this._pending ??= []).push(entry);
318
+ this._schedule();
319
+ });
320
+ }
321
+ dispose() {
322
+ this.uninstall();
323
+ // Anything still unapplied belongs to a page that is going away; apply it so
324
+ // the DOM does not stay behind the state that asked for the change.
325
+ //
326
+ // Order is request order, and that is why the capturing batch has to be taken
327
+ // first: its mutations were requested *before* everything in _pending and
328
+ // _queue, but they are the ones still waiting on a frame. Settling only the
329
+ // later two would apply them out of order. Clearing _batch also turns the
330
+ // running transition's update callback into a no-op, which is what keeps
331
+ // "applied exactly once" true across a dispose.
332
+ const capturing = this._batch;
333
+ this._batch = null;
334
+ const abandoned = [...(capturing ?? []), ...(this._pending ?? []), ...this._queue.flat()];
335
+ this._pending = null;
336
+ this._queue = [];
337
+ for (const entry of abandoned) {
338
+ this._settle(entry);
339
+ }
340
+ }
341
+ // --- internals ---
342
+ _canTransition() {
343
+ if (this._disabled)
344
+ return false;
345
+ const doc = globalThis.document;
346
+ if (doc === undefined || typeof doc.startViewTransition !== "function") {
347
+ return false;
348
+ }
349
+ // SSR: no transition is started while rendering on the server (G5). The gate
350
+ // lives here rather than in each participant because the protocol is public —
351
+ // a third-party participant has no reason to know wcstack's SSR marker, and
352
+ // the arbiter is the one place that owns the policy. `@wcstack/server` sets
353
+ // the attribute on its own document and it never reaches the client HTML.
354
+ if (doc.documentElement?.hasAttribute("data-wcs-server") === true)
355
+ return false;
356
+ // A hidden tab gets no rendering opportunities, so the update callback would
357
+ // not run until the page is looked at again — the DOM would silently freeze
358
+ // for as long as the tab stays in the background. Apply straight through.
359
+ if (doc.hidden === true)
360
+ return false;
361
+ if (this._reducedMotion === "skip" && prefersReducedMotion())
362
+ return false;
363
+ return true;
364
+ }
365
+ _applyNow(mutate) {
366
+ try {
367
+ mutate();
368
+ }
369
+ catch (error) {
370
+ return Promise.reject(error);
371
+ }
372
+ return Promise.resolve();
373
+ }
374
+ _settle(entry) {
375
+ try {
376
+ entry.mutate();
377
+ entry.resolve();
378
+ }
379
+ catch (error) {
380
+ entry.reject(error);
381
+ }
382
+ }
383
+ _schedule() {
384
+ if (this._flushScheduled)
385
+ return;
386
+ this._flushScheduled = true;
387
+ queueMicrotask(() => this._flush());
388
+ }
389
+ _flush() {
390
+ this._flushScheduled = false;
391
+ const batch = this._pending;
392
+ this._pending = null;
393
+ if (batch === null || batch.length === 0)
394
+ return;
395
+ if (this._transition !== null) {
396
+ if (this._mode === "queue") {
397
+ this._queue.push(batch);
398
+ return;
399
+ }
400
+ if (this._mode === "exhaust") {
401
+ for (const entry of batch) {
402
+ this._settle(entry);
403
+ }
404
+ return;
405
+ }
406
+ // "latest": starting a new transition skips the running one, and the
407
+ // running one is past its update callback (a capturing batch is joined in
408
+ // run(), never reaching here), so ordering holds.
409
+ }
410
+ this._start(batch);
411
+ }
412
+ _start(batch) {
413
+ const doc = globalThis.document;
414
+ const start = doc.startViewTransition;
415
+ this._batch = batch;
416
+ const update = () => {
417
+ const running = this._batch;
418
+ this._batch = null;
419
+ if (running === null)
420
+ return;
421
+ for (const entry of running) {
422
+ this._settle(entry);
423
+ }
424
+ };
425
+ let transition;
426
+ try {
427
+ transition = this._types.length > 0 && supportsTypes()
428
+ ? start.call(doc, { update, types: [...this._types] })
429
+ : start.call(doc, update);
430
+ }
431
+ catch (error) {
432
+ // Could not even start: apply the mutations rather than lose them.
433
+ this._batch = null;
434
+ this._setError(toError(error));
435
+ for (const entry of batch) {
436
+ this._settle(entry);
437
+ }
438
+ return;
439
+ }
440
+ this._transition = transition;
441
+ this._setError(null);
442
+ this._setActive(true);
443
+ // `finished` rejects when the update callback throws — it cannot here, since
444
+ // _settle catches per entry — and `ready` rejects whenever the transition is
445
+ // skipped, which is routine. Both are attached defensively so a routine skip
446
+ // never surfaces as an unhandled rejection.
447
+ const done = () => this._onFinished(transition);
448
+ transition.finished.then(done, done);
449
+ transition.ready.then(undefined, () => { });
450
+ transition.updateCallbackDone.then(undefined, () => { });
451
+ }
452
+ _onFinished(transition) {
453
+ // A superseded transition ("latest") still settles; only the current one owns
454
+ // the active flag and the queue.
455
+ if (this._transition !== transition)
456
+ return;
457
+ this._transition = null;
458
+ this._setActive(false);
459
+ const next = this._queue.shift();
460
+ if (next !== undefined) {
461
+ this._start(next);
462
+ }
463
+ }
464
+ _setActive(value) {
465
+ if (this._active === value)
466
+ return;
467
+ this._active = value;
468
+ this._dispatch("wcs-view-transition:active-changed", value);
469
+ }
470
+ _setError(error) {
471
+ if (this._error === error)
472
+ return;
473
+ this._error = error;
474
+ this._dispatch("wcs-view-transition:error", error);
475
+ }
476
+ _dispatch(type, detail) {
477
+ this._target.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
478
+ }
479
+ }
480
+
481
+ // ===========================================================================
482
+ // AUTO-GENERATED FILE - DO NOT EDIT.
483
+ // Generated from /protocol/upgrade-properties.ts by scripts/sync-protocol-types.mjs.
484
+ // Run `node scripts/sync-protocol-types.mjs` after editing the source.
485
+ // ===========================================================================
486
+ function hasAccessorOnPrototype(target, name) {
487
+ let proto = Object.getPrototypeOf(target);
488
+ while (proto !== null) {
489
+ const descriptor = Object.getOwnPropertyDescriptor(proto, name);
490
+ if (descriptor !== undefined) {
491
+ return typeof descriptor.get === "function" || typeof descriptor.set === "function";
492
+ }
493
+ proto = Object.getPrototypeOf(proto);
494
+ }
495
+ return false;
496
+ }
497
+ /**
498
+ * `connectedCallback` の先頭で呼ぶ。宣言済み input のうち upgrade 前の代入で
499
+ * accessor をシャドウしている own プロパティを、delete → 再代入で setter に通し直す。
500
+ *
501
+ * - 冪等: 再代入は accessor を通るので own プロパティは残らず、2 回目以降は no-op。
502
+ * - 宣言に `inputs` が無い要素、`wcBindable` を持たない要素では何もしない。
503
+ * - 値の意味は変えない。今まで捨てられていた代入が届くようになる一方向の変化。
504
+ */
505
+ function upgradeProperties(element) {
506
+ const declaration = element.constructor?.wcBindable;
507
+ const inputs = declaration?.inputs;
508
+ if (inputs === undefined)
509
+ return;
510
+ for (const input of inputs) {
511
+ const name = input.name;
512
+ if (!Object.prototype.hasOwnProperty.call(element, name))
513
+ continue;
514
+ if (!hasAccessorOnPrototype(element, name))
515
+ continue;
516
+ const record = element;
517
+ const value = record[name];
518
+ delete record[name];
519
+ record[name] = value;
520
+ }
521
+ }
522
+
523
+ /**
524
+ * `<wcs-view-transition>` — the page's view-transition policy node.
525
+ *
526
+ * It renders nothing and binds no data. It declares *how* the DOM changes that
527
+ * `@wcstack/router` and `@wcstack/state` make should animate, and it is the single
528
+ * arbiter that decides what happens when two of those changes collide. Dropping
529
+ * the tag on a page is the opt-in; removing it restores the framework's original
530
+ * synchronous behavior exactly (docs/view-transition-design.md §3, G1/G2).
531
+ *
532
+ * ```html
533
+ * <wcs-view-transition for="router" mode="latest"></wcs-view-transition>
534
+ * ```
535
+ *
536
+ * The animation itself is written in CSS against `::view-transition-*`. This tag
537
+ * starts and arbitrates transitions; it never describes one.
538
+ */
539
+ class WcsViewTransition extends HTMLElement {
540
+ static observedAttributes = [
541
+ "mode", "naming", "naming-limit", "reduced-motion", "types", "disabled", "for",
542
+ ];
543
+ // `properties` and `commands` come from the Core through the spread, so a
544
+ // member added there cannot be missed here. Only `inputs` — the attribute
545
+ // surface, which exists on the element and not on the Core — is declared.
546
+ static wcBindable = {
547
+ ...ViewTransitionCore.wcBindable,
548
+ inputs: [
549
+ { name: "disabled", attribute: "disabled" },
550
+ { name: "mode", attribute: "mode" },
551
+ { name: "naming", attribute: "naming" },
552
+ { name: "namingLimit", attribute: "naming-limit" },
553
+ { name: "reducedMotion", attribute: "reduced-motion" },
554
+ { name: "types", attribute: "types" },
555
+ { name: "participants", attribute: "for" },
556
+ ],
557
+ };
558
+ _core;
559
+ _internals = null;
560
+ _installed = false;
561
+ constructor() {
562
+ super();
563
+ this._core = new ViewTransitionCore(this);
564
+ this._internals = this._initInternals();
565
+ this._wireStates({
566
+ "wcs-view-transition:active-changed": (d) => ({ active: d === true }),
567
+ "wcs-view-transition:error": (d) => ({ error: d != null }),
568
+ });
569
+ }
570
+ /** The headless arbiter, for direct (non-DOM) use. */
571
+ get core() {
572
+ return this._core;
573
+ }
574
+ // CSS state reflection (:state()) — debug-only snapshot getter. NOT part of
575
+ // wc-bindable. MUST NOT return the live CustomStateSet.
576
+ get debugStates() {
577
+ return this._internals ? [...this._internals.states] : [];
578
+ }
579
+ _initInternals() {
580
+ // never-throw: attachInternals is absent in happy-dom / older environments,
581
+ // and pre-125 Chromium rejects non-dashed state names (probed and discarded).
582
+ try {
583
+ if (typeof this.attachInternals !== "function")
584
+ return null;
585
+ const internals = this.attachInternals();
586
+ internals.states.add("wcs-probe");
587
+ internals.states.delete("wcs-probe");
588
+ return internals;
589
+ }
590
+ catch {
591
+ return null;
592
+ }
593
+ }
594
+ _wireStates(map) {
595
+ if (this._internals === null)
596
+ return;
597
+ const states = this._internals.states;
598
+ for (const [event, toStates] of Object.entries(map)) {
599
+ this.addEventListener(event, (e) => {
600
+ const debug = this.hasAttribute("debug-states");
601
+ for (const [name, on] of Object.entries(toStates(e.detail))) {
602
+ try {
603
+ if (on) {
604
+ states.add(name);
605
+ }
606
+ else {
607
+ states.delete(name);
608
+ }
609
+ }
610
+ catch { /* never-throw */ }
611
+ if (debug)
612
+ this.toggleAttribute(`data-wcs-state-${name}`, on);
613
+ }
614
+ });
615
+ }
616
+ }
617
+ // --- inputs ---
618
+ get disabled() {
619
+ return this._core.disabled;
620
+ }
621
+ set disabled(value) {
622
+ this._core.disabled = value === true;
623
+ this.toggleAttribute("disabled", value === true);
624
+ }
625
+ get mode() {
626
+ return this._core.mode;
627
+ }
628
+ set mode(value) {
629
+ this._core.mode = value;
630
+ }
631
+ get naming() {
632
+ return this._core.naming;
633
+ }
634
+ set naming(value) {
635
+ this._core.naming = value;
636
+ }
637
+ get namingLimit() {
638
+ return this._core.namingLimit;
639
+ }
640
+ set namingLimit(value) {
641
+ this._core.namingLimit = Number(value);
642
+ }
643
+ get reducedMotion() {
644
+ return this._core.reducedMotion;
645
+ }
646
+ set reducedMotion(value) {
647
+ this._core.reducedMotion = value;
648
+ }
649
+ get types() {
650
+ return this._core.types;
651
+ }
652
+ set types(value) {
653
+ this._core.types = value;
654
+ }
655
+ get participants() {
656
+ return this._core.participants;
657
+ }
658
+ set participants(value) {
659
+ this._core.participants = value;
660
+ }
661
+ // --- observable outputs ---
662
+ get active() {
663
+ return this._core.active;
664
+ }
665
+ get error() {
666
+ return this._core.error;
667
+ }
668
+ // --- commands ---
669
+ skip() {
670
+ this._core.skip();
671
+ }
672
+ // --- lifecycle ---
673
+ connectedCallback() {
674
+ upgradeProperties(this);
675
+ this._syncAllAttributes();
676
+ this._installed = this._core.install();
677
+ }
678
+ disconnectedCallback() {
679
+ if (this._installed) {
680
+ // dispose(), not uninstall(): a mutation already handed to this arbiter must
681
+ // still be applied even though the page just removed its policy tag.
682
+ this._core.dispose();
683
+ this._installed = false;
684
+ }
685
+ }
686
+ attributeChangedCallback(name, oldValue, newValue) {
687
+ if (oldValue === newValue)
688
+ return;
689
+ this._applyAttribute(name, newValue);
690
+ }
691
+ /**
692
+ * Apply the attributes present at connect time. Absent ones are deliberately
693
+ * skipped rather than applied as null: a property assigned before upgrade
694
+ * (Angular's `[prop]`, Lit's `.prop=`, or plain `el.mode = ...`) has just been
695
+ * replayed through the setter by `upgradeProperties`, and re-applying a missing
696
+ * attribute would immediately reset it to the default. Removing an attribute
697
+ * still resets, via `attributeChangedCallback`.
698
+ */
699
+ _syncAllAttributes() {
700
+ for (const name of WcsViewTransition.observedAttributes) {
701
+ const value = this.getAttribute(name);
702
+ if (value === null)
703
+ continue;
704
+ this._applyAttribute(name, value);
705
+ }
706
+ }
707
+ _applyAttribute(name, value) {
708
+ switch (name) {
709
+ case "mode":
710
+ this._core.mode = (value ?? "latest");
711
+ break;
712
+ case "naming":
713
+ this._core.naming = (value ?? "manual");
714
+ break;
715
+ case "naming-limit":
716
+ this._core.namingLimit = value === null ? Number.NaN : Number(value);
717
+ break;
718
+ case "reduced-motion":
719
+ this._core.reducedMotion = (value ?? "skip");
720
+ break;
721
+ case "types":
722
+ this._core.types = value ?? "";
723
+ break;
724
+ case "disabled":
725
+ this._core.disabled = value !== null;
726
+ break;
727
+ case "for":
728
+ this._core.participants = value ?? "";
729
+ break;
730
+ }
731
+ }
732
+ }
733
+
734
+ /**
735
+ * Register this package's tags. Pass a scoped `CustomElementRegistry` to define
736
+ * them for a single shadow tree -- scoped registries do not inherit the global
737
+ * one, so a tree using one needs its own definitions.
738
+ */
739
+ function registerComponents(registry = customElements) {
740
+ if (!registry.get(config.tagNames.viewTransition)) {
741
+ registry.define(config.tagNames.viewTransition, WcsViewTransition);
742
+ }
743
+ }
744
+
745
+ function bootstrapViewTransition(userConfig, registry) {
746
+ if (userConfig) {
747
+ setConfig(userConfig);
748
+ }
749
+ registerComponents(registry);
750
+ }
751
+
752
+ export { TRANSITION_RUNNER_KEY, ViewTransitionCore, WcsViewTransition, bootstrapViewTransition, getConfig, getTransitionRunner, runTransition };
753
+ //# sourceMappingURL=index.esm.js.map