kuinetic 0.2.1 → 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/README.md +15 -8
- package/dist/esm/advanced/index.mjs +5 -5
- package/dist/esm/{chunk-U43BX5O2.mjs → chunk-FASAEQDR.mjs} +54 -7
- package/dist/esm/{chunk-BN2Y4UKB.mjs → chunk-FYMZW5N2.mjs} +2373 -541
- package/dist/esm/{chunk-RVTYN6NZ.mjs → chunk-MZQG2VMG.mjs} +208 -2
- package/dist/esm/core/index.mjs +21 -3
- package/dist/esm/effects/index.mjs +2 -2
- package/dist/esm/index.mjs +35 -4
- package/dist/esm/notes/index.mjs +1075 -0
- package/dist/kuinetic.advanced.js +63 -25
- package/dist/kuinetic.advanced.min.js +3 -3
- package/dist/kuinetic.all.js +2658 -538
- package/dist/kuinetic.all.min.js +2 -2
- package/dist/kuinetic.css +238 -65
- package/dist/kuinetic.js +2657 -537
- package/dist/kuinetic.min.css +1 -1
- package/dist/kuinetic.min.js +2 -2
- package/dist/kuinetic.notes.js +1 -0
- package/dist/types/3d/flattening.d.ts +8 -7
- package/dist/types/advanced/camera-3d.d.ts +0 -4
- package/dist/types/advanced/fluid-cursor.d.ts +0 -4
- package/dist/types/advanced/particles.d.ts +0 -4
- package/dist/types/advanced/scenes.d.ts +0 -4
- package/dist/types/browser/boot.d.ts +9 -4
- package/dist/types/core/activation.d.ts +11 -0
- package/dist/types/core/compile.d.ts +7 -0
- package/dist/types/core/deck-viewer.d.ts +87 -0
- package/dist/types/core/derived/types.d.ts +0 -1
- package/dist/types/core/describe.d.ts +174 -0
- package/dist/types/core/gesture.d.ts +16 -1
- package/dist/types/core/index.d.ts +6 -2
- package/dist/types/core/stagger.d.ts +1 -1
- package/dist/types/core/target.d.ts +28 -0
- package/dist/types/core/timing-contract.d.ts +61 -0
- package/dist/types/core/types.d.ts +59 -0
- package/dist/types/effects/auto-motion.d.ts +169 -0
- package/dist/types/effects/carousel/deck.d.ts +170 -0
- package/dist/types/effects/carousel/drag.d.ts +6 -0
- package/dist/types/effects/carousel/fit.d.ts +130 -0
- package/dist/types/effects/carousel/index.d.ts +6 -23
- package/dist/types/effects/carousel/stack.d.ts +77 -0
- package/dist/types/effects/catalog/interaction-reveal.d.ts +69 -9
- package/dist/types/effects/forms/primitives.d.ts +1 -2
- package/dist/types/effects/gestures/index.d.ts +3 -4
- package/dist/types/effects/shared.d.ts +3 -30
- package/dist/types/effects/step-index.d.ts +48 -10
- package/dist/types/effects/swipe-event.d.ts +75 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/notes/catalog-core.d.ts +3 -0
- package/dist/types/notes/catalog.d.ts +3 -0
- package/dist/types/notes/index.d.ts +13 -0
- package/dist/types/notes/interaction-reveal.d.ts +3 -0
- package/dist/types/notes/interaction.d.ts +6 -0
- package/dist/types/notes/keys.d.ts +7 -0
- package/dist/types/notes/mechanics.d.ts +7 -0
- package/dist/types/notes/scroll-nav-forms.d.ts +6 -0
- package/dist/types/notes/shared.d.ts +6 -0
- package/dist/types/notes/showcase.d.ts +7 -0
- package/dist/types/notes/text.d.ts +3 -0
- package/dist/types/showcase/compare.d.ts +9 -0
- package/dist/types/showcase/index.d.ts +3 -0
- package/dist/types/showcase/lightbox.d.ts +23 -1
- package/dist/types/showcase/modal-shell.d.ts +11 -0
- package/package.json +10 -4
package/dist/kuinetic.js
CHANGED
|
@@ -25,10 +25,13 @@ var kuinetic = (() => {
|
|
|
25
25
|
Animator: () => Animator,
|
|
26
26
|
CHANNEL: () => CHANNEL,
|
|
27
27
|
COMBOS: () => COMBOS,
|
|
28
|
+
KEY_NOTE_OWNER: () => KEY_NOTE_OWNER,
|
|
28
29
|
KUI_EVENT: () => KUI_EVENT,
|
|
29
30
|
PRESETS: () => PRESETS,
|
|
30
31
|
PRIMITIVES: () => PRIMITIVES,
|
|
31
32
|
Registry: () => Registry,
|
|
33
|
+
SUBTREE_CHANNEL: () => SUBTREE_CHANNEL,
|
|
34
|
+
attributeChannel: () => attributeChannel,
|
|
32
35
|
collectingReporter: () => collectingReporter,
|
|
33
36
|
consoleReporter: () => consoleReporter,
|
|
34
37
|
control: () => control,
|
|
@@ -36,6 +39,13 @@ var kuinetic = (() => {
|
|
|
36
39
|
createAnimator: () => createAnimator,
|
|
37
40
|
createRegistry: () => createRegistry,
|
|
38
41
|
default: () => src_default,
|
|
42
|
+
describe: () => describe2,
|
|
43
|
+
describeAttribute: () => describeAttribute,
|
|
44
|
+
describeEffect: () => describeEffect,
|
|
45
|
+
describeElement: () => describeElement,
|
|
46
|
+
describeElementAttribute: () => describeElementAttribute,
|
|
47
|
+
describeKeys: () => describeKeys,
|
|
48
|
+
describeSteps: () => describeSteps,
|
|
39
49
|
detect: () => detect,
|
|
40
50
|
inertInstance: () => inertInstance,
|
|
41
51
|
kuinetic: () => kuinetic,
|
|
@@ -43,6 +53,8 @@ var kuinetic = (() => {
|
|
|
43
53
|
resolveActivationSpec: () => resolveActivationSpec,
|
|
44
54
|
resolveTargets: () => resolveTargets,
|
|
45
55
|
silentReporter: () => silentReporter,
|
|
56
|
+
spellingsFor: () => spellingsFor,
|
|
57
|
+
timingContractOf: () => timingContractOf,
|
|
46
58
|
toAttributeValue: () => toAttributeValue
|
|
47
59
|
});
|
|
48
60
|
|
|
@@ -84,6 +96,10 @@ var kuinetic = (() => {
|
|
|
84
96
|
function queryScoped(el, ctx, selector, scope) {
|
|
85
97
|
return [...(scope === "page" ? ctx.doc : el).querySelectorAll(selector)];
|
|
86
98
|
}
|
|
99
|
+
function queryControls(el, ctx, selector) {
|
|
100
|
+
const inside = [...el.querySelectorAll(selector)];
|
|
101
|
+
return inside.length > 0 ? inside : [...ctx.doc.querySelectorAll(selector)];
|
|
102
|
+
}
|
|
87
103
|
|
|
88
104
|
// src/core/event-sources.ts
|
|
89
105
|
function createEventSourceBindings() {
|
|
@@ -589,18 +605,25 @@ var kuinetic = (() => {
|
|
|
589
605
|
if (typeof IntersectionObserver === "undefined") return void 0;
|
|
590
606
|
return (callback, init) => new IntersectionObserver(callback, init);
|
|
591
607
|
}
|
|
608
|
+
function onlyBoundLate(name, claims) {
|
|
609
|
+
const authorising = authorisingActivations(name);
|
|
610
|
+
if (!authorising.includes("enter")) return false;
|
|
611
|
+
const suits = (claim2) => authorising.some((named) => claim2.supported.includes(named));
|
|
612
|
+
if (!claims.some((claim2) => claim2.entrance && suits(claim2))) return false;
|
|
613
|
+
return claims.every((claim2) => suits(claim2) || claim2.supported.includes("load"));
|
|
614
|
+
}
|
|
592
615
|
function warnAboutActivation(request) {
|
|
593
616
|
warnUnsupported(request);
|
|
594
617
|
warnUnknownEvents(request);
|
|
595
618
|
}
|
|
596
|
-
function warnUnsupported({ el, spec, supported, reporter }) {
|
|
597
|
-
|
|
619
|
+
function warnUnsupported({ el, spec, supported, claims, reporter }) {
|
|
620
|
+
const declaredAny = supported.length > 0 || (claims?.some((claim2) => claim2.supported.length > 0) ?? false);
|
|
621
|
+
if (!declaredAny) return;
|
|
622
|
+
const shared = supported.length > 0 ? supported.join(", ") : "none in common across the composed effects";
|
|
598
623
|
for (const name of spec.names) {
|
|
599
624
|
if (authorisingActivations(name).some((named) => supported.includes(named))) continue;
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
el
|
|
603
|
-
);
|
|
625
|
+
if (claims && onlyBoundLate(name, claims)) continue;
|
|
626
|
+
reporter.warn(`activation "${name}" is not supported by this effect (supports: ${shared})`, el);
|
|
604
627
|
}
|
|
605
628
|
}
|
|
606
629
|
function warnUnknownEvents({ el, spec, reporter }) {
|
|
@@ -1248,6 +1271,10 @@ var kuinetic = (() => {
|
|
|
1248
1271
|
*/
|
|
1249
1272
|
skew: "skew"
|
|
1250
1273
|
};
|
|
1274
|
+
function attributeChannel(attribute) {
|
|
1275
|
+
return `attr:${attribute}`;
|
|
1276
|
+
}
|
|
1277
|
+
var SUBTREE_CHANNEL = "subtree";
|
|
1251
1278
|
function inertInstance(destroy = () => {
|
|
1252
1279
|
}) {
|
|
1253
1280
|
return {
|
|
@@ -3427,6 +3454,7 @@ var kuinetic = (() => {
|
|
|
3427
3454
|
unknown,
|
|
3428
3455
|
reducedMotion: "shorten",
|
|
3429
3456
|
supportedActivations: [],
|
|
3457
|
+
activationClaims: [],
|
|
3430
3458
|
supportedTimelines: [],
|
|
3431
3459
|
channels: [],
|
|
3432
3460
|
warnings
|
|
@@ -3560,6 +3588,10 @@ var kuinetic = (() => {
|
|
|
3560
3588
|
plan.fxNames.push(preset.name);
|
|
3561
3589
|
plan.reducedMotion = strictestPolicy(plan.reducedMotion, primitive.reducedMotion);
|
|
3562
3590
|
activations = intersect(activations, primitive.supportedActivations);
|
|
3591
|
+
plan.activationClaims.push({
|
|
3592
|
+
supported: primitive.supportedActivations,
|
|
3593
|
+
entrance: phaseOf(entry) === "entrance"
|
|
3594
|
+
});
|
|
3563
3595
|
timelines = intersect(timelines, primitive.supportedTimelines);
|
|
3564
3596
|
for (const channel of channelsFor(entry)) channels.add(channel);
|
|
3565
3597
|
warnUnsupportedTimeline(preset.name, primitive.supportedTimelines, timeline, warnings);
|
|
@@ -5125,11 +5157,20 @@ var kuinetic = (() => {
|
|
|
5125
5157
|
const windowRoot = windowScrollRoot(win);
|
|
5126
5158
|
return (el) => {
|
|
5127
5159
|
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
5160
|
+
if (scrollsTheViewport(node, win)) return windowRoot;
|
|
5128
5161
|
if (isScrollable(node)) return elementScrollRoot(node, win);
|
|
5129
5162
|
}
|
|
5130
5163
|
return windowRoot;
|
|
5131
5164
|
};
|
|
5132
5165
|
}
|
|
5166
|
+
function scrollsTheViewport(node, win) {
|
|
5167
|
+
const root = win.document.documentElement;
|
|
5168
|
+
if (node === root) return true;
|
|
5169
|
+
if (node !== win.document.body) return false;
|
|
5170
|
+
const rootStyle = win.getComputedStyle(root);
|
|
5171
|
+
return !CLIPPING_OVERFLOW.has(rootStyle.overflowX) && !CLIPPING_OVERFLOW.has(rootStyle.overflowY);
|
|
5172
|
+
}
|
|
5173
|
+
var CLIPPING_OVERFLOW = /* @__PURE__ */ new Set(["auto", "scroll", "overlay", "hidden", "clip"]);
|
|
5133
5174
|
var SCROLLABLE_OVERFLOW = /* @__PURE__ */ new Set(["auto", "scroll", "overlay"]);
|
|
5134
5175
|
function defaultIsScrollable(win) {
|
|
5135
5176
|
return (el) => {
|
|
@@ -5632,6 +5673,7 @@ var kuinetic = (() => {
|
|
|
5632
5673
|
el,
|
|
5633
5674
|
spec: resolveActivationSpec(config.activation),
|
|
5634
5675
|
supported: plan.supportedActivations,
|
|
5676
|
+
claims: plan.activationClaims,
|
|
5635
5677
|
reporter: this.reporter
|
|
5636
5678
|
});
|
|
5637
5679
|
return config.activation;
|
|
@@ -6517,8 +6559,8 @@ var kuinetic = (() => {
|
|
|
6517
6559
|
shouldObserve: options.observe ?? false
|
|
6518
6560
|
};
|
|
6519
6561
|
}
|
|
6520
|
-
function resolveJsEffectPreparer(
|
|
6521
|
-
return
|
|
6562
|
+
function resolveJsEffectPreparer(provided2, deps) {
|
|
6563
|
+
return provided2 ?? createJsEffectPreparer(deps);
|
|
6522
6564
|
}
|
|
6523
6565
|
function defaultRootResolver(root) {
|
|
6524
6566
|
const doc = isElementNode(root) ? root.ownerDocument : root;
|
|
@@ -6535,11 +6577,214 @@ var kuinetic = (() => {
|
|
|
6535
6577
|
}
|
|
6536
6578
|
}
|
|
6537
6579
|
|
|
6580
|
+
// src/core/timing-contract.ts
|
|
6581
|
+
var ALL_TIMING_TOKENS = ["duration", "delay", "ease"];
|
|
6582
|
+
var CONTRACTS = /* @__PURE__ */ new WeakMap();
|
|
6583
|
+
function declareTimingContract(prepare, contract) {
|
|
6584
|
+
CONTRACTS.set(prepare, contract);
|
|
6585
|
+
return prepare;
|
|
6586
|
+
}
|
|
6587
|
+
function inheritTimingContract(inner, outer) {
|
|
6588
|
+
const contract = CONTRACTS.get(inner);
|
|
6589
|
+
return contract ? declareTimingContract(outer, contract) : outer;
|
|
6590
|
+
}
|
|
6591
|
+
function timingContractOf(primitive) {
|
|
6592
|
+
return primitive.prepare ? CONTRACTS.get(primitive.prepare) : void 0;
|
|
6593
|
+
}
|
|
6594
|
+
|
|
6595
|
+
// src/core/describe.ts
|
|
6596
|
+
var POSITIONAL = [
|
|
6597
|
+
{ slot: "duration", accepts: "time" },
|
|
6598
|
+
{ slot: "delay", accepts: "time" },
|
|
6599
|
+
{ slot: "ease", accepts: "easing" }
|
|
6600
|
+
];
|
|
6601
|
+
function describeEffect(registry, name, notes = {}) {
|
|
6602
|
+
const resolved = registry.resolve(name);
|
|
6603
|
+
if (!resolved) return void 0;
|
|
6604
|
+
const { preset, primitive } = resolved;
|
|
6605
|
+
const declared = primitive.parameters;
|
|
6606
|
+
const contract = timingContractOf(primitive);
|
|
6607
|
+
const honours = contract ? contract.honours ?? [] : ALL_TIMING_TOKENS;
|
|
6608
|
+
const positionalOrder = POSITIONAL.map((slot) => ({
|
|
6609
|
+
...slot,
|
|
6610
|
+
...Object.hasOwn(declared, slot.slot) ? { param: slot.slot } : {},
|
|
6611
|
+
honoured: honours.includes(slot.slot)
|
|
6612
|
+
}));
|
|
6613
|
+
const params = Object.entries(declared).map(([param, spec]) => {
|
|
6614
|
+
const override = preset.params?.[param];
|
|
6615
|
+
const note = findNote(spec.note, notes, [preset.name, primitive.id, "*"], param);
|
|
6616
|
+
return {
|
|
6617
|
+
name: param,
|
|
6618
|
+
type: spec.type,
|
|
6619
|
+
default: override ?? spec.default,
|
|
6620
|
+
presetDefault: override !== void 0,
|
|
6621
|
+
required: note?.required === true,
|
|
6622
|
+
spellings: spellingsFor(spec, override ?? spec.default),
|
|
6623
|
+
...optionalFields(spec, param, note)
|
|
6624
|
+
};
|
|
6625
|
+
});
|
|
6626
|
+
const described = {
|
|
6627
|
+
name: preset.name,
|
|
6628
|
+
primitive: primitive.id,
|
|
6629
|
+
renderer: primitive.renderer,
|
|
6630
|
+
params,
|
|
6631
|
+
positionalOrder
|
|
6632
|
+
};
|
|
6633
|
+
if (contract && honours.length < ALL_TIMING_TOKENS.length) described.unhonouredBecause = contract.because;
|
|
6634
|
+
return described;
|
|
6635
|
+
}
|
|
6636
|
+
function optionalFields(spec, param, note) {
|
|
6637
|
+
const out = {};
|
|
6638
|
+
if (spec.keywords) out.keywords = spec.keywords;
|
|
6639
|
+
if (spec.minimum !== void 0) out.minimum = spec.minimum;
|
|
6640
|
+
if (spec.maximum !== void 0) out.maximum = spec.maximum;
|
|
6641
|
+
if (spec.integer) out.integer = true;
|
|
6642
|
+
const index = POSITIONAL.findIndex((slot) => slot.slot === param);
|
|
6643
|
+
if (index >= 0) out.positionalIndex = index;
|
|
6644
|
+
if (note) out.note = note.why;
|
|
6645
|
+
if (note?.whenOmitted) out.whenOmitted = note.whenOmitted;
|
|
6646
|
+
return out;
|
|
6647
|
+
}
|
|
6648
|
+
function describeAttribute(registry, value, notes = {}) {
|
|
6649
|
+
return describeSpecs(registry, parse(value).specs, notes);
|
|
6650
|
+
}
|
|
6651
|
+
function describeElementAttribute(registry, value, notes = {}) {
|
|
6652
|
+
const parsed = parse(value);
|
|
6653
|
+
const written = {};
|
|
6654
|
+
for (const [field, key] of Object.entries(ELEMENT_KEYS)) {
|
|
6655
|
+
const set = parsed[field];
|
|
6656
|
+
if (set !== void 0) written[key.key] = set;
|
|
6657
|
+
}
|
|
6658
|
+
return { steps: describeSpecs(registry, parsed.specs, notes), keys: describeKeys(notes), written };
|
|
6659
|
+
}
|
|
6660
|
+
function describeKeys(notes = {}) {
|
|
6661
|
+
const element = Object.values(ELEMENT_KEYS).map((key) => keyRow(key, "element", notes));
|
|
6662
|
+
return [...element, ...STEP_KEY_LIST.map((key) => keyRow(key, "step", notes))];
|
|
6663
|
+
}
|
|
6664
|
+
function describeSpecs(registry, specs, notes) {
|
|
6665
|
+
return specs.map((spec) => {
|
|
6666
|
+
const written = { written: writtenArgs(spec), writtenKeys: writtenStepKeys(spec) };
|
|
6667
|
+
const described = describeEffect(registry, spec.name, notes);
|
|
6668
|
+
if (described) return { ...described, ...written };
|
|
6669
|
+
const suggestion = suggest(spec.name, registry.names());
|
|
6670
|
+
const unknown = suggestion ? { name: spec.name, unknown: true, suggestion } : { name: spec.name, unknown: true };
|
|
6671
|
+
return { ...unknown, ...written };
|
|
6672
|
+
});
|
|
6673
|
+
}
|
|
6674
|
+
function writtenArgs(spec) {
|
|
6675
|
+
const written = { ...spec.params };
|
|
6676
|
+
if (spec.duration !== void 0) written.duration = spec.duration;
|
|
6677
|
+
if (spec.delay !== void 0) written.delay = spec.delay;
|
|
6678
|
+
if (spec.easing !== void 0) written.ease = spec.easing;
|
|
6679
|
+
return written;
|
|
6680
|
+
}
|
|
6681
|
+
function writtenStepKeys(spec) {
|
|
6682
|
+
const written = {};
|
|
6683
|
+
if (spec.at !== void 0) written.at = spec.at;
|
|
6684
|
+
Object.assign(written, spec.gate);
|
|
6685
|
+
if (spec.repeat !== void 0) written.repeat = spec.repeat;
|
|
6686
|
+
if (spec.yoyo !== void 0) written.yoyo = String(spec.yoyo);
|
|
6687
|
+
for (const [field, value] of Object.entries(spec.hoists ?? {})) {
|
|
6688
|
+
written[ELEMENT_KEYS[field].key] = value;
|
|
6689
|
+
}
|
|
6690
|
+
return written;
|
|
6691
|
+
}
|
|
6692
|
+
var KEY_NOTE_OWNER = "data-kui";
|
|
6693
|
+
function keyRow(key, scope, notes) {
|
|
6694
|
+
const note = findNote(void 0, notes, [KEY_NOTE_OWNER], key.key);
|
|
6695
|
+
const row = {
|
|
6696
|
+
name: key.key,
|
|
6697
|
+
type: key.type,
|
|
6698
|
+
default: key.default,
|
|
6699
|
+
presetDefault: false,
|
|
6700
|
+
required: false,
|
|
6701
|
+
spellings: [...key.spellings],
|
|
6702
|
+
scope
|
|
6703
|
+
};
|
|
6704
|
+
if (key.keywords) row.keywords = key.keywords;
|
|
6705
|
+
if (note) row.note = note.why;
|
|
6706
|
+
if (note?.whenOmitted) row.whenOmitted = note.whenOmitted;
|
|
6707
|
+
return row;
|
|
6708
|
+
}
|
|
6709
|
+
var TIMELINES2 = Object.keys({ time: 0, view: 0, scroll: 0, pointer: 0, pin: 0 });
|
|
6710
|
+
var RM_POLICIES2 = Object.keys({ shorten: 0, crossfade: 0, disable: 0 });
|
|
6711
|
+
var ELEMENT_KEYS = {
|
|
6712
|
+
activation: { key: "on", type: "text", default: "", spellings: ["enter", "load", "hover", "click", "hover/unhover"] },
|
|
6713
|
+
actions: { key: "actions", type: "text", default: "", spellings: ["play/pause/resume/reset", "play/none/none/reset"] },
|
|
6714
|
+
timeline: { key: "timeline", type: "keyword", default: "", spellings: TIMELINES2, keywords: TIMELINES2 },
|
|
6715
|
+
threshold: { key: "threshold", type: "percentage", default: "", spellings: ["0%", "25%"] },
|
|
6716
|
+
cascade: { key: "cascade", type: "time", default: "", spellings: ["90ms", "0.09s"] },
|
|
6717
|
+
spread: { key: "spread", type: "time", default: "", spellings: ["600ms", "0.6s"] },
|
|
6718
|
+
order: { key: "order", type: "text", default: "", spellings: ["start", "end", "center", "edges", "random", "3"] },
|
|
6719
|
+
cols: { key: "cols", type: "text", default: "", spellings: ["auto", "4"] },
|
|
6720
|
+
along: { key: "along", type: "keyword", default: "", spellings: ["x", "y"], keywords: ["x", "y"] },
|
|
6721
|
+
rm: { key: "rm", type: "keyword", default: "", spellings: RM_POLICIES2, keywords: RM_POLICIES2 },
|
|
6722
|
+
func: { key: "func", type: "text", default: "", spellings: ["onRevealDone"] }
|
|
6723
|
+
};
|
|
6724
|
+
var gateKey = (key) => ({
|
|
6725
|
+
key,
|
|
6726
|
+
type: "keyword",
|
|
6727
|
+
default: "",
|
|
6728
|
+
spellings: BREAKPOINT_NAMES,
|
|
6729
|
+
keywords: BREAKPOINT_NAMES
|
|
6730
|
+
});
|
|
6731
|
+
var STEP_KEYS = {
|
|
6732
|
+
at: { key: "at", type: "text", default: "", spellings: ["after", "with", "-200ms", "with+100ms"] },
|
|
6733
|
+
gate: { above: gateKey("above"), below: gateKey("below"), wide: gateKey("wide"), narrow: gateKey("narrow") },
|
|
6734
|
+
repeat: { key: "repeat", type: "text", default: "", spellings: ["infinite", "3"] },
|
|
6735
|
+
yoyo: { key: "yoyo", type: "keyword", default: "", spellings: ["true", "false"], keywords: ["true", "false"] }
|
|
6736
|
+
};
|
|
6737
|
+
var STEP_KEY_LIST = Object.values(STEP_KEYS).flatMap(
|
|
6738
|
+
(entry) => "key" in entry ? [entry] : Object.values(entry)
|
|
6739
|
+
);
|
|
6740
|
+
function findNote(inline, notes, owners, param) {
|
|
6741
|
+
const key = owners.map((owner) => `${owner}.${param}`).find((k) => Object.hasOwn(notes, k));
|
|
6742
|
+
const note = inline ?? (key === void 0 ? void 0 : notes[key]);
|
|
6743
|
+
if (note === void 0) return void 0;
|
|
6744
|
+
return typeof note === "string" ? { why: note } : note;
|
|
6745
|
+
}
|
|
6746
|
+
var NUMBER = /^-?(?:\d+(?:\.\d+)?|\.\d+)/;
|
|
6747
|
+
function spellingsFor(spec, fallback = spec.default) {
|
|
6748
|
+
const value = validate(fallback, spec).ok ? fallback.trim() : "";
|
|
6749
|
+
return EXAMPLES[spec.type](value, spec.keywords ?? []);
|
|
6750
|
+
}
|
|
6751
|
+
var EXAMPLES = {
|
|
6752
|
+
angle: (value) => angleForms(value),
|
|
6753
|
+
"angle|keyword": (value, keywords) => [...angleForms(keywords.includes(value) ? "" : value), ...keywords],
|
|
6754
|
+
keyword: (_, keywords) => [...keywords],
|
|
6755
|
+
time: timeForms,
|
|
6756
|
+
number: (value) => [value || "1"],
|
|
6757
|
+
percentage: (value) => [value || "50%"],
|
|
6758
|
+
"number|percentage": alphaForms,
|
|
6759
|
+
length: (value) => [value || "24px", "calc(1rem + 4px)"],
|
|
6760
|
+
"length|percentage": (value) => [value || "24px", "50%"],
|
|
6761
|
+
color: (value) => [value || "#e4f222", "rgb(228 242 34)", "currentcolor"],
|
|
6762
|
+
easing: (value) => [value || "ease-out", "linear", "cubic-bezier(.2, .8, .2, 1)", "spring"],
|
|
6763
|
+
path: () => ["M0,0L100,0"],
|
|
6764
|
+
text: () => []
|
|
6765
|
+
};
|
|
6766
|
+
function angleForms(value) {
|
|
6767
|
+
const n = NUMBER.exec(value)?.[0];
|
|
6768
|
+
const degrees = value.endsWith("deg") || /^-?[\d.]+d?$/.test(value);
|
|
6769
|
+
const base = degrees && n ? n : "45";
|
|
6770
|
+
const forms = [base, `${base}d`, `${base}deg`];
|
|
6771
|
+
return degrees || !value ? forms : [value, ...forms];
|
|
6772
|
+
}
|
|
6773
|
+
function timeForms(value) {
|
|
6774
|
+
const n = NUMBER.exec(value)?.[0];
|
|
6775
|
+
if (!n) return ["600ms", "0.6s"];
|
|
6776
|
+
return value.endsWith("ms") ? [value, `${decimalNumber(n, -3)}s`] : [value, `${decimalNumber(n, 3)}ms`];
|
|
6777
|
+
}
|
|
6778
|
+
function alphaForms(value) {
|
|
6779
|
+
const n = NUMBER.exec(value)?.[0];
|
|
6780
|
+
if (!n) return ["0.5", "50%"];
|
|
6781
|
+
return value.endsWith("%") ? [`${decimalNumber(n, -2)}`, value] : [value, `${decimalNumber(n, 2)}%`];
|
|
6782
|
+
}
|
|
6783
|
+
|
|
6538
6784
|
// src/effects/shared.ts
|
|
6539
6785
|
var TRIGGER_DELAY_PARAM = {
|
|
6540
6786
|
delay: { type: "time", default: "0ms", cssProperty: "--kui-delay" }
|
|
6541
6787
|
};
|
|
6542
|
-
var ALL_TIMING_TOKENS = ["duration", "delay", "ease"];
|
|
6543
6788
|
function authoredTiming(params, token) {
|
|
6544
6789
|
if (token === "ease") return params.timing.easing;
|
|
6545
6790
|
const ms = token === "duration" ? params.timing.durationMs : params.timing.delayMs;
|
|
@@ -6567,20 +6812,20 @@ var kuinetic = (() => {
|
|
|
6567
6812
|
}
|
|
6568
6813
|
}
|
|
6569
6814
|
function withTimingContract(id, contract, prepare) {
|
|
6570
|
-
return (el, params, ctx) => {
|
|
6815
|
+
return declareTimingContract((el, params, ctx) => {
|
|
6571
6816
|
warnUnhonouredTiming(id, contract, params, ctx.warn);
|
|
6572
6817
|
return prepare(el, params, ctx);
|
|
6573
|
-
};
|
|
6818
|
+
}, contract);
|
|
6574
6819
|
}
|
|
6575
6820
|
function stylesheetTimingPrepare(id, contract) {
|
|
6576
|
-
return (el, params, ctx) => {
|
|
6821
|
+
return declareTimingContract((el, params, ctx) => {
|
|
6577
6822
|
warnUnhonouredTiming(id, contract, params, ctx.warn);
|
|
6578
6823
|
return deferredInstance(() => {
|
|
6579
6824
|
mirrorTimingToCss(id, contract.honours ?? [], params, ctx);
|
|
6580
6825
|
return () => {
|
|
6581
6826
|
};
|
|
6582
6827
|
});
|
|
6583
|
-
};
|
|
6828
|
+
}, contract);
|
|
6584
6829
|
}
|
|
6585
6830
|
var TIMELINE_AGNOSTIC = ["time", "view", "scroll", "pin"];
|
|
6586
6831
|
var COMMON = {
|
|
@@ -6657,12 +6902,12 @@ var kuinetic = (() => {
|
|
|
6657
6902
|
* What the ring rules actually write (`ambient.css`) beyond the gradient: `mask` +
|
|
6658
6903
|
* `mask-composite`, which subtract the element's own content box to leave a ring — the same
|
|
6659
6904
|
* physical property `media-mask` claims under the `'mask'` channel — and `position: relative`
|
|
6660
|
-
* plus a `padding` that *is* the ring's thickness
|
|
6661
|
-
*
|
|
6662
|
-
* `
|
|
6663
|
-
*
|
|
6905
|
+
* plus a `padding` that *is* the ring's thickness — `position` as `pin` and `background-media`
|
|
6906
|
+
* claim it, `padding` as `header-shrink` does. Declared only as `background`, a
|
|
6907
|
+
* `gradient-border, pin-section` pair composed silently while both decided the host's
|
|
6908
|
+
* `position`, and `gradient-border, mask-reveal` while both wrote `mask`.
|
|
6664
6909
|
*/
|
|
6665
|
-
cssPrimitive("ambient-gradient-ring", [CHANNEL.background, "mask", "
|
|
6910
|
+
cssPrimitive("ambient-gradient-ring", [CHANNEL.background, "mask", "position", "padding"], {
|
|
6666
6911
|
parameters: drift,
|
|
6667
6912
|
defaultActivation: "load",
|
|
6668
6913
|
reducedMotion: "disable",
|
|
@@ -7340,22 +7585,287 @@ var kuinetic = (() => {
|
|
|
7340
7585
|
var STATE_PRIMITIVES = [...PRESS_PRIMITIVES, ...GROUP_PRIMITIVES];
|
|
7341
7586
|
var STATE_PRESETS = [...PRESS_PRESETS, ...GROUP_PRESETS];
|
|
7342
7587
|
|
|
7588
|
+
// src/core/element-size.ts
|
|
7589
|
+
function watchElementSize(win, onChange) {
|
|
7590
|
+
const ResizeObserverCtor = win.ResizeObserver;
|
|
7591
|
+
const observer = ResizeObserverCtor ? new ResizeObserverCtor(onChange) : void 0;
|
|
7592
|
+
const elements = /* @__PURE__ */ new Set();
|
|
7593
|
+
win.addEventListener("resize", onChange, { passive: true });
|
|
7594
|
+
return {
|
|
7595
|
+
observe(element) {
|
|
7596
|
+
if (elements.has(element)) return;
|
|
7597
|
+
elements.add(element);
|
|
7598
|
+
observer?.observe(element);
|
|
7599
|
+
},
|
|
7600
|
+
unobserve(element) {
|
|
7601
|
+
elements.delete(element);
|
|
7602
|
+
observer?.unobserve(element);
|
|
7603
|
+
},
|
|
7604
|
+
disconnect() {
|
|
7605
|
+
win.removeEventListener("resize", onChange);
|
|
7606
|
+
observer?.disconnect();
|
|
7607
|
+
elements.clear();
|
|
7608
|
+
}
|
|
7609
|
+
};
|
|
7610
|
+
}
|
|
7611
|
+
function frameScheduler(win, callback) {
|
|
7612
|
+
let frame;
|
|
7613
|
+
let timeout;
|
|
7614
|
+
const run = () => {
|
|
7615
|
+
frame = void 0;
|
|
7616
|
+
timeout = void 0;
|
|
7617
|
+
callback();
|
|
7618
|
+
};
|
|
7619
|
+
return {
|
|
7620
|
+
request() {
|
|
7621
|
+
if (frame !== void 0 || timeout !== void 0) return;
|
|
7622
|
+
if (win.requestAnimationFrame) frame = win.requestAnimationFrame(run);
|
|
7623
|
+
else timeout = win.setTimeout(run, 16);
|
|
7624
|
+
},
|
|
7625
|
+
cancel() {
|
|
7626
|
+
if (frame !== void 0) win.cancelAnimationFrame(frame);
|
|
7627
|
+
if (timeout !== void 0) win.clearTimeout(timeout);
|
|
7628
|
+
frame = void 0;
|
|
7629
|
+
timeout = void 0;
|
|
7630
|
+
}
|
|
7631
|
+
};
|
|
7632
|
+
}
|
|
7633
|
+
|
|
7343
7634
|
// src/effects/catalog/interaction-reveal.ts
|
|
7344
|
-
function revealPrimitive(id, channels, parameters, perfClass = "compositor") {
|
|
7635
|
+
function revealPrimitive(id, channels, parameters, { perfClass = "compositor", placed } = {}) {
|
|
7636
|
+
const contract = {
|
|
7637
|
+
honours: ALL_TIMING_TOKENS,
|
|
7638
|
+
because: "interaction.css pins that value on this effect"
|
|
7639
|
+
};
|
|
7345
7640
|
return {
|
|
7346
7641
|
id,
|
|
7347
7642
|
renderer: "javascript",
|
|
7348
|
-
|
|
7643
|
+
// A placed family publishes the resolved side, and a running tease, on its host.
|
|
7644
|
+
channels: placed ? [...channels, attributeChannel(placed.attribute), attributeChannel(placed.tease)] : channels,
|
|
7349
7645
|
parameters,
|
|
7350
7646
|
supportedTimelines: ["time"],
|
|
7351
7647
|
supportedActivations: ["load"],
|
|
7352
7648
|
defaultActivation: "load",
|
|
7353
7649
|
perfClass,
|
|
7354
7650
|
reducedMotion: "shorten",
|
|
7355
|
-
prepare:
|
|
7356
|
-
|
|
7357
|
-
|
|
7358
|
-
|
|
7651
|
+
prepare: placed ? withTimingContract(
|
|
7652
|
+
id,
|
|
7653
|
+
contract,
|
|
7654
|
+
(el, params, ctx) => deferredInstance(() => {
|
|
7655
|
+
mirrorTimingToCss(id, ALL_TIMING_TOKENS, params, ctx);
|
|
7656
|
+
return prepareReveal(el, params, placed, ctx);
|
|
7657
|
+
})
|
|
7658
|
+
) : stylesheetTimingPrepare(id, contract)
|
|
7659
|
+
};
|
|
7660
|
+
}
|
|
7661
|
+
var OPPOSITE = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
7662
|
+
function isSide(value) {
|
|
7663
|
+
return value !== null && Object.hasOwn(OPPOSITE, value);
|
|
7664
|
+
}
|
|
7665
|
+
function chooseSide(preferred, room, extent) {
|
|
7666
|
+
if (room[preferred] >= extent) return preferred;
|
|
7667
|
+
const opposite = OPPOSITE[preferred];
|
|
7668
|
+
return room[opposite] > room[preferred] ? opposite : preferred;
|
|
7669
|
+
}
|
|
7670
|
+
function extentOf(part, host, side) {
|
|
7671
|
+
switch (side) {
|
|
7672
|
+
case "top":
|
|
7673
|
+
return -part.offsetTop;
|
|
7674
|
+
case "bottom":
|
|
7675
|
+
return part.offsetTop + part.offsetHeight - host.clientHeight;
|
|
7676
|
+
case "left":
|
|
7677
|
+
return -part.offsetLeft;
|
|
7678
|
+
case "right":
|
|
7679
|
+
return part.offsetLeft + part.offsetWidth - host.clientWidth;
|
|
7680
|
+
}
|
|
7681
|
+
}
|
|
7682
|
+
function prepareReveal(el, params, spec, ctx) {
|
|
7683
|
+
const release2 = claimPart(el, params, spec, ctx);
|
|
7684
|
+
const attrs = createAttributeLedger(el);
|
|
7685
|
+
const restore = () => {
|
|
7686
|
+
attrs.restore();
|
|
7687
|
+
release2();
|
|
7688
|
+
};
|
|
7689
|
+
const place3 = params.text("place");
|
|
7690
|
+
const teaseMs = params.ms("tease", 0);
|
|
7691
|
+
const auto = place3 === "auto";
|
|
7692
|
+
if (!auto && isSide(place3)) attrs.set(spec.attribute, place3);
|
|
7693
|
+
const placeSide = auto ? autoPlacer(el, spec, attrs, ctx.win) : void 0;
|
|
7694
|
+
const shift = shifter(el, spec, ctx);
|
|
7695
|
+
const measure = () => {
|
|
7696
|
+
placeSide?.();
|
|
7697
|
+
shift();
|
|
7698
|
+
};
|
|
7699
|
+
let endTease = () => {
|
|
7700
|
+
};
|
|
7701
|
+
const shown = whileShown(el, ctx, measure, () => endTease());
|
|
7702
|
+
const tease = teaseMs > 0 ? teaseOnEnter(el, ctx, { ms: teaseMs, attribute: spec.tease, attrs, shown }) : void 0;
|
|
7703
|
+
if (tease) endTease = tease.end;
|
|
7704
|
+
return () => {
|
|
7705
|
+
tease?.stop();
|
|
7706
|
+
shown.stop();
|
|
7707
|
+
restore();
|
|
7708
|
+
};
|
|
7709
|
+
}
|
|
7710
|
+
function claimPart(el, params, spec, ctx) {
|
|
7711
|
+
const selector = resolveTarget(params.text("target"), ctx, spec.name);
|
|
7712
|
+
if (!selector) return () => {
|
|
7713
|
+
};
|
|
7714
|
+
const matches = queryScoped(el, ctx, selector, "self");
|
|
7715
|
+
const part = matches[0];
|
|
7716
|
+
if (!part) {
|
|
7717
|
+
ctx.warn(`${spec.name} target "${selector}" matched nothing inside this element`);
|
|
7718
|
+
return () => {
|
|
7719
|
+
};
|
|
7720
|
+
}
|
|
7721
|
+
if (matches.length > 1) {
|
|
7722
|
+
ctx.warn(
|
|
7723
|
+
`${spec.name} target "${selector}" matched ${matches.length} elements; only the first is used`
|
|
7724
|
+
);
|
|
7725
|
+
}
|
|
7726
|
+
const ledger = createAttributeLedger(part);
|
|
7727
|
+
if (!part.hasAttribute(spec.marker)) ledger.set(spec.marker, "");
|
|
7728
|
+
return () => ledger.restore();
|
|
7729
|
+
}
|
|
7730
|
+
function autoPlacer(el, spec, attrs, win) {
|
|
7731
|
+
const preferred = spec.preferred(el);
|
|
7732
|
+
let side = preferred;
|
|
7733
|
+
attrs.set(spec.attribute, side);
|
|
7734
|
+
return () => {
|
|
7735
|
+
const part = el.querySelector(`[${spec.marker}]`);
|
|
7736
|
+
if (!part) return;
|
|
7737
|
+
const rect = el.getBoundingClientRect();
|
|
7738
|
+
const room = {
|
|
7739
|
+
top: rect.top,
|
|
7740
|
+
bottom: win.innerHeight - rect.bottom,
|
|
7741
|
+
left: rect.left,
|
|
7742
|
+
right: win.innerWidth - rect.right
|
|
7743
|
+
};
|
|
7744
|
+
const next = chooseSide(preferred, room, extentOf(part, el, side));
|
|
7745
|
+
if (next === side) return;
|
|
7746
|
+
side = next;
|
|
7747
|
+
attrs.set(spec.attribute, side);
|
|
7748
|
+
};
|
|
7749
|
+
}
|
|
7750
|
+
var VIEWPORT_MARGIN = 8;
|
|
7751
|
+
function shiftWithin(start, size, viewport, margin = VIEWPORT_MARGIN) {
|
|
7752
|
+
const low = margin;
|
|
7753
|
+
const high = viewport - margin;
|
|
7754
|
+
if (start < low || size > high - low) return low - start;
|
|
7755
|
+
if (start + size > high) return high - start - size;
|
|
7756
|
+
return 0;
|
|
7757
|
+
}
|
|
7758
|
+
function shifter(el, spec, ctx) {
|
|
7759
|
+
let written = 0;
|
|
7760
|
+
return () => {
|
|
7761
|
+
const part = el.querySelector(`[${spec.marker}]`);
|
|
7762
|
+
if (!part) return;
|
|
7763
|
+
const stamped = el.getAttribute(spec.attribute);
|
|
7764
|
+
const side = isSide(stamped) ? stamped : spec.preferred(el);
|
|
7765
|
+
const rect = part.getBoundingClientRect();
|
|
7766
|
+
const hostRect = el.getBoundingClientRect();
|
|
7767
|
+
const [startKey, sizeKey, offsetKey, borderKey, layoutKey, viewportKey, windowKey] = side === "top" || side === "bottom" ? ["left", "width", "offsetLeft", "clientLeft", "offsetWidth", "clientWidth", "innerWidth"] : ["top", "height", "offsetTop", "clientTop", "offsetHeight", "clientHeight", "innerHeight"];
|
|
7768
|
+
const hostSize = el[layoutKey];
|
|
7769
|
+
const zoom = hostSize > 0 ? hostRect[sizeKey] / hostSize : 1;
|
|
7770
|
+
if (zoom <= 0) return;
|
|
7771
|
+
const entranceScale = parseFloat(ctx.win.getComputedStyle(part).scale) || 1;
|
|
7772
|
+
const size = rect[sizeKey] / entranceScale;
|
|
7773
|
+
const start = hostRect[startKey] + (el[borderKey] + part[offsetKey]) * zoom - size / 2;
|
|
7774
|
+
const viewport = ctx.doc.documentElement[viewportKey] || ctx.win[windowKey];
|
|
7775
|
+
const next = shiftWithin(start, size, viewport) / zoom;
|
|
7776
|
+
if (next === written) return;
|
|
7777
|
+
written = next;
|
|
7778
|
+
ctx.style.set(spec.shift, `${next}px`);
|
|
7779
|
+
};
|
|
7780
|
+
}
|
|
7781
|
+
function whileShown(el, ctx, measure, onUser) {
|
|
7782
|
+
const { win } = ctx;
|
|
7783
|
+
const frame = frameScheduler(win, measure);
|
|
7784
|
+
let hovered = false;
|
|
7785
|
+
let focused = false;
|
|
7786
|
+
let teasing = false;
|
|
7787
|
+
let watching;
|
|
7788
|
+
const sync = () => {
|
|
7789
|
+
const shown = hovered || focused || teasing;
|
|
7790
|
+
if (shown && !watching) {
|
|
7791
|
+
measure();
|
|
7792
|
+
watching = new AbortController();
|
|
7793
|
+
const options = { capture: true, passive: true, signal: watching.signal };
|
|
7794
|
+
win.addEventListener("scroll", frame.request, options);
|
|
7795
|
+
win.addEventListener("resize", frame.request, options);
|
|
7796
|
+
} else if (!shown && watching) {
|
|
7797
|
+
watching.abort();
|
|
7798
|
+
watching = void 0;
|
|
7799
|
+
frame.cancel();
|
|
7800
|
+
}
|
|
7801
|
+
};
|
|
7802
|
+
const on = (type, update) => el.addEventListener(type, (event) => {
|
|
7803
|
+
update(event);
|
|
7804
|
+
sync();
|
|
7805
|
+
}, { signal: ctx.signal });
|
|
7806
|
+
on("pointerenter", () => {
|
|
7807
|
+
hovered = true;
|
|
7808
|
+
onUser();
|
|
7809
|
+
});
|
|
7810
|
+
on("pointerleave", () => hovered = false);
|
|
7811
|
+
on("focusin", () => {
|
|
7812
|
+
focused = true;
|
|
7813
|
+
onUser();
|
|
7814
|
+
});
|
|
7815
|
+
on("focusout", (event) => {
|
|
7816
|
+
focused = el.contains(event.relatedTarget);
|
|
7817
|
+
});
|
|
7818
|
+
return {
|
|
7819
|
+
setTeasing(on2) {
|
|
7820
|
+
teasing = on2;
|
|
7821
|
+
sync();
|
|
7822
|
+
},
|
|
7823
|
+
stop() {
|
|
7824
|
+
watching?.abort();
|
|
7825
|
+
frame.cancel();
|
|
7826
|
+
}
|
|
7827
|
+
};
|
|
7828
|
+
}
|
|
7829
|
+
function teaseOnEnter(el, ctx, {
|
|
7830
|
+
ms,
|
|
7831
|
+
attribute,
|
|
7832
|
+
attrs,
|
|
7833
|
+
shown
|
|
7834
|
+
}) {
|
|
7835
|
+
const { win } = ctx;
|
|
7836
|
+
let timer;
|
|
7837
|
+
let observer;
|
|
7838
|
+
const end = () => {
|
|
7839
|
+
if (timer === void 0) return;
|
|
7840
|
+
win.clearTimeout(timer);
|
|
7841
|
+
timer = void 0;
|
|
7842
|
+
attrs.remove(attribute);
|
|
7843
|
+
shown.setTeasing(false);
|
|
7844
|
+
};
|
|
7845
|
+
const start = () => {
|
|
7846
|
+
shown.setTeasing(true);
|
|
7847
|
+
attrs.set(attribute, "");
|
|
7848
|
+
timer = win.setTimeout(end, ms);
|
|
7849
|
+
};
|
|
7850
|
+
const Observer = win.IntersectionObserver;
|
|
7851
|
+
if (Observer) {
|
|
7852
|
+
observer = new Observer(
|
|
7853
|
+
(entries) => {
|
|
7854
|
+
if (!entries.some((entry) => entry.isIntersecting && entry.intersectionRatio >= 0.5)) return;
|
|
7855
|
+
observer.disconnect();
|
|
7856
|
+
start();
|
|
7857
|
+
},
|
|
7858
|
+
{ threshold: 0.5 }
|
|
7859
|
+
);
|
|
7860
|
+
observer.observe(el);
|
|
7861
|
+
}
|
|
7862
|
+
return {
|
|
7863
|
+
end,
|
|
7864
|
+
stop() {
|
|
7865
|
+
observer?.disconnect();
|
|
7866
|
+
if (timer !== void 0) win.clearTimeout(timer);
|
|
7867
|
+
timer = void 0;
|
|
7868
|
+
}
|
|
7359
7869
|
};
|
|
7360
7870
|
}
|
|
7361
7871
|
var labelSwapParams = {
|
|
@@ -7388,10 +7898,34 @@ var kuinetic = (() => {
|
|
|
7388
7898
|
duration: { type: "time", default: "160ms", cssProperty: "--kui-duration" },
|
|
7389
7899
|
delay: { type: "time", default: "1000ms", cssProperty: "--kui-delay" },
|
|
7390
7900
|
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" },
|
|
7391
|
-
distance: { type: "length", default: "4px", cssProperty: "--kui-hover-intent-distance" }
|
|
7901
|
+
distance: { type: "length", default: "4px", cssProperty: "--kui-hover-intent-distance" },
|
|
7902
|
+
color: { type: "color", default: "#f4f4f0", cssProperty: "--kui-hover-intent-color" },
|
|
7903
|
+
"bg-color": { type: "color", default: "#111111", cssProperty: "--kui-hover-intent-bg-color" },
|
|
7904
|
+
radius: { type: "length", default: "12px", cssProperty: "--kui-hover-intent-radius" },
|
|
7905
|
+
// `place` and `tease` are read by `prepareReveal`, not by any stylesheet — see
|
|
7906
|
+
// `ParamSpecBase.cssProperty`.
|
|
7907
|
+
place: {
|
|
7908
|
+
type: "keyword",
|
|
7909
|
+
default: "top",
|
|
7910
|
+
keywords: ["top", "bottom", "auto"],
|
|
7911
|
+
cssProperty: "--kui-hover-intent-place"
|
|
7912
|
+
},
|
|
7913
|
+
tease: { type: "time", default: "0ms", cssProperty: "--kui-hover-intent-tease" },
|
|
7914
|
+
// Names the hint, in place of a `data-kui-hint` in the markup — see `claimPart`. Declared, so
|
|
7915
|
+
// `compile.ts` leaves it here rather than moving the effect onto the hint.
|
|
7916
|
+
target: { type: "text", default: "", cssProperty: "--kui-target" }
|
|
7392
7917
|
};
|
|
7393
7918
|
var HOVER_INTENT_PRIMITIVES = [
|
|
7394
|
-
revealPrimitive("hover-intent", ["hint"], hoverIntentParams
|
|
7919
|
+
revealPrimitive("hover-intent", ["hint"], hoverIntentParams, {
|
|
7920
|
+
placed: {
|
|
7921
|
+
name: "hover-intent",
|
|
7922
|
+
marker: "data-kui-hint",
|
|
7923
|
+
attribute: "data-kui-hint-place",
|
|
7924
|
+
tease: "data-kui-hint-tease",
|
|
7925
|
+
shift: "--kui-hover-intent-shift",
|
|
7926
|
+
preferred: () => "top"
|
|
7927
|
+
}
|
|
7928
|
+
})
|
|
7395
7929
|
];
|
|
7396
7930
|
var HOVER_INTENT_PRESETS = [
|
|
7397
7931
|
{ name: "hover-intent", phase: "state", primitive: "hover-intent" }
|
|
@@ -7409,16 +7943,65 @@ var kuinetic = (() => {
|
|
|
7409
7943
|
finite: true,
|
|
7410
7944
|
minimum: 0,
|
|
7411
7945
|
maximum: 1
|
|
7412
|
-
}
|
|
7946
|
+
},
|
|
7947
|
+
color: { type: "color", default: "currentcolor", cssProperty: "--kui-anchored-preview-color" },
|
|
7948
|
+
"bg-color": {
|
|
7949
|
+
type: "color",
|
|
7950
|
+
default: "transparent",
|
|
7951
|
+
cssProperty: "--kui-anchored-preview-bg-color"
|
|
7952
|
+
},
|
|
7953
|
+
radius: { type: "length", default: "0px", cssProperty: "--kui-anchored-preview-radius" },
|
|
7954
|
+
// `place` and `tease` are read by `prepareReveal`, not by any stylesheet — see
|
|
7955
|
+
// `ParamSpecBase.cssProperty`. `tease` is `hover-intent`'s, unchanged: `0ms` is off.
|
|
7956
|
+
place: {
|
|
7957
|
+
type: "keyword",
|
|
7958
|
+
default: "top",
|
|
7959
|
+
keywords: ["top", "bottom", "left", "right", "auto"],
|
|
7960
|
+
cssProperty: "--kui-anchored-preview-place"
|
|
7961
|
+
},
|
|
7962
|
+
tease: { type: "time", default: "0ms", cssProperty: "--kui-anchored-preview-tease" },
|
|
7963
|
+
// Names the preview, in place of a `data-kui-preview` in the markup — see `claimPart`.
|
|
7964
|
+
target: { type: "text", default: "", cssProperty: "--kui-target" }
|
|
7413
7965
|
};
|
|
7966
|
+
function previewSideOf(el) {
|
|
7967
|
+
for (const token of (el.getAttribute(ATTR.normalized) ?? "").split(/\s+/)) {
|
|
7968
|
+
const side = /^anchored-preview(?:-(bottom|left|right))?$/.exec(token);
|
|
7969
|
+
if (side) return side[1] ?? "top";
|
|
7970
|
+
}
|
|
7971
|
+
return "top";
|
|
7972
|
+
}
|
|
7414
7973
|
var ANCHORED_PREVIEW_PRIMITIVES = [
|
|
7415
|
-
revealPrimitive("anchored-preview", ["preview"], anchoredPreviewParams
|
|
7974
|
+
revealPrimitive("anchored-preview", ["preview"], anchoredPreviewParams, {
|
|
7975
|
+
placed: {
|
|
7976
|
+
name: "anchored-preview",
|
|
7977
|
+
marker: "data-kui-preview",
|
|
7978
|
+
attribute: "data-kui-preview-place",
|
|
7979
|
+
tease: "data-kui-preview-tease",
|
|
7980
|
+
shift: "--kui-anchored-preview-shift",
|
|
7981
|
+
preferred: previewSideOf
|
|
7982
|
+
}
|
|
7983
|
+
})
|
|
7416
7984
|
];
|
|
7417
7985
|
var ANCHORED_PREVIEW_PRESETS = [
|
|
7418
7986
|
{ name: "anchored-preview", phase: "state", primitive: "anchored-preview" },
|
|
7419
|
-
{
|
|
7420
|
-
|
|
7421
|
-
|
|
7987
|
+
{
|
|
7988
|
+
name: "anchored-preview-bottom",
|
|
7989
|
+
phase: "state",
|
|
7990
|
+
primitive: "anchored-preview",
|
|
7991
|
+
params: { place: "bottom" }
|
|
7992
|
+
},
|
|
7993
|
+
{
|
|
7994
|
+
name: "anchored-preview-left",
|
|
7995
|
+
phase: "state",
|
|
7996
|
+
primitive: "anchored-preview",
|
|
7997
|
+
params: { place: "left" }
|
|
7998
|
+
},
|
|
7999
|
+
{
|
|
8000
|
+
name: "anchored-preview-right",
|
|
8001
|
+
phase: "state",
|
|
8002
|
+
primitive: "anchored-preview",
|
|
8003
|
+
params: { place: "right" }
|
|
8004
|
+
}
|
|
7422
8005
|
];
|
|
7423
8006
|
var searchExpandParams = {
|
|
7424
8007
|
duration: { type: "time", default: "260ms", cssProperty: "--kui-duration" },
|
|
@@ -7436,7 +8019,7 @@ var kuinetic = (() => {
|
|
|
7436
8019
|
"search-expand",
|
|
7437
8020
|
["expand", "discrete", "search-field"],
|
|
7438
8021
|
searchExpandParams,
|
|
7439
|
-
"layout"
|
|
8022
|
+
{ perfClass: "layout" }
|
|
7440
8023
|
)
|
|
7441
8024
|
];
|
|
7442
8025
|
var SEARCH_EXPAND_PRESETS = [
|
|
@@ -7852,7 +8435,10 @@ var kuinetic = (() => {
|
|
|
7852
8435
|
label: { type: "text", default: "", cssProperty: "--kui-label" }
|
|
7853
8436
|
};
|
|
7854
8437
|
var POINTER_PRIMITIVES = [
|
|
7855
|
-
|
|
8438
|
+
// `skew` is the `transform` shorthand's channel, which is what `prepareTilt3d` writes (a
|
|
8439
|
+
// perspective and two axis rotations) — never the `rotate` property — beside an inline
|
|
8440
|
+
// `transition` for its settle.
|
|
8441
|
+
pointerPrimitive("tilt-3d", ["skew", "transition"], tiltParams, deferPrepare(prepareTilt3d)),
|
|
7856
8442
|
pointerPrimitive("tilt-parallax", ["translate"], parallaxParams, deferPrepare(prepareTiltParallax)),
|
|
7857
8443
|
pointerPrimitive("cursor-follow", ["translate"], cursorDotParams, deferPrepare(prepareCursorFollow)),
|
|
7858
8444
|
pointerPrimitive("cursor-lag", ["translate"], cursorDotParams, deferPrepare(prepareCursorLag)),
|
|
@@ -7861,7 +8447,13 @@ var kuinetic = (() => {
|
|
|
7861
8447
|
// `pseudo-before`: the glow overlay is a `::before` rule in `interaction.css`, so this primitive
|
|
7862
8448
|
// owns that box the same way the border family does. See `border-draw`'s comment in
|
|
7863
8449
|
// `HOVER_PRIMITIVES` for what the token means and why it was added here in the same change.
|
|
7864
|
-
|
|
8450
|
+
// `position`: the `relative` it gives an unpositioned host so that overlay has an anchor.
|
|
8451
|
+
pointerPrimitive(
|
|
8452
|
+
"cursor-spotlight",
|
|
8453
|
+
["spotlight", "pseudo-before", "position"],
|
|
8454
|
+
{},
|
|
8455
|
+
deferPrepare(prepareSpotlight)
|
|
8456
|
+
)
|
|
7865
8457
|
];
|
|
7866
8458
|
function prepareCursorFollow(el, params, ctx) {
|
|
7867
8459
|
return prepareCursorDot(el, params, ctx, "kui-cursor-dot-follow");
|
|
@@ -8341,52 +8933,6 @@ var kuinetic = (() => {
|
|
|
8341
8933
|
};
|
|
8342
8934
|
}
|
|
8343
8935
|
|
|
8344
|
-
// src/core/element-size.ts
|
|
8345
|
-
function watchElementSize(win, onChange) {
|
|
8346
|
-
const ResizeObserverCtor = win.ResizeObserver;
|
|
8347
|
-
const observer = ResizeObserverCtor ? new ResizeObserverCtor(onChange) : void 0;
|
|
8348
|
-
const elements = /* @__PURE__ */ new Set();
|
|
8349
|
-
win.addEventListener("resize", onChange, { passive: true });
|
|
8350
|
-
return {
|
|
8351
|
-
observe(element) {
|
|
8352
|
-
if (elements.has(element)) return;
|
|
8353
|
-
elements.add(element);
|
|
8354
|
-
observer?.observe(element);
|
|
8355
|
-
},
|
|
8356
|
-
unobserve(element) {
|
|
8357
|
-
elements.delete(element);
|
|
8358
|
-
observer?.unobserve(element);
|
|
8359
|
-
},
|
|
8360
|
-
disconnect() {
|
|
8361
|
-
win.removeEventListener("resize", onChange);
|
|
8362
|
-
observer?.disconnect();
|
|
8363
|
-
elements.clear();
|
|
8364
|
-
}
|
|
8365
|
-
};
|
|
8366
|
-
}
|
|
8367
|
-
function frameScheduler(win, callback) {
|
|
8368
|
-
let frame;
|
|
8369
|
-
let timeout;
|
|
8370
|
-
const run = () => {
|
|
8371
|
-
frame = void 0;
|
|
8372
|
-
timeout = void 0;
|
|
8373
|
-
callback();
|
|
8374
|
-
};
|
|
8375
|
-
return {
|
|
8376
|
-
request() {
|
|
8377
|
-
if (frame !== void 0 || timeout !== void 0) return;
|
|
8378
|
-
if (win.requestAnimationFrame) frame = win.requestAnimationFrame(run);
|
|
8379
|
-
else timeout = win.setTimeout(run, 16);
|
|
8380
|
-
},
|
|
8381
|
-
cancel() {
|
|
8382
|
-
if (frame !== void 0) win.cancelAnimationFrame(frame);
|
|
8383
|
-
if (timeout !== void 0) win.clearTimeout(timeout);
|
|
8384
|
-
frame = void 0;
|
|
8385
|
-
timeout = void 0;
|
|
8386
|
-
}
|
|
8387
|
-
};
|
|
8388
|
-
}
|
|
8389
|
-
|
|
8390
8936
|
// src/effects/catalog/media-shared.ts
|
|
8391
8937
|
var AXIS_DEGREES = { vertical: 0, horizontal: 90 };
|
|
8392
8938
|
var UNIT_DEGREES = { deg: 1, d: 1, grad: 0.9, rad: 180 / Math.PI, turn: 360 };
|
|
@@ -8745,14 +9291,14 @@ var kuinetic = (() => {
|
|
|
8745
9291
|
settle2 = resolve;
|
|
8746
9292
|
});
|
|
8747
9293
|
let landed = false;
|
|
8748
|
-
const
|
|
9294
|
+
const land2 = () => {
|
|
8749
9295
|
if (landed) return;
|
|
8750
9296
|
landed = true;
|
|
8751
9297
|
stage.classList.remove("kui-slat-animating");
|
|
8752
9298
|
built.restore();
|
|
8753
9299
|
settle2();
|
|
8754
9300
|
};
|
|
8755
|
-
const timer = ctx.win.setTimeout(
|
|
9301
|
+
const timer = ctx.win.setTimeout(land2, slatAssembleFinishMs(params, count));
|
|
8756
9302
|
return {
|
|
8757
9303
|
cleanup: () => {
|
|
8758
9304
|
ctx.win.clearTimeout(timer);
|
|
@@ -8763,7 +9309,7 @@ var kuinetic = (() => {
|
|
|
8763
9309
|
finished,
|
|
8764
9310
|
finish: () => {
|
|
8765
9311
|
ctx.win.clearTimeout(timer);
|
|
8766
|
-
|
|
9312
|
+
land2();
|
|
8767
9313
|
}
|
|
8768
9314
|
};
|
|
8769
9315
|
}
|
|
@@ -8898,7 +9444,9 @@ var kuinetic = (() => {
|
|
|
8898
9444
|
{
|
|
8899
9445
|
id: "slat-assemble",
|
|
8900
9446
|
renderer: "javascript",
|
|
8901
|
-
|
|
9447
|
+
// `position` for the `relative` it gives an unpositioned host, and the subtree because
|
|
9448
|
+
// `installSlatStage` appends its slat stage there.
|
|
9449
|
+
channels: [CHANNEL.opacity, CHANNEL.translate, CHANNEL.rotate, "position", SUBTREE_CHANNEL],
|
|
8902
9450
|
parameters: slatParams,
|
|
8903
9451
|
supportedTimelines: ["time"],
|
|
8904
9452
|
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
@@ -8922,12 +9470,15 @@ var kuinetic = (() => {
|
|
|
8922
9470
|
* and it is what makes `background-media, video-scrub` on one element a reported conflict
|
|
8923
9471
|
* rather than two effects silently fighting over the same picture.
|
|
8924
9472
|
*
|
|
8925
|
-
* `
|
|
8926
|
-
*
|
|
8927
|
-
*
|
|
8928
|
-
*
|
|
9473
|
+
* `position` and `isolation` are the two properties preparation writes on the *host*, a
|
|
9474
|
+
* stacking-context claim on someone else's element. Left undeclared, `background-media,
|
|
9475
|
+
* pin-section` composed silently while both decided what `position` the host has — the
|
|
9476
|
+
* conflict detector cannot report a claim it was never told about.
|
|
9477
|
+
*
|
|
9478
|
+
* And the subtree, because the layer is appended into the host (`installBackgroundMedia`): it
|
|
9479
|
+
* would become a deck's last slide, or `scroll-story`'s `:last-child` in place of its sections.
|
|
8929
9480
|
*/
|
|
8930
|
-
channels: ["media", "
|
|
9481
|
+
channels: ["media", "position", "isolation", SUBTREE_CHANNEL],
|
|
8931
9482
|
renderer: "javascript",
|
|
8932
9483
|
parameters: backgroundMediaParams,
|
|
8933
9484
|
// Not a claim to support four timelines — an abstention. A backdrop is not driven by progress
|
|
@@ -9456,7 +10007,9 @@ var kuinetic = (() => {
|
|
|
9456
10007
|
return {
|
|
9457
10008
|
id,
|
|
9458
10009
|
renderer: "javascript",
|
|
9459
|
-
|
|
10010
|
+
// The subtree too: both counters rebuild the host's children (the number, or the odometer's
|
|
10011
|
+
// digit columns), so a deck or a widget beside one would lose what it put there.
|
|
10012
|
+
channels: ["content", SUBTREE_CHANNEL],
|
|
9460
10013
|
parameters,
|
|
9461
10014
|
supportedTimelines: ["time"],
|
|
9462
10015
|
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
@@ -9844,7 +10397,9 @@ var kuinetic = (() => {
|
|
|
9844
10397
|
return {
|
|
9845
10398
|
id,
|
|
9846
10399
|
renderer: "javascript",
|
|
9847
|
-
|
|
10400
|
+
// Every one of these replaces the host's children with its own spans or layers, so it owns
|
|
10401
|
+
// them: beside a deck it would split the slides, beside `slow-mo` it would swallow the toggle.
|
|
10402
|
+
channels: [...channels, SUBTREE_CHANNEL],
|
|
9848
10403
|
parameters: options.parameters,
|
|
9849
10404
|
supportedTimelines: ["time"],
|
|
9850
10405
|
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
@@ -9974,21 +10529,21 @@ var kuinetic = (() => {
|
|
|
9974
10529
|
const finished = new Promise((resolve) => {
|
|
9975
10530
|
settle2 = resolve;
|
|
9976
10531
|
});
|
|
9977
|
-
const
|
|
10532
|
+
const land2 = () => {
|
|
9978
10533
|
ctx.win.clearTimeout(timer);
|
|
9979
10534
|
timer = void 0;
|
|
9980
10535
|
layers.decorative.removeAttribute("aria-hidden");
|
|
9981
10536
|
layers.srOnly.remove();
|
|
9982
10537
|
settle2();
|
|
9983
10538
|
};
|
|
9984
|
-
let timer = ctx.win.setTimeout(
|
|
10539
|
+
let timer = ctx.win.setTimeout(land2, splitRevealFinishMs(params, count));
|
|
9985
10540
|
return {
|
|
9986
10541
|
finished,
|
|
9987
|
-
land,
|
|
10542
|
+
land: land2,
|
|
9988
10543
|
rearm(nextCount) {
|
|
9989
10544
|
if (timer === void 0) return;
|
|
9990
10545
|
ctx.win.clearTimeout(timer);
|
|
9991
|
-
timer = ctx.win.setTimeout(
|
|
10546
|
+
timer = ctx.win.setTimeout(land2, splitRevealFinishMs(params, nextCount));
|
|
9992
10547
|
},
|
|
9993
10548
|
cancel() {
|
|
9994
10549
|
if (timer !== void 0) ctx.win.clearTimeout(timer);
|
|
@@ -10227,7 +10782,8 @@ var kuinetic = (() => {
|
|
|
10227
10782
|
prepare: deferPrepare(prepareTypewriter),
|
|
10228
10783
|
perfClass: "continuous"
|
|
10229
10784
|
}),
|
|
10230
|
-
|
|
10785
|
+
// `min-width`/`min-height`: the rest box it pins while the glyphs churn (`prepareScramble`).
|
|
10786
|
+
jsTextPrimitive("scramble-text", ["content", "min-width", "min-height"], {
|
|
10231
10787
|
parameters: scrambleParams,
|
|
10232
10788
|
prepare: deferPrepare(prepareScramble),
|
|
10233
10789
|
perfClass: "continuous"
|
|
@@ -10636,6 +11192,36 @@ var kuinetic = (() => {
|
|
|
10636
11192
|
return registry;
|
|
10637
11193
|
}
|
|
10638
11194
|
|
|
11195
|
+
// src/core/deck-viewer.ts
|
|
11196
|
+
var provided = null;
|
|
11197
|
+
function provideDeckViewer(viewer) {
|
|
11198
|
+
provided = viewer;
|
|
11199
|
+
}
|
|
11200
|
+
function attachDeckViewer(request) {
|
|
11201
|
+
return provided ? provided(request) : null;
|
|
11202
|
+
}
|
|
11203
|
+
var VIEWER_EVENT = "kui:viewer";
|
|
11204
|
+
function announceViewer(doc, opener, open2) {
|
|
11205
|
+
const Ctor = doc.defaultView?.CustomEvent ?? CustomEvent;
|
|
11206
|
+
const source = opener.isConnected ? opener : doc;
|
|
11207
|
+
source.dispatchEvent(new Ctor(VIEWER_EVENT, { bubbles: true, detail: { open: open2 } }));
|
|
11208
|
+
}
|
|
11209
|
+
var DECK_LIGHTBOX_PARAM = {
|
|
11210
|
+
type: "keyword",
|
|
11211
|
+
default: "false",
|
|
11212
|
+
cssProperty: "--kui-deck-lightbox",
|
|
11213
|
+
keywords: ["true", "false"]
|
|
11214
|
+
};
|
|
11215
|
+
function deckLightbox(enabled, request, warn) {
|
|
11216
|
+
if (!enabled) return () => {
|
|
11217
|
+
};
|
|
11218
|
+
const release2 = attachDeckViewer(request);
|
|
11219
|
+
if (release2) return release2;
|
|
11220
|
+
warn("lightbox:true needs the showcase module (the lightbox gallery), which is not loaded; cards will not open");
|
|
11221
|
+
return () => {
|
|
11222
|
+
};
|
|
11223
|
+
}
|
|
11224
|
+
|
|
10639
11225
|
// src/effects/step-marking.ts
|
|
10640
11226
|
var STEP_STATE_ATTR = "data-kui-step-state";
|
|
10641
11227
|
var STEP_OFFSET_ATTR = "data-kui-step-offset";
|
|
@@ -10724,28 +11310,49 @@ var kuinetic = (() => {
|
|
|
10724
11310
|
}
|
|
10725
11311
|
return Math.max(1, ...groups.values());
|
|
10726
11312
|
}
|
|
11313
|
+
function bindControl(request, param, run) {
|
|
11314
|
+
const { el, ctx, params, label } = request;
|
|
11315
|
+
const selector = resolveTarget(params.text(param), ctx, `${label} ${param}`);
|
|
11316
|
+
if (!selector) return null;
|
|
11317
|
+
if (queryControls(el, ctx, selector).length === 0) ctx.warn(`${label} ${param} "${selector}" matched nothing`);
|
|
11318
|
+
return { selector, run };
|
|
11319
|
+
}
|
|
10727
11320
|
function delegateControls(request) {
|
|
10728
|
-
const { el, ctx,
|
|
11321
|
+
const { el, ctx, groups } = request;
|
|
10729
11322
|
if (groups.length === 0) return () => {
|
|
10730
11323
|
};
|
|
10731
|
-
const
|
|
10732
|
-
const onClick = (event) => {
|
|
10733
|
-
const from = event.target;
|
|
10734
|
-
if (typeof from?.closest !== "function") return;
|
|
11324
|
+
const press = (from, inside) => {
|
|
10735
11325
|
for (const { selector, run } of groups) {
|
|
10736
|
-
const matches =
|
|
11326
|
+
const matches = queryControls(el, ctx, selector);
|
|
10737
11327
|
const matched = new Set(matches);
|
|
11328
|
+
const stop = inside ? el : null;
|
|
10738
11329
|
let node = from;
|
|
10739
|
-
while (node && !matched.has(node)) node = node.parentElement;
|
|
10740
|
-
if (!node) continue;
|
|
11330
|
+
while (node && node !== stop && !matched.has(node)) node = node.parentElement;
|
|
11331
|
+
if (!node || node === stop) continue;
|
|
10741
11332
|
run(node, matches.indexOf(node));
|
|
10742
11333
|
}
|
|
10743
11334
|
};
|
|
10744
|
-
|
|
10745
|
-
|
|
11335
|
+
const pressed = (event) => {
|
|
11336
|
+
const from = event.target;
|
|
11337
|
+
return typeof from?.closest === "function" ? from : null;
|
|
11338
|
+
};
|
|
11339
|
+
const onInside = (event) => {
|
|
11340
|
+
const from = pressed(event);
|
|
11341
|
+
if (from) press(from, true);
|
|
11342
|
+
};
|
|
11343
|
+
const onOutside = (event) => {
|
|
11344
|
+
const from = pressed(event);
|
|
11345
|
+
if (from && !el.contains(from)) press(from, false);
|
|
11346
|
+
};
|
|
11347
|
+
el.addEventListener("click", onInside);
|
|
11348
|
+
ctx.doc.addEventListener("click", onOutside);
|
|
11349
|
+
return () => {
|
|
11350
|
+
el.removeEventListener("click", onInside);
|
|
11351
|
+
ctx.doc.removeEventListener("click", onOutside);
|
|
11352
|
+
};
|
|
10746
11353
|
}
|
|
10747
11354
|
function createStepIndex(options) {
|
|
10748
|
-
const { el, params, ctx,
|
|
11355
|
+
const { el, params, ctx, resolveSteps, onRender } = options;
|
|
10749
11356
|
const name = options.name ?? "step-progress";
|
|
10750
11357
|
const marker = createStepMarker(resolveSteps, (message) => ctx.warn(`${name} ${message}`));
|
|
10751
11358
|
const self = createAttributeLedger(el);
|
|
@@ -10764,26 +11371,23 @@ var kuinetic = (() => {
|
|
|
10764
11371
|
};
|
|
10765
11372
|
const next = () => goTo(nextStep(step, total()));
|
|
10766
11373
|
const prev = () => goTo(prevStep(step, total()));
|
|
10767
|
-
const
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
if (!selector) return false;
|
|
10771
|
-
if (queryScoped(el, ctx, selector, scope).length === 0) {
|
|
10772
|
-
ctx.warn(`${name} ${param} "${selector}" matched nothing`);
|
|
10773
|
-
}
|
|
10774
|
-
groups.push({ selector, run });
|
|
10775
|
-
return true;
|
|
11374
|
+
const byHand = (move) => {
|
|
11375
|
+
options.onInput?.();
|
|
11376
|
+
move();
|
|
10776
11377
|
};
|
|
10777
|
-
const
|
|
10778
|
-
|
|
10779
|
-
bindControl("
|
|
10780
|
-
bindControl("
|
|
10781
|
-
|
|
10782
|
-
|
|
10783
|
-
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
const
|
|
11378
|
+
const request = { el, ctx, params, label: name };
|
|
11379
|
+
const groups = [
|
|
11380
|
+
bindControl(request, "next", () => byHand(next)),
|
|
11381
|
+
bindControl(request, "prev", () => byHand(prev)),
|
|
11382
|
+
bindControl(request, "jump", (_node, position) => {
|
|
11383
|
+
if (position >= 0) byHand(() => goTo(position));
|
|
11384
|
+
}),
|
|
11385
|
+
...options.controls ?? []
|
|
11386
|
+
].filter((group) => group !== null);
|
|
11387
|
+
const fallback = options.clickFallback !== false && groups.length === 0;
|
|
11388
|
+
const onContainerClick = () => byHand(next);
|
|
11389
|
+
if (fallback) el.addEventListener("click", onContainerClick);
|
|
11390
|
+
const releaseControls = delegateControls({ el, ctx, groups });
|
|
10787
11391
|
render();
|
|
10788
11392
|
return {
|
|
10789
11393
|
goTo,
|
|
@@ -10792,7 +11396,7 @@ var kuinetic = (() => {
|
|
|
10792
11396
|
current: () => step,
|
|
10793
11397
|
total,
|
|
10794
11398
|
release: () => {
|
|
10795
|
-
if (fallback) el.removeEventListener("click",
|
|
11399
|
+
if (fallback) el.removeEventListener("click", onContainerClick);
|
|
10796
11400
|
releaseControls();
|
|
10797
11401
|
self.restore();
|
|
10798
11402
|
selfStyle.restore();
|
|
@@ -10801,18 +11405,294 @@ var kuinetic = (() => {
|
|
|
10801
11405
|
};
|
|
10802
11406
|
}
|
|
10803
11407
|
|
|
10804
|
-
// src/effects/
|
|
10805
|
-
var
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10811
|
-
|
|
10812
|
-
|
|
10813
|
-
|
|
10814
|
-
|
|
10815
|
-
|
|
11408
|
+
// src/effects/auto-motion.ts
|
|
11409
|
+
var MIN_PERIOD_MS = 2e3;
|
|
11410
|
+
var MAX_FRAME_MS = 100;
|
|
11411
|
+
function clampPeriod(ms) {
|
|
11412
|
+
if (!Number.isFinite(ms) || ms === 0) return 0;
|
|
11413
|
+
return Math.sign(ms) * Math.max(MIN_PERIOD_MS, Math.abs(ms));
|
|
11414
|
+
}
|
|
11415
|
+
function cyclesFor(dtMs, periodMs) {
|
|
11416
|
+
return Math.min(Math.max(dtMs, 0), MAX_FRAME_MS) / periodMs;
|
|
11417
|
+
}
|
|
11418
|
+
function isKeyboardFocus(target) {
|
|
11419
|
+
const node = target;
|
|
11420
|
+
if (typeof node?.matches !== "function") return true;
|
|
11421
|
+
try {
|
|
11422
|
+
return node.matches(":focus-visible");
|
|
11423
|
+
} catch {
|
|
11424
|
+
return true;
|
|
11425
|
+
}
|
|
11426
|
+
}
|
|
11427
|
+
function createAutoMotion(request) {
|
|
11428
|
+
const { el, ctx, spinMs, autoplayMs, settleMs, advance, step, setSpinning, onPausedChange } = request;
|
|
11429
|
+
const pauseOnHover = request.pauseOnHover ?? true;
|
|
11430
|
+
const enabled = spinMs !== 0 || autoplayMs !== 0;
|
|
11431
|
+
if (!enabled) {
|
|
11432
|
+
return {
|
|
11433
|
+
enabled,
|
|
11434
|
+
interrupt() {
|
|
11435
|
+
},
|
|
11436
|
+
hold() {
|
|
11437
|
+
},
|
|
11438
|
+
toggle() {
|
|
11439
|
+
},
|
|
11440
|
+
release() {
|
|
11441
|
+
}
|
|
11442
|
+
};
|
|
11443
|
+
}
|
|
11444
|
+
const win = ctx.win;
|
|
11445
|
+
let userPaused = ctx.reducedMotion;
|
|
11446
|
+
let hovered = false;
|
|
11447
|
+
let focused = false;
|
|
11448
|
+
let onscreen = true;
|
|
11449
|
+
let gesture = false;
|
|
11450
|
+
let held = false;
|
|
11451
|
+
let viewing = false;
|
|
11452
|
+
let viewerOpen = false;
|
|
11453
|
+
let focusBeforeViewer = false;
|
|
11454
|
+
let frame = 0;
|
|
11455
|
+
let lastFrameAt = null;
|
|
11456
|
+
let timer = null;
|
|
11457
|
+
let holdTimer = null;
|
|
11458
|
+
let spinning = false;
|
|
11459
|
+
const active = () => !userPaused && !hovered && !focused && onscreen && !gesture && !held && !viewing && !ctx.doc.hidden;
|
|
11460
|
+
const requestFrame = (callback) => typeof win.requestAnimationFrame === "function" ? win.requestAnimationFrame(callback) : win.setTimeout(() => callback(Date.now()), 16);
|
|
11461
|
+
const cancelFrame = (handle) => {
|
|
11462
|
+
if (typeof win.cancelAnimationFrame === "function") win.cancelAnimationFrame(handle);
|
|
11463
|
+
else win.clearTimeout(handle);
|
|
11464
|
+
};
|
|
11465
|
+
const publishSpinning = (next) => {
|
|
11466
|
+
if (next === spinning) return;
|
|
11467
|
+
spinning = next;
|
|
11468
|
+
setSpinning(next);
|
|
11469
|
+
};
|
|
11470
|
+
const tick = (now) => {
|
|
11471
|
+
frame = 0;
|
|
11472
|
+
if (!active()) return;
|
|
11473
|
+
if (lastFrameAt !== null) advance(cyclesFor(now - lastFrameAt, spinMs));
|
|
11474
|
+
lastFrameAt = now;
|
|
11475
|
+
frame = requestFrame(tick);
|
|
11476
|
+
};
|
|
11477
|
+
const startSpin = () => {
|
|
11478
|
+
if (frame !== 0) return;
|
|
11479
|
+
publishSpinning(true);
|
|
11480
|
+
lastFrameAt = null;
|
|
11481
|
+
frame = requestFrame(tick);
|
|
11482
|
+
};
|
|
11483
|
+
const stopSpin = () => {
|
|
11484
|
+
if (frame !== 0) cancelFrame(frame);
|
|
11485
|
+
frame = 0;
|
|
11486
|
+
publishSpinning(false);
|
|
11487
|
+
};
|
|
11488
|
+
const armAutoplay = () => {
|
|
11489
|
+
if (timer !== null) return;
|
|
11490
|
+
timer = win.setTimeout(() => {
|
|
11491
|
+
timer = null;
|
|
11492
|
+
step(autoplayMs > 0 ? 1 : -1);
|
|
11493
|
+
update();
|
|
11494
|
+
}, Math.abs(autoplayMs));
|
|
11495
|
+
};
|
|
11496
|
+
const clearAutoplay = () => {
|
|
11497
|
+
if (timer === null) return;
|
|
11498
|
+
win.clearTimeout(timer);
|
|
11499
|
+
timer = null;
|
|
11500
|
+
};
|
|
11501
|
+
function update() {
|
|
11502
|
+
const running = active();
|
|
11503
|
+
if (spinMs !== 0) (running ? startSpin : stopSpin)();
|
|
11504
|
+
if (autoplayMs !== 0) (running ? armAutoplay : clearAutoplay)();
|
|
11505
|
+
}
|
|
11506
|
+
const onEnter = () => {
|
|
11507
|
+
hovered = true;
|
|
11508
|
+
update();
|
|
11509
|
+
};
|
|
11510
|
+
const onLeave = () => {
|
|
11511
|
+
hovered = false;
|
|
11512
|
+
update();
|
|
11513
|
+
};
|
|
11514
|
+
const onFocusIn = (event) => {
|
|
11515
|
+
focused = (!viewerOpen || focusBeforeViewer) && isKeyboardFocus(event.target);
|
|
11516
|
+
update();
|
|
11517
|
+
};
|
|
11518
|
+
const onFocusOut = (event) => {
|
|
11519
|
+
if (!viewerOpen) focusBeforeViewer = focused;
|
|
11520
|
+
const next = event.relatedTarget;
|
|
11521
|
+
focused = next !== null && el.contains(next) && isKeyboardFocus(next);
|
|
11522
|
+
update();
|
|
11523
|
+
};
|
|
11524
|
+
const onVisibility = () => update();
|
|
11525
|
+
const onViewer = (event) => {
|
|
11526
|
+
const open2 = event.detail?.open === true;
|
|
11527
|
+
viewerOpen = open2;
|
|
11528
|
+
viewing = open2 && el.contains(event.target);
|
|
11529
|
+
update();
|
|
11530
|
+
};
|
|
11531
|
+
if (pauseOnHover) {
|
|
11532
|
+
el.addEventListener("pointerenter", onEnter);
|
|
11533
|
+
el.addEventListener("pointerleave", onLeave);
|
|
11534
|
+
}
|
|
11535
|
+
el.addEventListener("focusin", onFocusIn);
|
|
11536
|
+
el.addEventListener("focusout", onFocusOut);
|
|
11537
|
+
ctx.doc.addEventListener("visibilitychange", onVisibility);
|
|
11538
|
+
ctx.doc.addEventListener(VIEWER_EVENT, onViewer);
|
|
11539
|
+
const Observer = win.IntersectionObserver;
|
|
11540
|
+
const observer = Observer ? new Observer((entries) => {
|
|
11541
|
+
for (const entry of entries) onscreen = entry.isIntersecting;
|
|
11542
|
+
update();
|
|
11543
|
+
}) : null;
|
|
11544
|
+
observer?.observe(el);
|
|
11545
|
+
onPausedChange(userPaused);
|
|
11546
|
+
update();
|
|
11547
|
+
return {
|
|
11548
|
+
enabled,
|
|
11549
|
+
interrupt() {
|
|
11550
|
+
held = true;
|
|
11551
|
+
if (holdTimer !== null) win.clearTimeout(holdTimer);
|
|
11552
|
+
holdTimer = win.setTimeout(() => {
|
|
11553
|
+
holdTimer = null;
|
|
11554
|
+
held = false;
|
|
11555
|
+
update();
|
|
11556
|
+
}, Math.max(0, settleMs));
|
|
11557
|
+
update();
|
|
11558
|
+
},
|
|
11559
|
+
hold(holding) {
|
|
11560
|
+
gesture = holding;
|
|
11561
|
+
update();
|
|
11562
|
+
},
|
|
11563
|
+
toggle() {
|
|
11564
|
+
userPaused = !userPaused;
|
|
11565
|
+
onPausedChange(userPaused);
|
|
11566
|
+
update();
|
|
11567
|
+
},
|
|
11568
|
+
release() {
|
|
11569
|
+
if (frame !== 0) cancelFrame(frame);
|
|
11570
|
+
frame = 0;
|
|
11571
|
+
if (timer !== null) win.clearTimeout(timer);
|
|
11572
|
+
if (holdTimer !== null) win.clearTimeout(holdTimer);
|
|
11573
|
+
timer = null;
|
|
11574
|
+
holdTimer = null;
|
|
11575
|
+
observer?.disconnect();
|
|
11576
|
+
el.removeEventListener("pointerenter", onEnter);
|
|
11577
|
+
el.removeEventListener("pointerleave", onLeave);
|
|
11578
|
+
el.removeEventListener("focusin", onFocusIn);
|
|
11579
|
+
el.removeEventListener("focusout", onFocusOut);
|
|
11580
|
+
ctx.doc.removeEventListener("visibilitychange", onVisibility);
|
|
11581
|
+
ctx.doc.removeEventListener(VIEWER_EVENT, onViewer);
|
|
11582
|
+
publishSpinning(false);
|
|
11583
|
+
}
|
|
11584
|
+
};
|
|
11585
|
+
}
|
|
11586
|
+
var AUTOPLAY_PARAM = { type: "time", default: "0s", cssProperty: "--kui-autoplay" };
|
|
11587
|
+
var PAUSE_PARAM = { type: "text", default: "", cssProperty: "--kui-pause" };
|
|
11588
|
+
var HOVER_PARAM = {
|
|
11589
|
+
type: "keyword",
|
|
11590
|
+
default: "pause",
|
|
11591
|
+
keywords: ["pause", "none"],
|
|
11592
|
+
cssProperty: "--kui-hover"
|
|
11593
|
+
};
|
|
11594
|
+
function motionPeriods(request) {
|
|
11595
|
+
const { params, ctx, label } = request;
|
|
11596
|
+
const spinRaw = request.spin ? params.ms("spin", 0) : 0;
|
|
11597
|
+
const autoplayRaw = params.ms("autoplay", 0);
|
|
11598
|
+
const spin2 = clampPeriod(spinRaw);
|
|
11599
|
+
const autoplay = clampPeriod(autoplayRaw);
|
|
11600
|
+
if (spin2 !== spinRaw) ctx.warn(`${label} spin: minimum is 2s; clamped to ${Math.abs(spin2) / 1e3}s`);
|
|
11601
|
+
if (autoplay !== autoplayRaw) {
|
|
11602
|
+
ctx.warn(`${label} autoplay: minimum is 2s; clamped to ${Math.abs(autoplay) / 1e3}s`);
|
|
11603
|
+
}
|
|
11604
|
+
if (spin2 !== 0 && autoplay !== 0) {
|
|
11605
|
+
ctx.warn(`${label}: spin: and autoplay: are both set; spin: wins`);
|
|
11606
|
+
return { spin: spin2, autoplay: 0 };
|
|
11607
|
+
}
|
|
11608
|
+
return { spin: spin2, autoplay };
|
|
11609
|
+
}
|
|
11610
|
+
function createDeckMotion(request) {
|
|
11611
|
+
const { el, ctx, params, label, settleMs, step } = request;
|
|
11612
|
+
const { spin: spin2, autoplay } = motionPeriods(request);
|
|
11613
|
+
const ledgers = /* @__PURE__ */ new Map();
|
|
11614
|
+
const pauseSelector = resolveTarget(params.text("pause"), ctx, `${label} pause`);
|
|
11615
|
+
const reflectPaused = (paused) => {
|
|
11616
|
+
if (!pauseSelector) return;
|
|
11617
|
+
for (const node of queryControls(el, ctx, pauseSelector)) {
|
|
11618
|
+
let ledger = ledgers.get(node);
|
|
11619
|
+
if (!ledger) {
|
|
11620
|
+
ledger = createAttributeLedger(node);
|
|
11621
|
+
ledgers.set(node, ledger);
|
|
11622
|
+
}
|
|
11623
|
+
ledger.set("aria-pressed", String(paused));
|
|
11624
|
+
}
|
|
11625
|
+
};
|
|
11626
|
+
const motion = createAutoMotion({
|
|
11627
|
+
el,
|
|
11628
|
+
ctx,
|
|
11629
|
+
spinMs: spin2,
|
|
11630
|
+
autoplayMs: autoplay,
|
|
11631
|
+
settleMs,
|
|
11632
|
+
pauseOnHover: !params.is("hover", "none"),
|
|
11633
|
+
advance: request.spin?.advance,
|
|
11634
|
+
step,
|
|
11635
|
+
setSpinning: request.spin?.setSpinning,
|
|
11636
|
+
onPausedChange: reflectPaused
|
|
11637
|
+
});
|
|
11638
|
+
let pause = null;
|
|
11639
|
+
if (pauseSelector && !motion.enabled) {
|
|
11640
|
+
ctx.warn(`${label} pause: has nothing to pause without ${request.spin ? "spin: or " : ""}autoplay:`);
|
|
11641
|
+
} else if (pauseSelector) {
|
|
11642
|
+
pause = bindControl({ el, ctx, params, label }, "pause", () => motion.toggle());
|
|
11643
|
+
}
|
|
11644
|
+
return {
|
|
11645
|
+
motion,
|
|
11646
|
+
pause,
|
|
11647
|
+
release() {
|
|
11648
|
+
motion.release();
|
|
11649
|
+
for (const ledger of ledgers.values()) ledger.restore();
|
|
11650
|
+
ledgers.clear();
|
|
11651
|
+
}
|
|
11652
|
+
};
|
|
11653
|
+
}
|
|
11654
|
+
|
|
11655
|
+
// src/effects/swipe-event.ts
|
|
11656
|
+
var SWIPE_EVENT = "kui:swipe";
|
|
11657
|
+
var DIRECTIONS = /* @__PURE__ */ new Set(["left", "right", "up", "down"]);
|
|
11658
|
+
function announceSwipe(win, el, direction) {
|
|
11659
|
+
const Ctor = win.CustomEvent;
|
|
11660
|
+
el.dispatchEvent(new Ctor(SWIPE_EVENT, { bubbles: true, detail: { direction } }));
|
|
11661
|
+
}
|
|
11662
|
+
function swipeStep(direction) {
|
|
11663
|
+
return direction === "left" || direction === "up" ? 1 : -1;
|
|
11664
|
+
}
|
|
11665
|
+
function swipeReaches(source, el) {
|
|
11666
|
+
if (source === el) return true;
|
|
11667
|
+
if (!source.contains(el)) return false;
|
|
11668
|
+
for (let node = el.parentElement; node && node !== source; node = node.parentElement) {
|
|
11669
|
+
if (node.hasAttribute("data-kui-step")) return false;
|
|
11670
|
+
}
|
|
11671
|
+
return true;
|
|
11672
|
+
}
|
|
11673
|
+
function stepOnSwipe(doc, el, step) {
|
|
11674
|
+
const onSwipe = (event) => {
|
|
11675
|
+
const direction = event.detail?.direction;
|
|
11676
|
+
const source = event.target;
|
|
11677
|
+
if (!direction || !DIRECTIONS.has(direction) || source.nodeType !== 1) return;
|
|
11678
|
+
if (swipeReaches(source, el)) step(swipeStep(direction));
|
|
11679
|
+
};
|
|
11680
|
+
doc.addEventListener(SWIPE_EVENT, onSwipe);
|
|
11681
|
+
return () => doc.removeEventListener(SWIPE_EVENT, onSwipe);
|
|
11682
|
+
}
|
|
11683
|
+
|
|
11684
|
+
// src/effects/forms/primitives.ts
|
|
11685
|
+
var timing = {
|
|
11686
|
+
duration: { type: "time", default: "400ms", cssProperty: "--kui-duration" },
|
|
11687
|
+
...TRIGGER_DELAY_PARAM,
|
|
11688
|
+
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" }
|
|
11689
|
+
};
|
|
11690
|
+
var FORM_STATE_TIMING = {
|
|
11691
|
+
because: "forms.css pins its timing literally \u2014 the motion lands on a sibling that inline custom properties cannot reach"
|
|
11692
|
+
};
|
|
11693
|
+
var NATIVE_STATE_PRIMITIVE = {
|
|
11694
|
+
id: "native-state",
|
|
11695
|
+
renderer: "javascript",
|
|
10816
11696
|
channels: [CHANNEL.translate, CHANNEL.scale, CHANNEL.opacity, CHANNEL.stroke, CHANNEL.color],
|
|
10817
11697
|
parameters: {},
|
|
10818
11698
|
supportedTimelines: ["time"],
|
|
@@ -10921,7 +11801,7 @@ var kuinetic = (() => {
|
|
|
10921
11801
|
}
|
|
10922
11802
|
var STRENGTH_METER_PRIMITIVE = jsInputPrimitive(
|
|
10923
11803
|
"strength-meter",
|
|
10924
|
-
["meter"],
|
|
11804
|
+
["meter", attributeChannel("data-kui-strength-level")],
|
|
10925
11805
|
{},
|
|
10926
11806
|
deferPrepare(prepareStrengthMeter)
|
|
10927
11807
|
);
|
|
@@ -10932,14 +11812,55 @@ var kuinetic = (() => {
|
|
|
10932
11812
|
deferPrepare(prepareRangeFill)
|
|
10933
11813
|
);
|
|
10934
11814
|
function prepareStepProgress(el, params, ctx) {
|
|
10935
|
-
const
|
|
11815
|
+
const label = "step-progress";
|
|
11816
|
+
const selector = resolveTarget(params.text("target"), ctx, label);
|
|
10936
11817
|
const scope = scopeParam(params, "page");
|
|
10937
11818
|
const resolveSteps = () => selector ? queryScoped(el, ctx, selector, scope) : el.children;
|
|
10938
|
-
|
|
11819
|
+
const lightbox = params.is("lightbox");
|
|
11820
|
+
const { motion, pause, release: releaseMotion } = createDeckMotion({
|
|
11821
|
+
el,
|
|
11822
|
+
ctx,
|
|
11823
|
+
params,
|
|
11824
|
+
label,
|
|
11825
|
+
settleMs: effectDurationMs(params, 400),
|
|
11826
|
+
step: (direction) => direction > 0 ? index.next() : index.prev()
|
|
11827
|
+
});
|
|
11828
|
+
const index = createStepIndex({
|
|
11829
|
+
el,
|
|
11830
|
+
params,
|
|
11831
|
+
ctx,
|
|
11832
|
+
resolveSteps,
|
|
11833
|
+
controls: pause ? [pause] : [],
|
|
11834
|
+
onInput: () => motion.interrupt(),
|
|
11835
|
+
/*
|
|
11836
|
+
* With `lightbox:true` a click on a slide opens the viewer, so the container click cannot also
|
|
11837
|
+
* advance the deck: one press would both open the gallery and move the slide behind it. The
|
|
11838
|
+
* deck still moves on its arrows, dots, swipe and autoplay.
|
|
11839
|
+
*/
|
|
11840
|
+
clickFallback: !lightbox
|
|
11841
|
+
});
|
|
11842
|
+
const releaseViewer = deckLightbox(
|
|
11843
|
+
lightbox,
|
|
11844
|
+
{ host: el, cards: resolveSteps, doc: ctx.doc, reducedMotion: ctx.reducedMotion },
|
|
11845
|
+
(message) => ctx.warn(`${label} ${message}`)
|
|
11846
|
+
);
|
|
11847
|
+
const releaseSwipe = stepOnSwipe(ctx.doc, el, (direction) => {
|
|
11848
|
+
motion.interrupt();
|
|
11849
|
+
if (direction > 0) index.next();
|
|
11850
|
+
else index.prev();
|
|
11851
|
+
});
|
|
11852
|
+
return () => {
|
|
11853
|
+
releaseViewer();
|
|
11854
|
+
releaseSwipe();
|
|
11855
|
+
releaseMotion();
|
|
11856
|
+
index.release();
|
|
11857
|
+
};
|
|
10939
11858
|
}
|
|
10940
11859
|
var STEP_PROGRESS_BASE = jsInputPrimitive(
|
|
10941
11860
|
"step-progress",
|
|
10942
|
-
|
|
11861
|
+
// The host's children are its steps unless `target:` says otherwise, so a widget that inserts
|
|
11862
|
+
// one (`slow-mo`'s toggle) would become a step: the subtree is this deck's.
|
|
11863
|
+
[SUBTREE_CHANNEL, attributeChannel("data-kui-step")],
|
|
10943
11864
|
{
|
|
10944
11865
|
// Empty default, not '4': `readParams` fills every declared default in unconditionally and
|
|
10945
11866
|
// does not validate it, so an empty one is how `prepareStepProgress` tells "unauthored" from
|
|
@@ -10986,6 +11907,16 @@ var kuinetic = (() => {
|
|
|
10986
11907
|
* at 390px and at 1440px, and the page keeps owning the box. Same reason `rest:` is a number.
|
|
10987
11908
|
*/
|
|
10988
11909
|
main: { type: "number", default: "1", cssProperty: "--kui-main", finite: true, minimum: 0 },
|
|
11910
|
+
/*
|
|
11911
|
+
* The deck steps itself every `autoplay:` (`0s`, the default, is off), and `pause:` names the
|
|
11912
|
+
* author's play/pause control, and `hover:none` stops a resting pointer pausing it. The spatial
|
|
11913
|
+
* decks' declarations and scheduler, shared — see `prepareStepProgress` and `effects/auto-motion.ts`.
|
|
11914
|
+
*/
|
|
11915
|
+
autoplay: AUTOPLAY_PARAM,
|
|
11916
|
+
pause: PAUSE_PARAM,
|
|
11917
|
+
hover: HOVER_PARAM,
|
|
11918
|
+
// A click on a slide opens every slide in the lightbox gallery. See `core/deck-viewer.ts`.
|
|
11919
|
+
lightbox: DECK_LIGHTBOX_PARAM,
|
|
10989
11920
|
// Which tree `target:` is searched in. Unset means this primitive's own historical answer —
|
|
10990
11921
|
// see `prepareStepProgress`. One declaration, shared: `effects/step-marking.ts`.
|
|
10991
11922
|
scope: SCOPE_PARAM
|
|
@@ -11032,7 +11963,7 @@ var kuinetic = (() => {
|
|
|
11032
11963
|
}
|
|
11033
11964
|
var SUBMIT_FLOW_PRIMITIVE = jsInputPrimitive(
|
|
11034
11965
|
"submit-flow",
|
|
11035
|
-
["
|
|
11966
|
+
[attributeChannel("data-kui-stage")],
|
|
11036
11967
|
{
|
|
11037
11968
|
load: { type: "time", default: "1200ms", cssProperty: "--kui-load" },
|
|
11038
11969
|
hold: { type: "time", default: "1500ms", cssProperty: "--kui-hold" }
|
|
@@ -11082,9 +12013,12 @@ var kuinetic = (() => {
|
|
|
11082
12013
|
* two readings is the stylesheet. `step-progress` was the wrong word to type on a deck of
|
|
11083
12014
|
* slides, which is the only reason this alias exists; nothing behaves differently under it.
|
|
11084
12015
|
*
|
|
11085
|
-
* It is still an *index*, not a carousel component: no ARIA, no roving focus
|
|
11086
|
-
*
|
|
11087
|
-
*
|
|
12016
|
+
* It is still an *index*, not a carousel component: no ARIA, no roving focus. That boundary is
|
|
12017
|
+
* the one section H states — the library animates elements you control and does not own the
|
|
12018
|
+
* widget — and naming this `carousel` does not move it. `autoplay:`/`pause:` do not cross it: they
|
|
12019
|
+
* press the same "next" a visitor's control does, on a timer, and the control is still the
|
|
12020
|
+
* author's markup (`effects/auto-motion.ts`, shared with the spatial decks). Likewise
|
|
12021
|
+
* `lightbox:true` hands a click on a slide to the showcase gallery (`core/deck-viewer.ts`).
|
|
11088
12022
|
*
|
|
11089
12023
|
* No `requiresOwnSubtree` here, unlike `step-progress` beside it. That flag means "this name's
|
|
11090
12024
|
* shipped CSS reaches past the element into its descendants, so the universal `target:` must
|
|
@@ -11146,6 +12080,10 @@ var kuinetic = (() => {
|
|
|
11146
12080
|
let active = false;
|
|
11147
12081
|
let longPressTimer = null;
|
|
11148
12082
|
let longPressFired = false;
|
|
12083
|
+
let captured = false;
|
|
12084
|
+
let pointerId = null;
|
|
12085
|
+
let lastHandled = null;
|
|
12086
|
+
let documentListening = null;
|
|
11149
12087
|
function sampleOf(event) {
|
|
11150
12088
|
return { x: event.clientX, y: event.clientY, time: deps.now() };
|
|
11151
12089
|
}
|
|
@@ -11159,11 +12097,24 @@ var kuinetic = (() => {
|
|
|
11159
12097
|
longPressTimer = null;
|
|
11160
12098
|
}
|
|
11161
12099
|
function onDown(event) {
|
|
12100
|
+
if (origin && pointerId !== null && event.pointerId !== pointerId) {
|
|
12101
|
+
clearLongPress();
|
|
12102
|
+
const last = samples[samples.length - 1];
|
|
12103
|
+
releaseCapture(pointerId);
|
|
12104
|
+
finish({ x: last.x, y: last.y, time: deps.now() });
|
|
12105
|
+
return;
|
|
12106
|
+
}
|
|
12107
|
+
if (options.accept && !options.accept(event)) {
|
|
12108
|
+
origin = null;
|
|
12109
|
+
return;
|
|
12110
|
+
}
|
|
11162
12111
|
origin = sampleOf(event);
|
|
11163
12112
|
samples = [origin];
|
|
11164
12113
|
active = false;
|
|
11165
12114
|
longPressFired = false;
|
|
11166
|
-
|
|
12115
|
+
pointerId = event.pointerId;
|
|
12116
|
+
captured = capturePointer === true && takeCapture(event.pointerId);
|
|
12117
|
+
if (!captured) listenOnDocument();
|
|
11167
12118
|
if (longPressMs > 0) {
|
|
11168
12119
|
longPressTimer = deps.setTimer(() => {
|
|
11169
12120
|
longPressFired = true;
|
|
@@ -11171,8 +12122,43 @@ var kuinetic = (() => {
|
|
|
11171
12122
|
}, longPressMs);
|
|
11172
12123
|
}
|
|
11173
12124
|
}
|
|
12125
|
+
function takeCapture(id) {
|
|
12126
|
+
if (typeof el.setPointerCapture !== "function") return false;
|
|
12127
|
+
try {
|
|
12128
|
+
el.setPointerCapture(id);
|
|
12129
|
+
return true;
|
|
12130
|
+
} catch {
|
|
12131
|
+
return false;
|
|
12132
|
+
}
|
|
12133
|
+
}
|
|
12134
|
+
function captureOnDrag(event) {
|
|
12135
|
+
if (capturePointer !== "drag") return;
|
|
12136
|
+
captured = takeCapture(event.pointerId);
|
|
12137
|
+
if (captured) stopListeningOnDocument();
|
|
12138
|
+
}
|
|
12139
|
+
function listenOnDocument() {
|
|
12140
|
+
const doc = el.ownerDocument;
|
|
12141
|
+
if (!doc || documentListening) return;
|
|
12142
|
+
documentListening = doc;
|
|
12143
|
+
doc.addEventListener("pointermove", onMove, { passive: true });
|
|
12144
|
+
doc.addEventListener("pointerup", onUp, { passive: true });
|
|
12145
|
+
doc.addEventListener("pointercancel", onUp, { passive: true });
|
|
12146
|
+
}
|
|
12147
|
+
function stopListeningOnDocument() {
|
|
12148
|
+
const doc = documentListening;
|
|
12149
|
+
if (!doc) return;
|
|
12150
|
+
documentListening = null;
|
|
12151
|
+
doc.removeEventListener("pointermove", onMove);
|
|
12152
|
+
doc.removeEventListener("pointerup", onUp);
|
|
12153
|
+
doc.removeEventListener("pointercancel", onUp);
|
|
12154
|
+
}
|
|
12155
|
+
function firstHearing(event) {
|
|
12156
|
+
if (event === lastHandled) return false;
|
|
12157
|
+
lastHandled = event;
|
|
12158
|
+
return true;
|
|
12159
|
+
}
|
|
11174
12160
|
function onMove(event) {
|
|
11175
|
-
if (!origin) return;
|
|
12161
|
+
if (!origin || !firstHearing(event)) return;
|
|
11176
12162
|
const sample = sampleOf(event);
|
|
11177
12163
|
samples.push(sample);
|
|
11178
12164
|
if (samples.length > MAX_SAMPLES2) samples.shift();
|
|
@@ -11181,17 +12167,17 @@ var kuinetic = (() => {
|
|
|
11181
12167
|
if (!active) {
|
|
11182
12168
|
active = true;
|
|
11183
12169
|
clearLongPress();
|
|
12170
|
+
captureOnDrag(event);
|
|
11184
12171
|
handlers.onStart?.(origin);
|
|
11185
12172
|
}
|
|
11186
12173
|
handlers.onMove?.(vector, sample);
|
|
11187
12174
|
}
|
|
11188
|
-
function
|
|
11189
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
11194
|
-
const vector = vectorNow(sample);
|
|
12175
|
+
function releaseCapture(id) {
|
|
12176
|
+
if (captured) el.releasePointerCapture?.(id);
|
|
12177
|
+
captured = false;
|
|
12178
|
+
pointerId = null;
|
|
12179
|
+
}
|
|
12180
|
+
function reportEnd(vector, sample) {
|
|
11195
12181
|
if (active) {
|
|
11196
12182
|
handlers.onEnd?.(vector, sample);
|
|
11197
12183
|
const direction = swipeDirection(vector, swipeVelocity);
|
|
@@ -11199,21 +12185,41 @@ var kuinetic = (() => {
|
|
|
11199
12185
|
} else if (longPressFired) {
|
|
11200
12186
|
handlers.onEnd?.(vector, sample);
|
|
11201
12187
|
}
|
|
12188
|
+
}
|
|
12189
|
+
function onUp(event) {
|
|
12190
|
+
clearLongPress();
|
|
12191
|
+
stopListeningOnDocument();
|
|
12192
|
+
releaseCapture(event.pointerId);
|
|
12193
|
+
if (!origin) return;
|
|
12194
|
+
finish(sampleOf(event));
|
|
12195
|
+
}
|
|
12196
|
+
function finish(sample) {
|
|
12197
|
+
samples.push(sample);
|
|
12198
|
+
reportEnd(vectorNow(sample), sample);
|
|
11202
12199
|
origin = null;
|
|
11203
12200
|
active = false;
|
|
11204
12201
|
longPressFired = false;
|
|
11205
12202
|
samples = [];
|
|
11206
12203
|
}
|
|
12204
|
+
function onLostCapture(event) {
|
|
12205
|
+
if (event.target !== el) return;
|
|
12206
|
+
if (!captured || event.pointerId !== pointerId) return;
|
|
12207
|
+
captured = false;
|
|
12208
|
+
onUp(event);
|
|
12209
|
+
}
|
|
11207
12210
|
el.addEventListener("pointerdown", onDown);
|
|
11208
12211
|
el.addEventListener("pointermove", onMove, { passive: true });
|
|
11209
12212
|
el.addEventListener("pointerup", onUp, { passive: true });
|
|
11210
12213
|
el.addEventListener("pointercancel", onUp, { passive: true });
|
|
12214
|
+
el.addEventListener("lostpointercapture", onLostCapture, { passive: true });
|
|
11211
12215
|
return () => {
|
|
11212
12216
|
clearLongPress();
|
|
12217
|
+
stopListeningOnDocument();
|
|
11213
12218
|
el.removeEventListener("pointerdown", onDown);
|
|
11214
12219
|
el.removeEventListener("pointermove", onMove);
|
|
11215
12220
|
el.removeEventListener("pointerup", onUp);
|
|
11216
12221
|
el.removeEventListener("pointercancel", onUp);
|
|
12222
|
+
el.removeEventListener("lostpointercapture", onLostCapture);
|
|
11217
12223
|
};
|
|
11218
12224
|
}
|
|
11219
12225
|
function defaultGestureDeps() {
|
|
@@ -11351,22 +12357,26 @@ var kuinetic = (() => {
|
|
|
11351
12357
|
runners.x.to(offset.x + vector.vx * carry);
|
|
11352
12358
|
runners.y.to(offset.y + vector.vy * carry);
|
|
11353
12359
|
}
|
|
11354
|
-
function prepareSwipeable(el, params) {
|
|
12360
|
+
function prepareSwipeable(el, params, ctx) {
|
|
12361
|
+
const axis = params.text("axis", "both");
|
|
12362
|
+
if (axis !== "both") ctx.style.set("touch-action", `${axis === "x" ? "pan-y" : "pan-x"} pinch-zoom`);
|
|
11355
12363
|
const stop = recognise(
|
|
11356
12364
|
el,
|
|
11357
12365
|
{
|
|
11358
12366
|
onSwipe(direction) {
|
|
11359
12367
|
el.setAttribute("data-kui-swipe", direction);
|
|
12368
|
+
announceSwipe(ctx.win, el, direction);
|
|
11360
12369
|
}
|
|
11361
12370
|
},
|
|
11362
12371
|
{
|
|
11363
|
-
axis
|
|
12372
|
+
axis,
|
|
11364
12373
|
swipeVelocity: params.num("velocity", 300),
|
|
11365
|
-
// This primitive
|
|
11366
|
-
//
|
|
11367
|
-
//
|
|
11368
|
-
//
|
|
11369
|
-
capturePointer
|
|
12374
|
+
// This primitive moves nothing, so it must not hold the pointer from `pointerdown` — that
|
|
12375
|
+
// retargets the following `click` at this element and kills every interactive child (a
|
|
12376
|
+
// `swipe-x` on a carousel shell killed its own dots and buttons). But a flick that leaves a
|
|
12377
|
+
// small element never delivers `pointerup` to it unless captured, so capture only once the
|
|
12378
|
+
// drag starts. See `capturePointer` in `core/gesture.ts`.
|
|
12379
|
+
capturePointer: "drag"
|
|
11370
12380
|
}
|
|
11371
12381
|
);
|
|
11372
12382
|
return () => {
|
|
@@ -11425,7 +12435,7 @@ var kuinetic = (() => {
|
|
|
11425
12435
|
var GESTURE_PRIMITIVES = [
|
|
11426
12436
|
gesturePrimitive(
|
|
11427
12437
|
"draggable",
|
|
11428
|
-
["translate"],
|
|
12438
|
+
["translate", attributeChannel("data-kui-dragging")],
|
|
11429
12439
|
{
|
|
11430
12440
|
...springParams2,
|
|
11431
12441
|
axis: { type: "keyword", default: "both", cssProperty: "--kui-axis", keywords: ["x", "y", "both"] },
|
|
@@ -11456,7 +12466,7 @@ var kuinetic = (() => {
|
|
|
11456
12466
|
),
|
|
11457
12467
|
gesturePrimitive(
|
|
11458
12468
|
"swipeable",
|
|
11459
|
-
["
|
|
12469
|
+
[attributeChannel("data-kui-swipe")],
|
|
11460
12470
|
{
|
|
11461
12471
|
axis: { type: "keyword", default: "both", cssProperty: "--kui-axis", keywords: ["x", "y", "both"] },
|
|
11462
12472
|
velocity: { type: "number", default: "300", cssProperty: "--kui-velocity" }
|
|
@@ -11465,7 +12475,7 @@ var kuinetic = (() => {
|
|
|
11465
12475
|
),
|
|
11466
12476
|
gesturePrimitive(
|
|
11467
12477
|
"pressable",
|
|
11468
|
-
["
|
|
12478
|
+
[attributeChannel("data-kui-pressed")],
|
|
11469
12479
|
{ duration: { type: "time", default: "500ms", cssProperty: "--kui-duration" } },
|
|
11470
12480
|
// `duration` here is the hold threshold, not a span of motion — `long-press 800ms` means
|
|
11471
12481
|
// "count it once the finger has been down for 800ms". It is read (see `preparePressable`), so
|
|
@@ -11516,6 +12526,7 @@ var kuinetic = (() => {
|
|
|
11516
12526
|
},
|
|
11517
12527
|
{ name: "swipe", primitive: "swipeable", phase: "state" },
|
|
11518
12528
|
{ name: "swipe-x", primitive: "swipeable", params: { axis: "x" }, phase: "state" },
|
|
12529
|
+
{ name: "swipe-y", primitive: "swipeable", params: { axis: "y" }, phase: "state" },
|
|
11519
12530
|
{ name: "long-press", primitive: "pressable", phase: "state" },
|
|
11520
12531
|
{ name: "magnetic", primitive: "magnetic", phase: "state" },
|
|
11521
12532
|
{
|
|
@@ -11811,13 +12822,15 @@ var kuinetic = (() => {
|
|
|
11811
12822
|
),
|
|
11812
12823
|
layoutPrimitive(
|
|
11813
12824
|
"auto-height",
|
|
11814
|
-
|
|
12825
|
+
// The two properties it writes on the panel: `overflow: hidden`, and the `height` it animates.
|
|
12826
|
+
["overflow", "height"],
|
|
11815
12827
|
{ attribute: { type: "text", default: "data-open", cssProperty: "--kui-attribute" } },
|
|
11816
12828
|
deferPrepare(prepareAutoHeight)
|
|
11817
12829
|
),
|
|
11818
12830
|
layoutPrimitive(
|
|
11819
12831
|
"flip-indicator",
|
|
11820
|
-
|
|
12832
|
+
// The bar's landing spot (`translate`) and its `width`, matched to the selected tab.
|
|
12833
|
+
["translate", "width"],
|
|
11821
12834
|
{
|
|
11822
12835
|
follow: { type: "text", default: "", cssProperty: "--kui-follow" },
|
|
11823
12836
|
attribute: { type: "text", default: "aria-selected", cssProperty: "--kui-attribute" }
|
|
@@ -12066,26 +13079,25 @@ var kuinetic = (() => {
|
|
|
12066
13079
|
var NAV_JS_PRIMITIVES = [
|
|
12067
13080
|
navPrimitive(
|
|
12068
13081
|
"header-shrink",
|
|
12069
|
-
//
|
|
12070
|
-
//
|
|
12071
|
-
//
|
|
12072
|
-
//
|
|
12073
|
-
//
|
|
12074
|
-
//
|
|
12075
|
-
|
|
12076
|
-
["layout", "shadow"],
|
|
13082
|
+
// The three properties its host rule writes (navigation.css): the two it interpolates from
|
|
13083
|
+
// `--kui-shrink`, `padding-block` and `font-size`, each under its own name, and `box-shadow`
|
|
13084
|
+
// on the channel that already owns every other box-shadow writer (`lift-shadow`,
|
|
13085
|
+
// `border-glow`), so composing `header-shrink` with either of those is a refused conflict
|
|
13086
|
+
// instead of a silent clobber. The two used to sit in the shared `'layout'` bucket, which also
|
|
13087
|
+
// refused `header-shrink, pin-until` — a sticky header that shrinks — over nothing they share.
|
|
13088
|
+
["padding", "font-size", "shadow", attributeChannel("data-kui-shrunk")],
|
|
12077
13089
|
{ offset: { type: "number", default: "120", cssProperty: "--kui-offset" } },
|
|
12078
13090
|
deferPrepare(prepareHeaderShrink)
|
|
12079
13091
|
),
|
|
12080
13092
|
navPrimitive(
|
|
12081
13093
|
"header-hide-on-scroll",
|
|
12082
|
-
[CHANNEL.translate],
|
|
13094
|
+
[CHANNEL.translate, attributeChannel("data-kui-hidden")],
|
|
12083
13095
|
{ offset: { type: "number", default: "8", cssProperty: "--kui-offset" } },
|
|
12084
13096
|
deferPrepare(prepareHeaderHide)
|
|
12085
13097
|
),
|
|
12086
13098
|
navPrimitive(
|
|
12087
13099
|
"back-to-top-fade",
|
|
12088
|
-
[CHANNEL.opacity, CHANNEL.translate],
|
|
13100
|
+
[CHANNEL.opacity, CHANNEL.translate, attributeChannel("data-kui-visible")],
|
|
12089
13101
|
{ offset: { type: "number", default: "400", cssProperty: "--kui-offset" } },
|
|
12090
13102
|
deferPrepare(prepareBackToTop)
|
|
12091
13103
|
)
|
|
@@ -12648,7 +13660,10 @@ var kuinetic = (() => {
|
|
|
12648
13660
|
var SCROLL_PRIMITIVES = [
|
|
12649
13661
|
scrollPrimitive({
|
|
12650
13662
|
id: "pin",
|
|
12651
|
-
|
|
13663
|
+
// `position: sticky` and its `top` (`installSticky`): the host's position is what it holds. The
|
|
13664
|
+
// spacer it can insert is a sibling, not a child, so the host's subtree is not claimed. And the
|
|
13665
|
+
// `data-kui-pinned` flag it publishes while holding.
|
|
13666
|
+
channels: ["position", "progress", attributeChannel("data-kui-pinned")],
|
|
12652
13667
|
parameters: {
|
|
12653
13668
|
...distanceParam2,
|
|
12654
13669
|
...stickyParams
|
|
@@ -12658,7 +13673,13 @@ var kuinetic = (() => {
|
|
|
12658
13673
|
}),
|
|
12659
13674
|
scrollPrimitive({
|
|
12660
13675
|
id: "scroll-progress",
|
|
12661
|
-
|
|
13676
|
+
// `data-kui-step` beside `progress`: with `steps:` this publishes the same index attribute (and
|
|
13677
|
+
// its `--kui-step` twin) a `carousel` does, so the two on one element would overwrite each
|
|
13678
|
+
// other's step every frame. The claim is static, so it holds with `steps:0` too — a list can't
|
|
13679
|
+
// be composed or refused per param value. One claim covers both writes: every primitive that
|
|
13680
|
+
// writes the `--kui-step` index also writes the attribute, so a separate channel for the
|
|
13681
|
+
// property could never change a decision.
|
|
13682
|
+
channels: ["progress", attributeChannel("data-kui-step")],
|
|
12662
13683
|
parameters: {
|
|
12663
13684
|
...distanceParam2,
|
|
12664
13685
|
steps: { type: "number", default: "0", cssProperty: "--kui-steps" },
|
|
@@ -12694,7 +13715,8 @@ var kuinetic = (() => {
|
|
|
12694
13715
|
}),
|
|
12695
13716
|
scrollPrimitive({
|
|
12696
13717
|
id: "media-scrub",
|
|
12697
|
-
|
|
13718
|
+
// `position`: it holds its host the way a pin does (`position: sticky` and its `top`).
|
|
13719
|
+
channels: ["media", "progress", "position"],
|
|
12698
13720
|
parameters: {
|
|
12699
13721
|
...distanceParam2,
|
|
12700
13722
|
// A scrub is a hold, so it needs the same two knobs a pin does. Declaring them here is what
|
|
@@ -12713,7 +13735,7 @@ var kuinetic = (() => {
|
|
|
12713
13735
|
}),
|
|
12714
13736
|
scrollPrimitive({
|
|
12715
13737
|
id: "scroll-spy",
|
|
12716
|
-
channels: ["
|
|
13738
|
+
channels: [attributeChannel("data-kui-active")],
|
|
12717
13739
|
parameters: {
|
|
12718
13740
|
// `distance`: the per-section form only. `offset-top`: the container form only. Each is a
|
|
12719
13741
|
// no-op — warned, not silent — in the other; see `prepareScrollSpySingle` and
|
|
@@ -12739,8 +13761,8 @@ var kuinetic = (() => {
|
|
|
12739
13761
|
scrollPrimitive({
|
|
12740
13762
|
id: "smooth-scroll",
|
|
12741
13763
|
/*
|
|
12742
|
-
* Its own channel, not the `'layout'` it used to share with `pin`, `stacking-cards` and
|
|
12743
|
-
* `scroll-snap
|
|
13764
|
+
* Its own channel, not the `'layout'` bucket it used to share with `pin`, `stacking-cards` and
|
|
13765
|
+
* `scroll-snap` (a bucket since split into the properties each writes). The channel model exists to stop two effects fighting over the same CSS
|
|
12744
13766
|
* property, and this one writes exactly `scroll-behavior` — a property that describes how a
|
|
12745
13767
|
* *user-or-script-initiated* scroll is performed, and that no other primitive touches.
|
|
12746
13768
|
*
|
|
@@ -12761,7 +13783,11 @@ var kuinetic = (() => {
|
|
|
12761
13783
|
}),
|
|
12762
13784
|
scrollPrimitive({
|
|
12763
13785
|
id: "scroll-snap",
|
|
12764
|
-
|
|
13786
|
+
// `scroll-snap-type`, and with `target:` the container it makes of the host (`installSnapContainer`):
|
|
13787
|
+
// `overflow`, and `display: flex` on the x axis — `discrete`'s property. A claim is static, so
|
|
13788
|
+
// it holds without `target:` too. The subtree likewise: with no `target:` the host's children
|
|
13789
|
+
// are the snap items, each given a `scroll-snap-align`.
|
|
13790
|
+
channels: ["scroll-snap-type", "overflow", "discrete", SUBTREE_CHANNEL],
|
|
12765
13791
|
parameters: {
|
|
12766
13792
|
axis: { type: "keyword", default: "y", cssProperty: "--kui-axis", keywords: ["x", "y"] },
|
|
12767
13793
|
strictness: {
|
|
@@ -13103,7 +14129,7 @@ var kuinetic = (() => {
|
|
|
13103
14129
|
return children.filter((child) => child.textContent.trim() === "");
|
|
13104
14130
|
}
|
|
13105
14131
|
function withIconParts(inner) {
|
|
13106
|
-
return (el, params, ctx) => {
|
|
14132
|
+
return inheritTimingContract(inner, (el, params, ctx) => {
|
|
13107
14133
|
if (!el.querySelector(".kui-bar")) {
|
|
13108
14134
|
const bars = findBars(el);
|
|
13109
14135
|
if (bars.length > 0) {
|
|
@@ -13122,7 +14148,7 @@ var kuinetic = (() => {
|
|
|
13122
14148
|
}
|
|
13123
14149
|
}
|
|
13124
14150
|
return inner(el, params, ctx);
|
|
13125
|
-
};
|
|
14151
|
+
});
|
|
13126
14152
|
}
|
|
13127
14153
|
|
|
13128
14154
|
// src/effects/svg/index.ts
|
|
@@ -13161,18 +14187,18 @@ var kuinetic = (() => {
|
|
|
13161
14187
|
};
|
|
13162
14188
|
let current = 0;
|
|
13163
14189
|
const enter = () => drive(1);
|
|
13164
|
-
const
|
|
14190
|
+
const leave2 = () => drive(0);
|
|
13165
14191
|
el.addEventListener("pointerenter", enter, { passive: true });
|
|
13166
14192
|
el.addEventListener("focusin", enter, { passive: true });
|
|
13167
|
-
el.addEventListener("pointerleave",
|
|
13168
|
-
el.addEventListener("focusout",
|
|
14193
|
+
el.addEventListener("pointerleave", leave2, { passive: true });
|
|
14194
|
+
el.addEventListener("focusout", leave2, { passive: true });
|
|
13169
14195
|
return () => {
|
|
13170
14196
|
cancelled = true;
|
|
13171
14197
|
cancelAnimationFrame(frame);
|
|
13172
14198
|
el.removeEventListener("pointerenter", enter);
|
|
13173
14199
|
el.removeEventListener("focusin", enter);
|
|
13174
|
-
el.removeEventListener("pointerleave",
|
|
13175
|
-
el.removeEventListener("focusout",
|
|
14200
|
+
el.removeEventListener("pointerleave", leave2);
|
|
14201
|
+
el.removeEventListener("focusout", leave2);
|
|
13176
14202
|
path2.setAttribute("d", startPath);
|
|
13177
14203
|
};
|
|
13178
14204
|
}
|
|
@@ -13193,7 +14219,16 @@ var kuinetic = (() => {
|
|
|
13193
14219
|
var ICON_TOGGLE_PRIMITIVE = {
|
|
13194
14220
|
id: "icon-toggle",
|
|
13195
14221
|
renderer: "javascript",
|
|
13196
|
-
|
|
14222
|
+
// The subtree too: with no `.kui-bar` markup the host's own children are the bars
|
|
14223
|
+
// (`icon-parts.ts` stamps them), so a deck or widget beside it would have its children moved.
|
|
14224
|
+
channels: [
|
|
14225
|
+
CHANNEL.translate,
|
|
14226
|
+
CHANNEL.rotate,
|
|
14227
|
+
CHANNEL.scale,
|
|
14228
|
+
CHANNEL.opacity,
|
|
14229
|
+
CHANNEL.clip,
|
|
14230
|
+
SUBTREE_CHANNEL
|
|
14231
|
+
],
|
|
13197
14232
|
parameters: {
|
|
13198
14233
|
duration: { type: "time", default: "260ms", cssProperty: "--kui-duration" },
|
|
13199
14234
|
// The state flip is the start moment: `aria-expanded` goes true and the bars begin to move.
|
|
@@ -13391,8 +14426,10 @@ var kuinetic = (() => {
|
|
|
13391
14426
|
// `'discrete'` alongside `rotate`: the unconditional `[data-kui-fx~='flip-card']` rule pins
|
|
13392
14427
|
// `display: grid` so the two faces stack in one cell instead of flowing as normal block
|
|
13393
14428
|
// siblings — unrelated to `catalog/discrete.ts`'s show/hide use of the same physical property,
|
|
13394
|
-
// but `display` is tracked as one channel regardless of the value written into it.
|
|
13395
|
-
|
|
14429
|
+
// but `display` is tracked as one channel regardless of the value written into it. The subtree
|
|
14430
|
+
// because the host's children are the two faces (`flip-parts.ts` marks them, and injects the
|
|
14431
|
+
// toggle button when none is authored).
|
|
14432
|
+
channels: [CHANNEL.rotate, "discrete", SUBTREE_CHANNEL],
|
|
13396
14433
|
parameters: {
|
|
13397
14434
|
duration: { type: "time", default: "700ms", cssProperty: "--kui-duration" },
|
|
13398
14435
|
// The turn has a start moment — the click, or the pointer arriving — so a delay before it is
|
|
@@ -13530,8 +14567,8 @@ var kuinetic = (() => {
|
|
|
13530
14567
|
function snapTo(projected) {
|
|
13531
14568
|
return Math.round(projected) + 0;
|
|
13532
14569
|
}
|
|
13533
|
-
function wrapPlace(
|
|
13534
|
-
return total > 0 ? (
|
|
14570
|
+
function wrapPlace(place3, total) {
|
|
14571
|
+
return total > 0 ? (place3 % total + total) % total : 0;
|
|
13535
14572
|
}
|
|
13536
14573
|
var KEY_DELTAS = {
|
|
13537
14574
|
ArrowRight: 1,
|
|
@@ -13543,8 +14580,26 @@ var kuinetic = (() => {
|
|
|
13543
14580
|
Home: null,
|
|
13544
14581
|
End: null
|
|
13545
14582
|
};
|
|
13546
|
-
function
|
|
13547
|
-
|
|
14583
|
+
function holdSelection(doc) {
|
|
14584
|
+
let ledger;
|
|
14585
|
+
const release2 = () => {
|
|
14586
|
+
ledger?.restore();
|
|
14587
|
+
ledger = void 0;
|
|
14588
|
+
};
|
|
14589
|
+
return {
|
|
14590
|
+
hold() {
|
|
14591
|
+
release2();
|
|
14592
|
+
doc.getSelection()?.removeAllRanges();
|
|
14593
|
+
ledger = createStyleLedger(doc.documentElement);
|
|
14594
|
+
ledger.claim("user-select");
|
|
14595
|
+
ledger.claim("-webkit-user-select");
|
|
14596
|
+
ledger.set("user-select", "none");
|
|
14597
|
+
ledger.set("-webkit-user-select", "none");
|
|
14598
|
+
},
|
|
14599
|
+
release: release2
|
|
14600
|
+
};
|
|
14601
|
+
}
|
|
14602
|
+
function guardClicks(el, enabled) {
|
|
13548
14603
|
let suppressClick = false;
|
|
13549
14604
|
const onClickCapture = (event) => {
|
|
13550
14605
|
if (!suppressClick) return;
|
|
@@ -13552,19 +14607,49 @@ var kuinetic = (() => {
|
|
|
13552
14607
|
event.stopPropagation();
|
|
13553
14608
|
event.preventDefault();
|
|
13554
14609
|
};
|
|
14610
|
+
const onPressCapture = () => {
|
|
14611
|
+
suppressClick = false;
|
|
14612
|
+
};
|
|
14613
|
+
const onNativeDrag = (event) => {
|
|
14614
|
+
event.preventDefault();
|
|
14615
|
+
};
|
|
14616
|
+
el.addEventListener("click", onClickCapture, true);
|
|
14617
|
+
el.addEventListener("pointerdown", onPressCapture, true);
|
|
14618
|
+
if (enabled) el.addEventListener("dragstart", onNativeDrag);
|
|
14619
|
+
return {
|
|
14620
|
+
arm() {
|
|
14621
|
+
suppressClick = true;
|
|
14622
|
+
},
|
|
14623
|
+
release() {
|
|
14624
|
+
el.removeEventListener("click", onClickCapture, true);
|
|
14625
|
+
el.removeEventListener("pointerdown", onPressCapture, true);
|
|
14626
|
+
el.removeEventListener("dragstart", onNativeDrag);
|
|
14627
|
+
}
|
|
14628
|
+
};
|
|
14629
|
+
}
|
|
14630
|
+
function createRingDrag(request) {
|
|
14631
|
+
const { el, ctx, enabled, travelPx, total, positionOf, moveTo, setDragging } = request;
|
|
14632
|
+
const clicks = guardClicks(el, enabled);
|
|
14633
|
+
let dragFrom = 0;
|
|
14634
|
+
let sign = 1;
|
|
14635
|
+
const selection = holdSelection(el.ownerDocument);
|
|
13555
14636
|
const stopRecognising = enabled ? recognise(
|
|
13556
14637
|
el,
|
|
13557
14638
|
{
|
|
13558
14639
|
onStart() {
|
|
14640
|
+
dragFrom = positionOf();
|
|
14641
|
+
sign = request.travelSign();
|
|
14642
|
+
selection.hold();
|
|
13559
14643
|
setDragging(true);
|
|
13560
14644
|
},
|
|
13561
14645
|
onMove(vector) {
|
|
13562
|
-
moveTo(
|
|
14646
|
+
moveTo(dragFrom - sign * vector.dx / travelPx);
|
|
13563
14647
|
},
|
|
13564
14648
|
onEnd(vector) {
|
|
14649
|
+
selection.release();
|
|
13565
14650
|
setDragging(false);
|
|
13566
|
-
|
|
13567
|
-
const settled = snapTo(projectRelease(positionOf(), vector.vx, travelPx));
|
|
14651
|
+
clicks.arm();
|
|
14652
|
+
const settled = snapTo(projectRelease(positionOf(), sign * vector.vx, travelPx));
|
|
13568
14653
|
moveTo(wrapPlace(settled, total()));
|
|
13569
14654
|
}
|
|
13570
14655
|
},
|
|
@@ -13573,7 +14658,15 @@ var kuinetic = (() => {
|
|
|
13573
14658
|
// threshold than `recognise`'s own default of 4px is what buys that — a ring occupies a
|
|
13574
14659
|
// large slab of a phone screen, and a scroll that begins with a two-pixel horizontal wobble
|
|
13575
14660
|
// should not capture the pointer away from the page.
|
|
13576
|
-
|
|
14661
|
+
//
|
|
14662
|
+
// Capture only once the press becomes a drag (`'drag'`), never on `pointerdown`. Held from
|
|
14663
|
+
// the press, capture retargets the `click` that ends a plain tap at this host instead of the
|
|
14664
|
+
// card under the pointer — found by the `lightbox:true` browser tier, where a real click on
|
|
14665
|
+
// a card arrived as a click on the ring and opened nothing. The same retargeting made every
|
|
14666
|
+
// link and button inside a card dead in a real browser while jsdom (which has no capture)
|
|
14667
|
+
// stayed green. The host never moves under the pointer — its cards do — so the press has
|
|
14668
|
+
// nothing to stay captured for until it is a drag, and a drag's click is swallowed above.
|
|
14669
|
+
{ axis: "x", threshold: DRAG_THRESHOLD_PX, capturePointer: "drag" }
|
|
13577
14670
|
) : () => {
|
|
13578
14671
|
};
|
|
13579
14672
|
if (enabled) ctx.style.set("touch-action", "pan-y");
|
|
@@ -13592,216 +14685,213 @@ var kuinetic = (() => {
|
|
|
13592
14685
|
const grantedFocus = el.getAttribute("tabindex") === null;
|
|
13593
14686
|
if (grantedFocus) el.setAttribute("tabindex", "0");
|
|
13594
14687
|
el.addEventListener("keydown", onKeyDown);
|
|
13595
|
-
el.addEventListener("click", onClickCapture, true);
|
|
13596
14688
|
return () => {
|
|
13597
14689
|
stopRecognising();
|
|
14690
|
+
selection.release();
|
|
13598
14691
|
el.removeEventListener("keydown", onKeyDown);
|
|
13599
|
-
|
|
14692
|
+
clicks.release();
|
|
13600
14693
|
if (grantedFocus) el.removeAttribute("tabindex");
|
|
13601
14694
|
};
|
|
13602
14695
|
}
|
|
13603
14696
|
|
|
13604
|
-
// src/effects/carousel/
|
|
14697
|
+
// src/effects/carousel/deck.ts
|
|
13605
14698
|
var DRAGGING_ATTR = "data-kui-ring-dragging";
|
|
13606
|
-
var
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
["opacity", (value) => value !== "" && Number(value) < 1],
|
|
13612
|
-
["filter", (value) => value !== "" && value !== "none"],
|
|
13613
|
-
["backdrop-filter", (value) => value !== "" && value !== "none"]
|
|
14699
|
+
var SPATIAL_DECK_CHANNELS = [
|
|
14700
|
+
SUBTREE_CHANNEL,
|
|
14701
|
+
attributeChannel("data-kui-step"),
|
|
14702
|
+
attributeChannel(DRAGGING_ATTR),
|
|
14703
|
+
attributeChannel("tabindex")
|
|
13614
14704
|
];
|
|
13615
|
-
|
|
13616
|
-
|
|
13617
|
-
|
|
13618
|
-
|
|
13619
|
-
|
|
13620
|
-
if (found) {
|
|
13621
|
-
ctx.warn(
|
|
13622
|
-
`carousel: an ancestor <${node.localName}> has ${found.property}: ${found.value}, which flattens transform-style: preserve-3d \u2014 the ring will render as flat overlapping cards. Move the ring out of it, or drop that property on the ancestor.`
|
|
13623
|
-
);
|
|
13624
|
-
return;
|
|
13625
|
-
}
|
|
13626
|
-
node = node.parentElement;
|
|
13627
|
-
}
|
|
13628
|
-
}
|
|
13629
|
-
function flatteningDeclaration(node, ctx) {
|
|
13630
|
-
const style = ctx.win.getComputedStyle?.(node);
|
|
13631
|
-
if (!style) return null;
|
|
13632
|
-
for (const [property2, flattens] of FLATTENING) {
|
|
13633
|
-
const value = style.getPropertyValue(property2);
|
|
13634
|
-
if (flattens(value)) return { property: property2, value };
|
|
13635
|
-
}
|
|
13636
|
-
return null;
|
|
13637
|
-
}
|
|
13638
|
-
function ledgersFor(store, node) {
|
|
13639
|
-
let attributes = store.attributes.get(node);
|
|
13640
|
-
if (!attributes) {
|
|
13641
|
-
attributes = createAttributeLedger(node);
|
|
13642
|
-
store.attributes.set(node, attributes);
|
|
13643
|
-
}
|
|
13644
|
-
let styles = store.styles.get(node);
|
|
13645
|
-
if (!styles) {
|
|
13646
|
-
styles = createStyleLedger(node);
|
|
13647
|
-
store.styles.set(node, styles);
|
|
13648
|
-
}
|
|
13649
|
-
return { attributes, styles };
|
|
14705
|
+
var SPINNING_ATTR = "data-kui-ring-spinning";
|
|
14706
|
+
var FACE_ATTR = "data-kui-ring-face";
|
|
14707
|
+
var WRAP_ATTR = "data-kui-ring-wrap";
|
|
14708
|
+
function commitStyle(win, node) {
|
|
14709
|
+
return win.getComputedStyle?.(node).transform;
|
|
13650
14710
|
}
|
|
13651
|
-
function
|
|
13652
|
-
|
|
14711
|
+
function travelSign(liveX, nextX, previousX) {
|
|
14712
|
+
if (nextX !== null) return nextX < liveX ? -1 : 1;
|
|
14713
|
+
if (previousX !== null) return previousX > liveX ? -1 : 1;
|
|
14714
|
+
return 1;
|
|
13653
14715
|
}
|
|
13654
|
-
function
|
|
13655
|
-
|
|
13656
|
-
return wrapped2 > 180 ? wrapped2 - 360 : wrapped2;
|
|
14716
|
+
function offsetWrapped(previous, next, count) {
|
|
14717
|
+
return Math.abs(next - previous) > count / 2;
|
|
13657
14718
|
}
|
|
13658
14719
|
function snapPosition(position, total) {
|
|
13659
14720
|
if (total <= 0) return { step: 0, drift: 0 };
|
|
13660
14721
|
const nearest = Math.round(position);
|
|
13661
14722
|
return { step: (nearest % total + total) % total, drift: position - nearest };
|
|
13662
14723
|
}
|
|
13663
|
-
var
|
|
14724
|
+
var DECK_PARAMETERS = {
|
|
13664
14725
|
duration: { type: "time", default: "620ms", cssProperty: "--kui-duration" },
|
|
13665
14726
|
...TRIGGER_DELAY_PARAM,
|
|
13666
14727
|
ease: { type: "easing", default: "cubic-bezier(0.22, 1, 0.36, 1)", cssProperty: "--kui-ease" },
|
|
14728
|
+
/** Whether a pointer can move the deck by hand. */
|
|
14729
|
+
grab: { type: "keyword", default: "true", cssProperty: "--kui-grab", keywords: ["true", "false"] },
|
|
13667
14730
|
/*
|
|
13668
|
-
*
|
|
13669
|
-
*
|
|
13670
|
-
* A designer specifies fifteen degrees; nobody specifies 0.375 of an unstated maximum. A
|
|
13671
|
-
* normalised knob also bakes taste into a coordinate — the maximum *is* an opinion about how much
|
|
13672
|
-
* tilt is too much, expressed as a unit — and it cannot be typed in the spelling the rest of the
|
|
13673
|
-
* attribute grammar uses (`0.05turn` is a legal angle and means something exact). Legibility is
|
|
13674
|
-
* still bounded, but the bound is applied to the angle, in `carousel.css`, where it is one
|
|
13675
|
-
* `clamp()` an author can see in devtools rather than a hidden rescale.
|
|
14731
|
+
* How far the pointer travels, in pixels, to move the deck one place.
|
|
13676
14732
|
*
|
|
13677
|
-
*
|
|
13678
|
-
* is the
|
|
13679
|
-
*
|
|
13680
|
-
* says, and it should not be spelled `-18deg` because of which way a matrix happens to turn.
|
|
14733
|
+
* A pixels-per-step mapping rather than pixels-per-degree, because a step is the unit everything
|
|
14734
|
+
* else here is in — the index, the offset, the snap — and a degree is not: the same drag would
|
|
14735
|
+
* move a six-item ring one place and a sixty-item ring ten, purely because the spacing changed.
|
|
13681
14736
|
*/
|
|
13682
|
-
|
|
14737
|
+
travel: { type: "number", default: "220", cssProperty: "--kui-travel", finite: true, minimum: 1 },
|
|
14738
|
+
/** Which elements are the slides. Unset means this element's own children. */
|
|
14739
|
+
target: { type: "text", default: "", cssProperty: "--kui-target" },
|
|
14740
|
+
/** Optional controls, resolved exactly as `step-progress` resolves its own. */
|
|
14741
|
+
next: { type: "text", default: "", cssProperty: "--kui-next" },
|
|
14742
|
+
prev: { type: "text", default: "", cssProperty: "--kui-prev" },
|
|
14743
|
+
jump: { type: "text", default: "", cssProperty: "--kui-jump" },
|
|
14744
|
+
/** A control that pauses and resumes `spin:`/`autoplay:`. See `PAUSE_PARAM` (`auto-motion.ts`). */
|
|
14745
|
+
pause: PAUSE_PARAM,
|
|
14746
|
+
/** `hover:none`: a resting pointer no longer pauses `spin:`/`autoplay:`. See `HOVER_PARAM`. */
|
|
14747
|
+
hover: HOVER_PARAM,
|
|
13683
14748
|
/*
|
|
13684
|
-
*
|
|
14749
|
+
* Continuous rotation: how long one full cycle of the deck takes. `0s` is off.
|
|
13685
14750
|
*
|
|
13686
|
-
*
|
|
13687
|
-
*
|
|
13688
|
-
*
|
|
14751
|
+
* A period rather than a speed because a period is what a designer can picture ("one turn every
|
|
14752
|
+
* forty seconds") and it is a `time`, which the attribute grammar already reads, clamps and
|
|
14753
|
+
* documents. A full cycle is every slide passing once — one revolution of a full ring — so adding a
|
|
14754
|
+
* card does not change how fast the ring turns. **Negative runs the other way**: `spin:-40s`. A
|
|
14755
|
+
* signed value rather than a `direction:` keyword so one row can say everything about its motion
|
|
14756
|
+
* in one token, which is what three rows at three speeds on one page want.
|
|
13689
14757
|
*/
|
|
13690
|
-
|
|
14758
|
+
spin: { type: "time", default: "0s", cssProperty: "--kui-spin" },
|
|
13691
14759
|
/*
|
|
13692
|
-
*
|
|
13693
|
-
*
|
|
13694
|
-
* Empty default, not a number, and for the same reason `step-progress`'s `steps` is empty:
|
|
13695
|
-
* `readParams` fills every declared default in unconditionally, so an empty one is the only way
|
|
13696
|
-
* to tell "the author said nothing" from "the author said 320px". Unset, `carousel.css` computes
|
|
13697
|
-
* the circumradius of a regular polygon with this many sides and this much side length —
|
|
13698
|
-
* `(width + gap) / (2 * tan(step / 2))` — which is the arrangement where neighbours just touch.
|
|
13699
|
-
* Every consumer solving that by hand was the alternative, and they would all have solved it
|
|
13700
|
-
* differently.
|
|
13701
|
-
*
|
|
13702
|
-
* The width half of that has to come from JavaScript: CSS can compute with a measurement but
|
|
13703
|
-
* cannot take one. See `measureItemWidth`.
|
|
14760
|
+
* Stepped motion: how long the deck rests on each slide. When both are set `spin:` wins, because a
|
|
14761
|
+
* deck cannot be both gliding and resting. See `AUTOPLAY_PARAM` (`auto-motion.ts`).
|
|
13704
14762
|
*/
|
|
13705
|
-
|
|
13706
|
-
/** Breathing room between neighbours, spent by the derived radius above. */
|
|
13707
|
-
gap: { type: "length", default: "24px", cssProperty: "--kui-gap" },
|
|
13708
|
-
/** Camera distance. Shorter is a wider lens: more foreshortening, more drama, more distortion. */
|
|
13709
|
-
perspective: { type: "length", default: "1600px", cssProperty: "--kui-perspective" },
|
|
14763
|
+
autoplay: AUTOPLAY_PARAM,
|
|
13710
14764
|
/*
|
|
13711
|
-
*
|
|
13712
|
-
*
|
|
13713
|
-
*
|
|
13714
|
-
* `
|
|
13715
|
-
* one faces you. It is what you want for a cover-flow, and it is what makes the far side of the
|
|
13716
|
-
* ring read as the far side.
|
|
13717
|
-
*
|
|
13718
|
-
* `camera` counter-rotates each slot's *child* — never the slot itself; the placement transform
|
|
13719
|
-
* and the billboard have to stay on separate boxes or every later change to one silently
|
|
13720
|
-
* multiplies into the other — so text stays readable at every position, which is what a raised
|
|
13721
|
-
* camera needs. Costs one element of markup per slide; see `docs/catalog.md`.
|
|
14765
|
+
* `lightbox:true`: a click on a card opens every card of this deck in the shared lightbox gallery,
|
|
14766
|
+
* at the card clicked. A drag still only moves the deck — its click is swallowed before it can
|
|
14767
|
+
* reach a card (`drag.ts`). The gallery itself lives in the showcase module; see
|
|
14768
|
+
* `core/deck-viewer.ts` for how a deck reaches it without importing it.
|
|
13722
14769
|
*/
|
|
13723
|
-
|
|
13724
|
-
type: "keyword",
|
|
13725
|
-
default: "radial",
|
|
13726
|
-
cssProperty: "--kui-facing",
|
|
13727
|
-
keywords: ["radial", "camera"]
|
|
13728
|
-
},
|
|
13729
|
-
/** Whether a pointer can spin the ring by hand. */
|
|
13730
|
-
grab: { type: "keyword", default: "true", cssProperty: "--kui-grab", keywords: ["true", "false"] },
|
|
13731
|
-
/*
|
|
13732
|
-
* How far the pointer travels, in pixels, to move the ring one place.
|
|
13733
|
-
*
|
|
13734
|
-
* A pixels-per-step mapping rather than pixels-per-degree, because a step is the unit everything
|
|
13735
|
-
* else here is in — the index, the offset, the snap — and a degree is not: the same drag would
|
|
13736
|
-
* move a six-item ring one place and a sixty-item ring ten, purely because the spacing changed.
|
|
13737
|
-
*/
|
|
13738
|
-
travel: { type: "number", default: "220", cssProperty: "--kui-travel", finite: true, minimum: 1 },
|
|
13739
|
-
/** Which elements sit on the ring. Unset means this element's own children. */
|
|
13740
|
-
target: { type: "text", default: "", cssProperty: "--kui-target" },
|
|
13741
|
-
/** Optional controls, resolved exactly as `step-progress` resolves its own. */
|
|
13742
|
-
next: { type: "text", default: "", cssProperty: "--kui-next" },
|
|
13743
|
-
prev: { type: "text", default: "", cssProperty: "--kui-prev" },
|
|
13744
|
-
jump: { type: "text", default: "", cssProperty: "--kui-jump" },
|
|
14770
|
+
lightbox: DECK_LIGHTBOX_PARAM,
|
|
13745
14771
|
scope: SCOPE_PARAM
|
|
13746
14772
|
};
|
|
13747
|
-
function
|
|
13748
|
-
|
|
13749
|
-
|
|
13750
|
-
|
|
13751
|
-
|
|
13752
|
-
}
|
|
13753
|
-
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
14773
|
+
function ledgerFor(store, node) {
|
|
14774
|
+
let ledger = store.attributes.get(node);
|
|
14775
|
+
if (!ledger) {
|
|
14776
|
+
ledger = createAttributeLedger(node);
|
|
14777
|
+
store.attributes.set(node, ledger);
|
|
14778
|
+
}
|
|
14779
|
+
return ledger;
|
|
14780
|
+
}
|
|
14781
|
+
function measureItem(styles, slots) {
|
|
14782
|
+
const first = slots[0];
|
|
14783
|
+
if (!first) return;
|
|
14784
|
+
if (first.offsetWidth > 0) styles.set("--kui-item-width", `${first.offsetWidth}px`);
|
|
14785
|
+
if (first.offsetHeight > 0) styles.set("--kui-item-height", `${first.offsetHeight}px`);
|
|
14786
|
+
}
|
|
14787
|
+
function prepareSpatialDeck(el, params, ctx, layout) {
|
|
14788
|
+
const { label } = layout;
|
|
14789
|
+
mirrorTimingToCss(layout.id, ALL_TIMING_TOKENS, params, ctx);
|
|
14790
|
+
const selector = resolveTarget(params.text("target"), ctx, label);
|
|
13758
14791
|
const scope = scopeParam(params, "self");
|
|
13759
14792
|
const resolveSlots = () => selector ? queryScoped(el, ctx, selector, scope) : el.children;
|
|
13760
|
-
const marker = createStepMarker(resolveSlots, (message) => ctx.warn(
|
|
14793
|
+
const marker = createStepMarker(resolveSlots, (message) => ctx.warn(`${label} ${message}`));
|
|
13761
14794
|
const total = () => countSteps(params, resolveSlots);
|
|
13762
14795
|
const hostAttributes = createAttributeLedger(el);
|
|
13763
14796
|
const hostStyles = createStyleLedger(el);
|
|
13764
|
-
|
|
13765
|
-
const slots = { attributes: /* @__PURE__ */ new Map()
|
|
13766
|
-
const
|
|
14797
|
+
for (const [name, value] of Object.entries(layout.hostAttributes ?? {})) hostAttributes.set(name, value);
|
|
14798
|
+
const slots = { attributes: /* @__PURE__ */ new Map() };
|
|
14799
|
+
const attachment = layout.attach?.(hostStyles);
|
|
13767
14800
|
let position = 0;
|
|
13768
|
-
|
|
14801
|
+
let rendered = { step: -1, count: 1, nodes: [] };
|
|
14802
|
+
const lastOffsets = /* @__PURE__ */ new WeakMap();
|
|
14803
|
+
const settleWrapped = (nodes, count) => {
|
|
14804
|
+
for (const node of nodes) {
|
|
14805
|
+
const raw = Number(node.getAttribute("data-kui-step-offset"));
|
|
14806
|
+
const offset = layout.placeOf ? layout.placeOf(raw, count) : raw;
|
|
14807
|
+
const previous = lastOffsets.get(node);
|
|
14808
|
+
lastOffsets.set(node, offset);
|
|
14809
|
+
if (previous === void 0 || !offsetWrapped(previous, offset, count)) continue;
|
|
14810
|
+
const flag = createAttributeLedger(node);
|
|
14811
|
+
flag.set(WRAP_ATTR, "true");
|
|
14812
|
+
commitStyle(ctx.win, node);
|
|
14813
|
+
flag.restore();
|
|
14814
|
+
}
|
|
14815
|
+
};
|
|
14816
|
+
const markFaces = (nodes, count, drift2) => {
|
|
14817
|
+
for (const node of nodes) {
|
|
14818
|
+
const offset = Number(node.getAttribute("data-kui-step-offset"));
|
|
14819
|
+
const face = layout.faceOf(offset - drift2, offset, count);
|
|
14820
|
+
if (node.getAttribute(FACE_ATTR) !== face) ledgerFor(slots, node).set(FACE_ATTR, face);
|
|
14821
|
+
}
|
|
14822
|
+
};
|
|
14823
|
+
const publishPosition = (step, drift2) => {
|
|
14824
|
+
hostStyles.set("--kui-step-position", (step + drift2).toFixed(4));
|
|
14825
|
+
};
|
|
13769
14826
|
const render = () => {
|
|
13770
14827
|
const count = total();
|
|
13771
14828
|
const { step, drift: drift2 } = snapPosition(position, count);
|
|
13772
14829
|
hostAttributes.set("data-kui-step", String(step));
|
|
13773
14830
|
hostStyles.set("--kui-step", String(step));
|
|
13774
|
-
hostStyles.set("--kui-
|
|
14831
|
+
hostStyles.set("--kui-item-count", String(count));
|
|
14832
|
+
publishPosition(step, drift2);
|
|
13775
14833
|
marker.mark(step);
|
|
13776
|
-
const
|
|
13777
|
-
if (
|
|
13778
|
-
|
|
13779
|
-
|
|
13780
|
-
|
|
13781
|
-
|
|
13782
|
-
|
|
14834
|
+
const nodes = [...resolveSlots()];
|
|
14835
|
+
if (layout.measure) measureItem(hostStyles, nodes);
|
|
14836
|
+
markFaces(nodes, count, drift2);
|
|
14837
|
+
settleWrapped(nodes, count);
|
|
14838
|
+
attachment?.render(nodes, count);
|
|
14839
|
+
rendered = { step, count, nodes };
|
|
14840
|
+
};
|
|
14841
|
+
const renderFrame = () => {
|
|
14842
|
+
const { step, drift: drift2 } = snapPosition(position, rendered.count);
|
|
14843
|
+
if (step !== rendered.step) {
|
|
14844
|
+
render();
|
|
14845
|
+
return;
|
|
13783
14846
|
}
|
|
14847
|
+
publishPosition(step, drift2);
|
|
14848
|
+
markFaces(rendered.nodes, rendered.count, drift2);
|
|
13784
14849
|
};
|
|
14850
|
+
const { motion, pause, release: releaseMotion } = createDeckMotion({
|
|
14851
|
+
el,
|
|
14852
|
+
ctx,
|
|
14853
|
+
params,
|
|
14854
|
+
label,
|
|
14855
|
+
settleMs: effectDurationMs(params, 620),
|
|
14856
|
+
step(direction) {
|
|
14857
|
+
const count = total();
|
|
14858
|
+
const { step } = snapPosition(position, count);
|
|
14859
|
+
position = direction > 0 ? nextStep(step, count) : prevStep(step, count);
|
|
14860
|
+
render();
|
|
14861
|
+
},
|
|
14862
|
+
spin: {
|
|
14863
|
+
advance(cycles) {
|
|
14864
|
+
position = wrapPlace(position + cycles * rendered.count, rendered.count);
|
|
14865
|
+
renderFrame();
|
|
14866
|
+
},
|
|
14867
|
+
setSpinning: (spinning) => hostAttributes.set(SPINNING_ATTR, String(spinning))
|
|
14868
|
+
}
|
|
14869
|
+
});
|
|
13785
14870
|
const goTo = (next) => {
|
|
14871
|
+
motion.interrupt();
|
|
13786
14872
|
position = next;
|
|
13787
14873
|
render();
|
|
13788
14874
|
};
|
|
13789
|
-
const
|
|
13790
|
-
const
|
|
13791
|
-
|
|
13792
|
-
|
|
13793
|
-
|
|
13794
|
-
|
|
14875
|
+
const request = { el, ctx, params, label };
|
|
14876
|
+
const groups = [
|
|
14877
|
+
bindControl(request, "next", () => goTo(nextStep(snapPosition(position, total()).step, total()))),
|
|
14878
|
+
bindControl(request, "prev", () => goTo(prevStep(snapPosition(position, total()).step, total()))),
|
|
14879
|
+
bindControl(request, "jump", (_node, at) => {
|
|
14880
|
+
if (at >= 0) goTo(at);
|
|
14881
|
+
}),
|
|
14882
|
+
pause
|
|
14883
|
+
].filter((group) => group !== null);
|
|
14884
|
+
const releaseControls = delegateControls({ el, ctx, groups });
|
|
14885
|
+
const screenTravel = () => {
|
|
14886
|
+
const centres = /* @__PURE__ */ new Map();
|
|
14887
|
+
for (const node of rendered.nodes) {
|
|
14888
|
+
const offset = Number(node.getAttribute("data-kui-step-offset"));
|
|
14889
|
+
if (Math.abs(offset) > 1) continue;
|
|
14890
|
+
const box = node.getBoundingClientRect();
|
|
14891
|
+
centres.set(offset, box.left + box.width / 2);
|
|
13795
14892
|
}
|
|
13796
|
-
|
|
13797
|
-
return true;
|
|
14893
|
+
return travelSign(centres.get(0) ?? 0, centres.get(1) ?? null, centres.get(-1) ?? null);
|
|
13798
14894
|
};
|
|
13799
|
-
bindControl("next", () => goTo(nextStep(snapPosition(position, total()).step, total())));
|
|
13800
|
-
bindControl("prev", () => goTo(prevStep(snapPosition(position, total()).step, total())));
|
|
13801
|
-
bindControl("jump", (_node, at) => {
|
|
13802
|
-
if (at >= 0) goTo(at);
|
|
13803
|
-
});
|
|
13804
|
-
const releaseControls = delegateControls({ el, ctx, scope, groups });
|
|
13805
14895
|
const releaseDrag = createRingDrag({
|
|
13806
14896
|
el,
|
|
13807
14897
|
ctx,
|
|
@@ -13809,22 +14899,405 @@ var kuinetic = (() => {
|
|
|
13809
14899
|
travelPx: params.num("travel", 220),
|
|
13810
14900
|
total,
|
|
13811
14901
|
positionOf: () => position,
|
|
14902
|
+
travelSign: screenTravel,
|
|
13812
14903
|
moveTo: goTo,
|
|
13813
|
-
setDragging: (dragging) =>
|
|
14904
|
+
setDragging: (dragging) => {
|
|
14905
|
+
hostAttributes.set(DRAGGING_ATTR, String(dragging));
|
|
14906
|
+
motion.hold(dragging);
|
|
14907
|
+
}
|
|
13814
14908
|
});
|
|
14909
|
+
const releaseViewer = deckLightbox(
|
|
14910
|
+
params.is("lightbox"),
|
|
14911
|
+
{ host: el, cards: resolveSlots, doc: ctx.doc, reducedMotion: ctx.reducedMotion },
|
|
14912
|
+
(message) => ctx.warn(`${label} ${message}`)
|
|
14913
|
+
);
|
|
13815
14914
|
render();
|
|
13816
14915
|
return () => {
|
|
14916
|
+
releaseViewer();
|
|
14917
|
+
releaseMotion();
|
|
13817
14918
|
releaseDrag();
|
|
13818
14919
|
releaseControls();
|
|
13819
14920
|
marker.restore();
|
|
13820
14921
|
for (const ledger of slots.attributes.values()) ledger.restore();
|
|
13821
|
-
for (const ledger of slots.styles.values()) ledger.restore();
|
|
13822
14922
|
slots.attributes.clear();
|
|
13823
|
-
|
|
14923
|
+
attachment?.release();
|
|
13824
14924
|
hostAttributes.restore();
|
|
13825
14925
|
hostStyles.restore();
|
|
13826
14926
|
};
|
|
13827
14927
|
}
|
|
14928
|
+
|
|
14929
|
+
// src/effects/carousel/fit.ts
|
|
14930
|
+
var RING_FIT_PROPERTY = "--kui-ring-fit";
|
|
14931
|
+
var SAMPLE_DEG = 0.25;
|
|
14932
|
+
var SEARCH_CEILING_PX = 1e5;
|
|
14933
|
+
var SEARCH_STEPS = 34;
|
|
14934
|
+
var RAD = Math.PI / 180;
|
|
14935
|
+
function reachableAngles(arcDeg, count) {
|
|
14936
|
+
if (Math.abs(arcDeg) >= 360) return { fromDeg: -180, toDeg: 180 };
|
|
14937
|
+
const places = Math.max(1, count);
|
|
14938
|
+
const spacing = arcDeg / places;
|
|
14939
|
+
const high = Math.floor(places / 2);
|
|
14940
|
+
const low = high - places + 1;
|
|
14941
|
+
const ends = [(low - 0.5) * spacing, (high + 0.5) * spacing];
|
|
14942
|
+
return { fromDeg: Math.min(...ends), toDeg: Math.max(...ends) };
|
|
14943
|
+
}
|
|
14944
|
+
function place2(point, { sinA, cosA, sinT, cosT }) {
|
|
14945
|
+
const [x, y, z] = point;
|
|
14946
|
+
const x1 = x * cosA + z * sinA;
|
|
14947
|
+
const z1 = -x * sinA + z * cosA;
|
|
14948
|
+
return [x1, y * cosT - z1 * sinT, y * sinT + z1 * cosT];
|
|
14949
|
+
}
|
|
14950
|
+
function cardFits(g, r, rotation) {
|
|
14951
|
+
const z0 = (g.inside ? -1 : 1) * r;
|
|
14952
|
+
const centre = place2([0, 0, z0], rotation);
|
|
14953
|
+
const hw = g.width / 2;
|
|
14954
|
+
const hh = g.height / 2;
|
|
14955
|
+
for (const [cx, cy] of [[-hw, -hh], [hw, -hh], [-hw, hh], [hw, hh]]) {
|
|
14956
|
+
const [x, , z] = g.facing === "camera" ? [centre[0] + cx, centre[1] + cy, centre[2]] : place2([cx, cy, z0], rotation);
|
|
14957
|
+
if (z >= g.perspective - 1) return false;
|
|
14958
|
+
const scale = Number.isFinite(g.perspective) ? g.perspective / (g.perspective - z) : 1;
|
|
14959
|
+
const screen2 = (g.originOffset + x) * scale;
|
|
14960
|
+
if (screen2 < g.minX || screen2 > g.maxX) return false;
|
|
14961
|
+
}
|
|
14962
|
+
return true;
|
|
14963
|
+
}
|
|
14964
|
+
function fitsAt(g, r, angles) {
|
|
14965
|
+
const sinT = Math.sin(g.tiltDeg * RAD);
|
|
14966
|
+
const cosT = Math.cos(g.tiltDeg * RAD);
|
|
14967
|
+
return angles.every((angle) => cardFits(g, r, { sinA: Math.sin(angle * RAD), cosA: Math.cos(angle * RAD), sinT, cosT }));
|
|
14968
|
+
}
|
|
14969
|
+
function sampleAngles(g) {
|
|
14970
|
+
const from = g.inside ? Math.max(g.fromDeg, -90) : g.fromDeg;
|
|
14971
|
+
const to = g.inside ? Math.min(g.toDeg, 90) : g.toDeg;
|
|
14972
|
+
const steps = Math.max(1, Math.ceil((to - from) / SAMPLE_DEG));
|
|
14973
|
+
return Array.from({ length: steps + 1 }, (_, i) => from + (to - from) * i / steps);
|
|
14974
|
+
}
|
|
14975
|
+
function solveRingFit(g) {
|
|
14976
|
+
const angles = sampleAngles(g);
|
|
14977
|
+
if (!fitsAt(g, 0, angles)) return 0;
|
|
14978
|
+
if (fitsAt(g, SEARCH_CEILING_PX, angles)) return Infinity;
|
|
14979
|
+
let low = 0;
|
|
14980
|
+
let high = SEARCH_CEILING_PX;
|
|
14981
|
+
for (let i = 0; i < SEARCH_STEPS; i++) {
|
|
14982
|
+
const mid = (low + high) / 2;
|
|
14983
|
+
if (fitsAt(g, mid, angles)) low = mid;
|
|
14984
|
+
else high = mid;
|
|
14985
|
+
}
|
|
14986
|
+
return Math.floor(low * 100) / 100;
|
|
14987
|
+
}
|
|
14988
|
+
function fitBounds(el, win) {
|
|
14989
|
+
const doc = el.ownerDocument;
|
|
14990
|
+
for (let node = el.parentElement; node && node !== doc.body && node !== doc.documentElement; node = node.parentElement) {
|
|
14991
|
+
const overflow = win.getComputedStyle(node).overflowX;
|
|
14992
|
+
if (overflow === "clip" || overflow === "hidden") return null;
|
|
14993
|
+
if (overflow === "auto" || overflow === "scroll") {
|
|
14994
|
+
const left2 = node.getBoundingClientRect().left + node.clientLeft;
|
|
14995
|
+
return { left: left2, right: left2 + node.clientWidth };
|
|
14996
|
+
}
|
|
14997
|
+
}
|
|
14998
|
+
const left = -win.scrollX;
|
|
14999
|
+
return { left, right: left + doc.documentElement.clientWidth };
|
|
15000
|
+
}
|
|
15001
|
+
function pixels(value, fallback) {
|
|
15002
|
+
const parsed = Number.parseFloat(value);
|
|
15003
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
15004
|
+
}
|
|
15005
|
+
function createRingFit(options) {
|
|
15006
|
+
const { el, ctx, styles } = options;
|
|
15007
|
+
if (typeof ctx.win.getComputedStyle !== "function" || typeof ctx.win.addEventListener !== "function") {
|
|
15008
|
+
return { render() {
|
|
15009
|
+
}, release() {
|
|
15010
|
+
} };
|
|
15011
|
+
}
|
|
15012
|
+
const tiltDeg = Math.min(80, Math.max(-80, -options.tiltParamDeg));
|
|
15013
|
+
let card;
|
|
15014
|
+
let count = 1;
|
|
15015
|
+
const solve = () => {
|
|
15016
|
+
const bounds = fitBounds(el, ctx.win);
|
|
15017
|
+
if (!bounds || !card || card.offsetWidth === 0) {
|
|
15018
|
+
styles.set(RING_FIT_PROPERTY, "");
|
|
15019
|
+
return;
|
|
15020
|
+
}
|
|
15021
|
+
const style = ctx.win.getComputedStyle(el);
|
|
15022
|
+
const host = el.getBoundingClientRect();
|
|
15023
|
+
const originX = host.left + pixels(style.perspectiveOrigin, host.width / 2);
|
|
15024
|
+
const fit = solveRingFit({
|
|
15025
|
+
width: card.offsetWidth,
|
|
15026
|
+
height: card.offsetHeight,
|
|
15027
|
+
perspective: pixels(style.perspective, Infinity),
|
|
15028
|
+
tiltDeg,
|
|
15029
|
+
// The stylesheet's own selector for the concave placement, read rather than threaded through
|
|
15030
|
+
// as a parameter: the name is what `carousel.css` branches on, so it is what this must too.
|
|
15031
|
+
inside: el.matches("[data-kui-fx~='carousel-3d-inside']"),
|
|
15032
|
+
// `facing:camera` counter-rotates the slot's element *children* (`carousel.css`); a slot with
|
|
15033
|
+
// none — bare text — is drawn at its radial angle, and is fitted as the radial card it is.
|
|
15034
|
+
facing: card.firstElementChild ? options.facing : "radial",
|
|
15035
|
+
...reachableAngles(options.arcDeg, count),
|
|
15036
|
+
originOffset: host.left + host.width / 2 - originX,
|
|
15037
|
+
minX: bounds.left - originX,
|
|
15038
|
+
maxX: bounds.right - originX
|
|
15039
|
+
});
|
|
15040
|
+
styles.set(RING_FIT_PROPERTY, Number.isFinite(fit) ? `${fit}px` : "");
|
|
15041
|
+
};
|
|
15042
|
+
const frame = frameScheduler(ctx.win, solve);
|
|
15043
|
+
const watch = watchElementSize(ctx.win, frame.request);
|
|
15044
|
+
watch.observe(el);
|
|
15045
|
+
watch.observe(el.ownerDocument.documentElement);
|
|
15046
|
+
return {
|
|
15047
|
+
render(nodes, total) {
|
|
15048
|
+
const first = nodes[0];
|
|
15049
|
+
if (first === card && total === count) return;
|
|
15050
|
+
card = first;
|
|
15051
|
+
count = total;
|
|
15052
|
+
solve();
|
|
15053
|
+
},
|
|
15054
|
+
release() {
|
|
15055
|
+
watch.disconnect();
|
|
15056
|
+
frame.cancel();
|
|
15057
|
+
}
|
|
15058
|
+
};
|
|
15059
|
+
}
|
|
15060
|
+
|
|
15061
|
+
// src/effects/carousel/stack.ts
|
|
15062
|
+
function stackFace(offset, drift2, count, depth) {
|
|
15063
|
+
const ahead = stackPlace(offset, count) - drift2;
|
|
15064
|
+
return ahead > -0.5 && ahead < depth + 1 ? "front" : "back";
|
|
15065
|
+
}
|
|
15066
|
+
function stackPlace(offset, count) {
|
|
15067
|
+
return wrapPlace(offset + 1, count) - 1;
|
|
15068
|
+
}
|
|
15069
|
+
var STACK_PARAMETERS = {
|
|
15070
|
+
...DECK_PARAMETERS,
|
|
15071
|
+
/*
|
|
15072
|
+
* How far each card behind the front one steps sideways. Positive is to the right, negative to
|
|
15073
|
+
* the left. A length rather than a percentage because the host reserves `depth × shift` of room
|
|
15074
|
+
* on that side (cards moved by a transform take no layout space) and a padding percentage would
|
|
15075
|
+
* resolve against a different box than a translate percentage does.
|
|
15076
|
+
*/
|
|
15077
|
+
shift: { type: "length", default: "48px", cssProperty: "--kui-stack-shift" },
|
|
15078
|
+
/** How far each card behind steps upward. Negative recedes downward instead. */
|
|
15079
|
+
rise: { type: "length", default: "36px", cssProperty: "--kui-stack-rise" },
|
|
15080
|
+
/** Scale lost per place behind the front: `0.08` makes the third card back 76% size. */
|
|
15081
|
+
shrink: { type: "number", default: "0.08", cssProperty: "--kui-stack-shrink", finite: true, minimum: 0, maximum: 0.3 },
|
|
15082
|
+
/** Blur added per place behind the front. The front card is always sharp. */
|
|
15083
|
+
blur: { type: "length", default: "2px", cssProperty: "--kui-stack-blur" },
|
|
15084
|
+
/** Opacity lost per place behind the front. */
|
|
15085
|
+
fade: { type: "number", default: "0.2", cssProperty: "--kui-stack-fade", finite: true, minimum: 0, maximum: 1 },
|
|
15086
|
+
/*
|
|
15087
|
+
* How many cards show behind the front one. The rest wait, hidden, at the back — `visibility`,
|
|
15088
|
+
* not only `opacity`, so a card nobody can see is also not a tab stop or a click target.
|
|
15089
|
+
*/
|
|
15090
|
+
depth: { type: "number", default: "3", cssProperty: "--kui-stack-depth", integer: true, minimum: 1, maximum: 8 }
|
|
15091
|
+
};
|
|
15092
|
+
function prepareSpatialStack(el, params, ctx) {
|
|
15093
|
+
const depth = params.num("depth", 3);
|
|
15094
|
+
return prepareSpatialDeck(el, params, ctx, {
|
|
15095
|
+
id: "spatial-stack",
|
|
15096
|
+
label: "carousel-stack",
|
|
15097
|
+
faceOf: (place3, offset, count) => stackFace(offset, offset - place3, count, depth),
|
|
15098
|
+
placeOf: stackPlace,
|
|
15099
|
+
measure: false
|
|
15100
|
+
});
|
|
15101
|
+
}
|
|
15102
|
+
var SPATIAL_STACK_PRIMITIVE = {
|
|
15103
|
+
id: "spatial-stack",
|
|
15104
|
+
renderer: "javascript",
|
|
15105
|
+
// What `deck.ts` writes on the host: the cards (the subtree), the step index, the drag.
|
|
15106
|
+
channels: [CHANNEL.skew, "discrete", CHANNEL.opacity, CHANNEL.filter, ...SPATIAL_DECK_CHANNELS],
|
|
15107
|
+
parameters: STACK_PARAMETERS,
|
|
15108
|
+
supportedTimelines: ["time"],
|
|
15109
|
+
supportedActivations: ["load", "enter", "click", "manual"],
|
|
15110
|
+
defaultActivation: "load",
|
|
15111
|
+
perfClass: "compositor",
|
|
15112
|
+
reducedMotion: "shorten",
|
|
15113
|
+
prepare: deferPrepare(prepareSpatialStack)
|
|
15114
|
+
};
|
|
15115
|
+
var STACK_PRESETS = [
|
|
15116
|
+
{
|
|
15117
|
+
name: "carousel-stack",
|
|
15118
|
+
primitive: "spatial-stack",
|
|
15119
|
+
requiresOwnSubtree: true,
|
|
15120
|
+
phase: "idle"
|
|
15121
|
+
}
|
|
15122
|
+
];
|
|
15123
|
+
|
|
15124
|
+
// src/effects/carousel/index.ts
|
|
15125
|
+
var QUARTER_TURN_DEG = 90;
|
|
15126
|
+
var FLATTENING = [
|
|
15127
|
+
["overflow", (value) => value !== "" && value !== "visible"],
|
|
15128
|
+
["clip-path", (value) => value !== "" && value !== "none"],
|
|
15129
|
+
["opacity", (value) => value !== "" && Number(value) < 1],
|
|
15130
|
+
["filter", (value) => value !== "" && value !== "none"],
|
|
15131
|
+
["backdrop-filter", (value) => value !== "" && value !== "none"],
|
|
15132
|
+
["isolation", (value) => value === "isolate"],
|
|
15133
|
+
["mix-blend-mode", (value) => value !== "" && value !== "normal"],
|
|
15134
|
+
["mask-image", (value) => value !== "" && value !== "none"]
|
|
15135
|
+
];
|
|
15136
|
+
function warnFlattenedHost(el, ctx) {
|
|
15137
|
+
const found = flatteningDeclaration(el, ctx);
|
|
15138
|
+
if (!found) return;
|
|
15139
|
+
ctx.warn(
|
|
15140
|
+
`carousel: the ring's own <${el.localName}> has ${found.property}: ${found.value}, which flattens transform-style: preserve-3d \u2014 the ring will render as flat overlapping cards. Move that property to a wrapper around the ring: an ancestor does not flatten it.`
|
|
15141
|
+
);
|
|
15142
|
+
}
|
|
15143
|
+
function flatteningDeclaration(node, ctx) {
|
|
15144
|
+
const style = ctx.win.getComputedStyle?.(node);
|
|
15145
|
+
if (!style) return null;
|
|
15146
|
+
for (const [property2, flattens] of FLATTENING) {
|
|
15147
|
+
const value = style.getPropertyValue(property2);
|
|
15148
|
+
if (flattens(value)) return { property: property2, value };
|
|
15149
|
+
}
|
|
15150
|
+
return null;
|
|
15151
|
+
}
|
|
15152
|
+
function faceAt(angleDeg) {
|
|
15153
|
+
return Math.abs(normaliseDegrees(angleDeg)) >= QUARTER_TURN_DEG ? "back" : "front";
|
|
15154
|
+
}
|
|
15155
|
+
function normaliseDegrees(angleDeg) {
|
|
15156
|
+
const wrapped2 = (angleDeg % 360 + 360) % 360;
|
|
15157
|
+
return wrapped2 > 180 ? wrapped2 - 360 : wrapped2;
|
|
15158
|
+
}
|
|
15159
|
+
var RING_PARAMETERS = {
|
|
15160
|
+
...DECK_PARAMETERS,
|
|
15161
|
+
/*
|
|
15162
|
+
* A real CSS `<angle>`, not a normalised scalar.
|
|
15163
|
+
*
|
|
15164
|
+
* A designer specifies fifteen degrees; nobody specifies 0.375 of an unstated maximum. A
|
|
15165
|
+
* normalised knob also bakes taste into a coordinate — the maximum *is* an opinion about how much
|
|
15166
|
+
* tilt is too much, expressed as a unit — and it cannot be typed in the spelling the rest of the
|
|
15167
|
+
* attribute grammar uses (`0.05turn` is a legal angle and means something exact). Legibility is
|
|
15168
|
+
* still bounded, but the bound is applied to the angle, in `carousel.css`, where it is one
|
|
15169
|
+
* `clamp()` an author can see in devtools rather than a hidden rescale.
|
|
15170
|
+
*
|
|
15171
|
+
* Sign convention: **positive tilts the camera up and over the ring**, looking down at it. That
|
|
15172
|
+
* is the opposite sign from the `rotateX()` it compiles to — see `carousel.css` for the
|
|
15173
|
+
* derivation — and the inversion is deliberate: "18 degrees above" is the sentence a designer
|
|
15174
|
+
* says, and it should not be spelled `-18deg` because of which way a matrix happens to turn.
|
|
15175
|
+
*/
|
|
15176
|
+
tilt: { type: "angle", default: "0deg", cssProperty: "--kui-tilt" },
|
|
15177
|
+
/*
|
|
15178
|
+
* How far the ring spreads. `360deg` is a full ring; anything less is a slice.
|
|
15179
|
+
*
|
|
15180
|
+
* This is the parameter the concave name exists around. From the centre of a *full* ring most of
|
|
15181
|
+
* it is behind your head, so a 360-degree spread there is not a design choice but a way of hiding
|
|
15182
|
+
* two thirds of the content — the inside name defaults to a slice for that reason.
|
|
15183
|
+
*/
|
|
15184
|
+
arc: { type: "angle", default: "360deg", cssProperty: "--kui-arc" },
|
|
15185
|
+
/*
|
|
15186
|
+
* The ring's radius, or unset for one derived from the content.
|
|
15187
|
+
*
|
|
15188
|
+
* Empty default, not a number, and for the same reason `step-progress`'s `steps` is empty:
|
|
15189
|
+
* `readParams` fills every declared default in unconditionally, so an empty one is the only way
|
|
15190
|
+
* to tell "the author said nothing" from "the author said 320px". Unset, `carousel.css` computes
|
|
15191
|
+
* the circumradius of a regular polygon with this many sides and this much side length —
|
|
15192
|
+
* `(width + gap) / (2 * tan(step / 2))` — which is the arrangement where neighbours just touch.
|
|
15193
|
+
* Every consumer solving that by hand was the alternative, and they would all have solved it
|
|
15194
|
+
* differently.
|
|
15195
|
+
*
|
|
15196
|
+
* The width half of that has to come from JavaScript: CSS can compute with a measurement but
|
|
15197
|
+
* cannot take one. See `measureItem` in `deck.ts`.
|
|
15198
|
+
*
|
|
15199
|
+
* Authored or derived, a depth ring's radius is a ceiling: a ring that would push its page
|
|
15200
|
+
* sideways is pulled in exactly as far as it has to be (`fit.ts`, `--kui-ring-fit`), and one that
|
|
15201
|
+
* already fits — or sits inside an ancestor that clips it — keeps this radius to the pixel.
|
|
15202
|
+
*/
|
|
15203
|
+
radius: { type: "length", default: "", cssProperty: "--kui-radius" },
|
|
15204
|
+
/** Breathing room between neighbours, spent by the derived radius above. */
|
|
15205
|
+
gap: { type: "length", default: "24px", cssProperty: "--kui-gap" },
|
|
15206
|
+
/** Camera distance. Shorter is a wider lens: more foreshortening, more drama, more distortion. */
|
|
15207
|
+
perspective: { type: "length", default: "1600px", cssProperty: "--kui-perspective" },
|
|
15208
|
+
/*
|
|
15209
|
+
* Whether a slot keeps its own outward orientation, or turns to keep facing the viewer.
|
|
15210
|
+
*
|
|
15211
|
+
* `radial` is the ring's natural state and needs nothing: a slot placed by
|
|
15212
|
+
* `rotateY(θ) translateZ(R)` already faces outward along its own radius, which is why the live
|
|
15213
|
+
* one faces you. It is what you want for a cover-flow, and it is what makes the far side of the
|
|
15214
|
+
* ring read as the far side.
|
|
15215
|
+
*
|
|
15216
|
+
* `camera` counter-rotates each slot's *child* — never the slot itself; the placement transform
|
|
15217
|
+
* and the billboard have to stay on separate boxes or every later change to one silently
|
|
15218
|
+
* multiplies into the other — so text stays readable at every position, which is what a raised
|
|
15219
|
+
* camera needs. Costs one element of markup per slide; see `docs/catalog.md`.
|
|
15220
|
+
*/
|
|
15221
|
+
facing: {
|
|
15222
|
+
type: "keyword",
|
|
15223
|
+
default: "radial",
|
|
15224
|
+
cssProperty: "--kui-facing",
|
|
15225
|
+
keywords: ["radial", "camera"]
|
|
15226
|
+
},
|
|
15227
|
+
/*
|
|
15228
|
+
* Which plane the ring lies in: `depth` (the default — a carousel you look *at*, turning about a
|
|
15229
|
+
* vertical pole) or `screen` (a clock face, turning about the axis that points at you).
|
|
15230
|
+
*
|
|
15231
|
+
* ## Why a parameter of this primitive, and not a new one
|
|
15232
|
+
*
|
|
15233
|
+
* A flat ring of cards around a centred word is the same object as the 3D ring seen from its
|
|
15234
|
+
* pole: N slots, evenly spaced by `arc / count`, placed from `--kui-offset` and the shared drift,
|
|
15235
|
+
* advanced by the same drag, keys, controls and `spin:`. Every number this primitive publishes
|
|
15236
|
+
* means the same thing in both planes; only the three transform functions that turn a place into
|
|
15237
|
+
* a position differ, and those live in `carousel.css` anyway. A second primitive would have
|
|
15238
|
+
* duplicated the whole index to change one stylesheet rule and one face test.
|
|
15239
|
+
*
|
|
15240
|
+
* It is a parameter rather than only a name (unlike convex/concave, which differ in every
|
|
15241
|
+
* default and so earned two names) because the runtime has to know it: a flat slot is never
|
|
15242
|
+
* turned away from the viewer, so the quarter-turn face test that stops the back of a 3D ring
|
|
15243
|
+
* swallowing clicks would, on a clock face, disable every card in the bottom half. The name that
|
|
15244
|
+
* ships it, `carousel-orbit`, sets it; the stylesheet gates on the published attribute rather
|
|
15245
|
+
* than the name, so `carousel-3d plane:screen` is equally honoured — same rule as `facing:`.
|
|
15246
|
+
*
|
|
15247
|
+
* `facing:` keeps its meaning here. `radial` turns each card with the ring, top edge outward like
|
|
15248
|
+
* the numerals on a watch bezel; `camera` keeps every card upright. In the screen plane upright
|
|
15249
|
+
* needs no child counter-rotation at all — the slot is *translated* to its place on the circle
|
|
15250
|
+
* instead of rotated there — so it costs no extra markup, unlike the 3D billboard.
|
|
15251
|
+
*/
|
|
15252
|
+
plane: {
|
|
15253
|
+
type: "keyword",
|
|
15254
|
+
default: "depth",
|
|
15255
|
+
cssProperty: "--kui-ring-plane",
|
|
15256
|
+
keywords: ["depth", "screen"]
|
|
15257
|
+
}
|
|
15258
|
+
};
|
|
15259
|
+
function prepareSpatialRing(el, params, ctx) {
|
|
15260
|
+
const plane = params.text("plane", "depth");
|
|
15261
|
+
if (plane === "depth") warnFlattenedHost(el, ctx);
|
|
15262
|
+
const arcDeg = degreesOf(params.text("arc", "360deg"), 360);
|
|
15263
|
+
const faceOf = (place3, _offset, count) => plane === "screen" ? "front" : faceAt(place3 * (arcDeg / count));
|
|
15264
|
+
return prepareSpatialDeck(el, params, ctx, {
|
|
15265
|
+
id: "spatial-ring",
|
|
15266
|
+
label: "carousel",
|
|
15267
|
+
faceOf,
|
|
15268
|
+
/*
|
|
15269
|
+
* A derived radius needs a card width; a flat ring also needs a card's size to reserve its own
|
|
15270
|
+
* circle (the host is sized to it, since transformed slots take no layout space), so it measures
|
|
15271
|
+
* even when the author named a radius.
|
|
15272
|
+
*/
|
|
15273
|
+
measure: params.text("radius") === "" || plane === "screen",
|
|
15274
|
+
hostAttributes: {
|
|
15275
|
+
/*
|
|
15276
|
+
* `facing:` and `plane:` reach CSS as attributes, not only as the custom properties their
|
|
15277
|
+
* specs declare. A stylesheet cannot select on the *value* of a custom property — `@container
|
|
15278
|
+
* style(--kui-facing: camera)` can, and is not supported widely enough to hang a documented
|
|
15279
|
+
* parameter on — so the one thing a selector needs is published in the one form a selector
|
|
15280
|
+
* can read. Same pairing, for the same reason, as the number/attribute pair `step-marking.ts`
|
|
15281
|
+
* publishes for the ring place.
|
|
15282
|
+
*/
|
|
15283
|
+
"data-kui-ring-facing": params.text("facing", "radial"),
|
|
15284
|
+
"data-kui-ring-plane": plane
|
|
15285
|
+
},
|
|
15286
|
+
/*
|
|
15287
|
+
* The fit to the page (`fit.ts`). Depth plane only: a flat ring already reserves and clamps to
|
|
15288
|
+
* its own square (`carousel.css`, `[data-kui-ring-plane='screen']`), and its cards never leave
|
|
15289
|
+
* the plane that square is measured in.
|
|
15290
|
+
*/
|
|
15291
|
+
attach: plane === "depth" ? (styles) => createRingFit({
|
|
15292
|
+
el,
|
|
15293
|
+
ctx,
|
|
15294
|
+
styles,
|
|
15295
|
+
arcDeg,
|
|
15296
|
+
tiltParamDeg: degreesOf(params.text("tilt", "0deg"), 0),
|
|
15297
|
+
facing: params.text("facing", "radial") === "camera" ? "camera" : "radial"
|
|
15298
|
+
}) : void 0
|
|
15299
|
+
});
|
|
15300
|
+
}
|
|
13828
15301
|
function degreesOf(value, fallbackDeg) {
|
|
13829
15302
|
const match = /^(-?[\d.]+)(deg|rad|turn|grad)?$/i.exec(value.trim());
|
|
13830
15303
|
if (!match) return fallbackDeg;
|
|
@@ -13844,7 +15317,15 @@ var kuinetic = (() => {
|
|
|
13844
15317
|
var SPATIAL_RING_PRIMITIVE = {
|
|
13845
15318
|
id: "spatial-ring",
|
|
13846
15319
|
renderer: "javascript",
|
|
13847
|
-
|
|
15320
|
+
// What `deck.ts` writes on the host (the cards, the step index, the drag), then the two
|
|
15321
|
+
// attributes only the ring's own layout publishes.
|
|
15322
|
+
channels: [
|
|
15323
|
+
CHANNEL.skew,
|
|
15324
|
+
"discrete",
|
|
15325
|
+
...SPATIAL_DECK_CHANNELS,
|
|
15326
|
+
attributeChannel("data-kui-ring-facing"),
|
|
15327
|
+
attributeChannel("data-kui-ring-plane")
|
|
15328
|
+
],
|
|
13848
15329
|
parameters: RING_PARAMETERS,
|
|
13849
15330
|
supportedTimelines: ["time"],
|
|
13850
15331
|
// `load`, not `enter`: a carousel that only wires its arrows once scrolled into view is broken,
|
|
@@ -13855,7 +15336,7 @@ var kuinetic = (() => {
|
|
|
13855
15336
|
reducedMotion: "shorten",
|
|
13856
15337
|
prepare: deferPrepare(prepareSpatialRing)
|
|
13857
15338
|
};
|
|
13858
|
-
var CAROUSEL_PRIMITIVES = [SPATIAL_RING_PRIMITIVE];
|
|
15339
|
+
var CAROUSEL_PRIMITIVES = [SPATIAL_RING_PRIMITIVE, SPATIAL_STACK_PRIMITIVE];
|
|
13859
15340
|
var CAROUSEL_PRESETS = [
|
|
13860
15341
|
{
|
|
13861
15342
|
name: "carousel-3d",
|
|
@@ -13892,10 +15373,31 @@ var kuinetic = (() => {
|
|
|
13892
15373
|
params: { arc: "120deg", facing: "camera", perspective: "900px" },
|
|
13893
15374
|
requiresOwnSubtree: true,
|
|
13894
15375
|
phase: "idle"
|
|
15376
|
+
},
|
|
15377
|
+
/*
|
|
15378
|
+
* The flat ring: cards on a clock face around whatever sits in the middle of the host (a word, a
|
|
15379
|
+
* logo — anything the `target:` does not name stays centred and off the ring). `plane:screen` is
|
|
15380
|
+
* the whole difference; see the parameter's note for why that is a parameter of this primitive and
|
|
15381
|
+
* not a new one.
|
|
15382
|
+
*
|
|
15383
|
+
* Named for what it does rather than which way it goes — `orbit` is the motion, and it has no
|
|
15384
|
+
* direction to name (the directional-naming rule, "named by travel", is about entrances that
|
|
15385
|
+
* *have* one; a ring's direction is the sign of `spin:`). Not `carousel-3d-*`, because it is not
|
|
15386
|
+
* three-dimensional, and a name that promised depth would send people looking for a `tilt:`.
|
|
15387
|
+
*
|
|
15388
|
+
* `facing:camera` by default: upright cards read as a set of things; radially turned ones read as
|
|
15389
|
+
* a dial, which is the less common ask and one keyword away.
|
|
15390
|
+
*/
|
|
15391
|
+
{
|
|
15392
|
+
name: "carousel-orbit",
|
|
15393
|
+
primitive: "spatial-ring",
|
|
15394
|
+
params: { plane: "screen", facing: "camera" },
|
|
15395
|
+
requiresOwnSubtree: true,
|
|
15396
|
+
phase: "idle"
|
|
13895
15397
|
}
|
|
13896
15398
|
];
|
|
13897
15399
|
function registerCarousel(registry) {
|
|
13898
|
-
return registry.registerPrimitives(CAROUSEL_PRIMITIVES).registerPresets(CAROUSEL_PRESETS);
|
|
15400
|
+
return registry.registerPrimitives(CAROUSEL_PRIMITIVES).registerPresets(CAROUSEL_PRESETS).registerPresets(STACK_PRESETS);
|
|
13899
15401
|
}
|
|
13900
15402
|
|
|
13901
15403
|
// src/effects/tween/properties.ts
|
|
@@ -14451,7 +15953,10 @@ var kuinetic = (() => {
|
|
|
14451
15953
|
keywords: [...DEVICE_FRAME_KINDS]
|
|
14452
15954
|
},
|
|
14453
15955
|
color: { type: "color", default: "#111", cssProperty: "--kui-device-color" },
|
|
14454
|
-
radius: { type: "length", default: RADIUS_UNSET, cssProperty: "--kui-device-radius" }
|
|
15956
|
+
radius: { type: "length", default: RADIUS_UNSET, cssProperty: "--kui-device-radius" },
|
|
15957
|
+
// Its own knob rather than a reading of `radius:`: the frame's corner and the screen's are two
|
|
15958
|
+
// shapes, and the derived screen value already follows `radius:` when this is left unwritten.
|
|
15959
|
+
"screen-radius": { type: "length", default: RADIUS_UNSET, cssProperty: "--kui-device-screen-radius" }
|
|
14455
15960
|
};
|
|
14456
15961
|
function prepareDeviceFrame(el, params) {
|
|
14457
15962
|
const state = createAttributeLedger(el);
|
|
@@ -14464,8 +15969,9 @@ var kuinetic = (() => {
|
|
|
14464
15969
|
// Exclusive ownership of the frame chrome. Its static box-model declarations are outside
|
|
14465
15970
|
// the channel-property audit, as they are for the existing catalog. The chrome also reaches
|
|
14466
15971
|
// into the direct media child (`object-fit: cover` etc.); that is a `requiresOwnSubtree`
|
|
14467
|
-
// question, as with the other showcase widgets.
|
|
14468
|
-
|
|
15972
|
+
// question, as with the other showcase widgets. The frame kind it stamps on the host is an
|
|
15973
|
+
// attribute it writes, so it claims that too.
|
|
15974
|
+
channels: ["frame", attributeChannel("data-kui-device")],
|
|
14469
15975
|
parameters: deviceFrameParams,
|
|
14470
15976
|
// `'layout'`: the bezel's `border`+`padding` are box-model properties added once, at
|
|
14471
15977
|
// activation, which is a real (if one-time) layout — not the "transform/opacity only" budget
|
|
@@ -14500,10 +16006,12 @@ var kuinetic = (() => {
|
|
|
14500
16006
|
state.lock = ledger;
|
|
14501
16007
|
}
|
|
14502
16008
|
function clearContent(state) {
|
|
14503
|
-
state.current
|
|
16009
|
+
const closing = state.current;
|
|
16010
|
+
closing?.onClose?.();
|
|
14504
16011
|
state.current = void 0;
|
|
14505
16012
|
state.currentOwner = void 0;
|
|
14506
16013
|
state.slot?.replaceChildren();
|
|
16014
|
+
if (closing?.opener) announceViewer(state.doc, closing.opener, false);
|
|
14507
16015
|
}
|
|
14508
16016
|
function onClose(state) {
|
|
14509
16017
|
state.dialog?.classList.remove("is-open");
|
|
@@ -14522,11 +16030,9 @@ var kuinetic = (() => {
|
|
|
14522
16030
|
}, state.current.reducedMotion ? 0 : state.current.duration);
|
|
14523
16031
|
}
|
|
14524
16032
|
function clickedOutside(state, target) {
|
|
14525
|
-
const
|
|
14526
|
-
if (
|
|
14527
|
-
|
|
14528
|
-
}
|
|
14529
|
-
return !content.contains(target) && !target.closest("button");
|
|
16033
|
+
const { node, inside } = state.current;
|
|
16034
|
+
if (inside) return !inside(target);
|
|
16035
|
+
return !node.contains(target) && !target.closest("button");
|
|
14530
16036
|
}
|
|
14531
16037
|
function forceReflow(element) {
|
|
14532
16038
|
return element.offsetWidth;
|
|
@@ -14576,6 +16082,7 @@ var kuinetic = (() => {
|
|
|
14576
16082
|
dialog.scrollTop = 0;
|
|
14577
16083
|
forceReflow(dialog);
|
|
14578
16084
|
dialog.classList.add("is-open");
|
|
16085
|
+
if (content.opener) announceViewer(state.doc, content.opener, true);
|
|
14579
16086
|
}
|
|
14580
16087
|
function closeNow(state) {
|
|
14581
16088
|
if (state.timer !== void 0) clearTimeout(state.timer);
|
|
@@ -14655,25 +16162,31 @@ var kuinetic = (() => {
|
|
|
14655
16162
|
}
|
|
14656
16163
|
|
|
14657
16164
|
// src/showcase/lightbox.ts
|
|
16165
|
+
var DEFAULT_DURATION_MS = 280;
|
|
16166
|
+
var DEFAULT_SCALE = "0.965";
|
|
16167
|
+
var DEFAULT_EASE = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
14658
16168
|
var paramsSchema = {
|
|
14659
|
-
media: { type: "keyword", default: "image", cssProperty: "--kui-lightbox-media", keywords: ["image", "video"] },
|
|
16169
|
+
media: { type: "keyword", default: "image", cssProperty: "--kui-lightbox-media", keywords: ["image", "video", "mixed"] },
|
|
14660
16170
|
target: { type: "text", default: "", cssProperty: "--kui-target" },
|
|
14661
16171
|
scope: SCOPE_PARAM,
|
|
14662
|
-
scale: { type: "number", default:
|
|
14663
|
-
duration: { type: "time", default:
|
|
14664
|
-
ease: { type: "easing", default:
|
|
16172
|
+
scale: { type: "number", default: DEFAULT_SCALE, cssProperty: "--kui-from-scale", finite: true, minimum: 0 },
|
|
16173
|
+
duration: { type: "time", default: `${DEFAULT_DURATION_MS}ms`, cssProperty: "--kui-lightbox-duration" },
|
|
16174
|
+
ease: { type: "easing", default: DEFAULT_EASE, cssProperty: "--kui-lightbox-ease" },
|
|
14665
16175
|
loop: { type: "keyword", default: "true", cssProperty: "--kui-lightbox-loop", keywords: ["true", "false"] },
|
|
14666
16176
|
aspect: { type: "keyword", default: "", cssProperty: "--kui-lightbox-aspect", keywords: ["wide", "tall", "square"] },
|
|
14667
16177
|
caption: { type: "keyword", default: "figcaption", cssProperty: "--kui-lightbox-caption", keywords: ["figcaption", "alt", "title", "none"] }
|
|
14668
16178
|
};
|
|
14669
16179
|
function defaultTargets(el, media) {
|
|
14670
|
-
if (el.matches("a[href]") || media
|
|
16180
|
+
if (el.matches("a[href]") || media !== "video" && el.matches("img")) return [el];
|
|
14671
16181
|
return [...el.querySelectorAll(media === "video" ? ":scope a[href]" : ":scope a[href], :scope img")];
|
|
14672
16182
|
}
|
|
16183
|
+
function effectName(media) {
|
|
16184
|
+
return media === "video" ? "video-lightbox" : "lightbox";
|
|
16185
|
+
}
|
|
14673
16186
|
function targets(el, params, ctx, media) {
|
|
14674
16187
|
const authored = params.text("target");
|
|
14675
16188
|
if (!authored) return defaultTargets(el, media);
|
|
14676
|
-
const selector = resolveTarget(authored, ctx, media
|
|
16189
|
+
const selector = resolveTarget(authored, ctx, effectName(media));
|
|
14677
16190
|
return selector ? queryScoped(el, ctx, selector, scopeParam(params, "self")) : [];
|
|
14678
16191
|
}
|
|
14679
16192
|
function imageTrigger(target) {
|
|
@@ -14684,52 +16197,137 @@ var kuinetic = (() => {
|
|
|
14684
16197
|
}
|
|
14685
16198
|
function captionFor(trigger, image, source) {
|
|
14686
16199
|
if (source === "none") return "";
|
|
14687
|
-
|
|
14688
|
-
if (source === "
|
|
14689
|
-
|
|
16200
|
+
const alt = image?.alt ?? "";
|
|
16201
|
+
if (source === "alt") return alt;
|
|
16202
|
+
if (source === "title") return titleOf(trigger, image);
|
|
16203
|
+
return trigger.closest("figure")?.querySelector("figcaption")?.textContent?.trim() || alt;
|
|
16204
|
+
}
|
|
16205
|
+
function titleOf(trigger, image) {
|
|
16206
|
+
return (trigger.getAttribute("title") || image?.title || "").trim();
|
|
14690
16207
|
}
|
|
14691
16208
|
function imageItem(trigger, captionSource) {
|
|
14692
16209
|
const image = imageIn(trigger);
|
|
14693
16210
|
if (!image) return null;
|
|
14694
16211
|
const link = trigger instanceof HTMLAnchorElement ? trigger : null;
|
|
14695
16212
|
return {
|
|
16213
|
+
kind: "image",
|
|
14696
16214
|
trigger,
|
|
14697
16215
|
image,
|
|
14698
16216
|
src: link?.href ?? (image.currentSrc || image.src),
|
|
14699
16217
|
caption: captionFor(trigger, image, captionSource)
|
|
14700
16218
|
};
|
|
14701
16219
|
}
|
|
14702
|
-
function
|
|
16220
|
+
function videoTitle(link, poster) {
|
|
16221
|
+
return link.title || poster?.getAttribute("alt") || link.getAttribute("aria-label")?.trim() || link.textContent?.trim() || "Video";
|
|
16222
|
+
}
|
|
16223
|
+
function videoItem(link, captionSource) {
|
|
16224
|
+
const source = resolveMediaSource(link.getAttribute("href") ?? "");
|
|
16225
|
+
if (!source) return null;
|
|
16226
|
+
const poster = link.querySelector("img");
|
|
16227
|
+
return { kind: "video", trigger: link, source, title: videoTitle(link, poster), caption: captionFor(link, poster, captionSource) };
|
|
16228
|
+
}
|
|
16229
|
+
function itemFor(trigger, media, captionSource) {
|
|
16230
|
+
if (media !== "image" && trigger instanceof HTMLAnchorElement) {
|
|
16231
|
+
const video = videoItem(trigger, captionSource);
|
|
16232
|
+
if (video || media === "video") return video;
|
|
16233
|
+
}
|
|
16234
|
+
return imageItem(trigger, captionSource);
|
|
16235
|
+
}
|
|
16236
|
+
function triggersFor(found, media) {
|
|
14703
16237
|
const seen = /* @__PURE__ */ new Set();
|
|
14704
|
-
const
|
|
14705
|
-
for (const target of
|
|
14706
|
-
const trigger = imageTrigger(target);
|
|
16238
|
+
const triggers = [];
|
|
16239
|
+
for (const target of found) {
|
|
16240
|
+
const trigger = media === "video" ? target : imageTrigger(target);
|
|
14707
16241
|
if (seen.has(trigger)) continue;
|
|
14708
|
-
|
|
14709
|
-
if (!item) continue;
|
|
16242
|
+
if (!usable(trigger, media)) continue;
|
|
14710
16243
|
seen.add(trigger);
|
|
14711
|
-
|
|
16244
|
+
triggers.push(trigger);
|
|
14712
16245
|
}
|
|
14713
|
-
return
|
|
16246
|
+
return triggers;
|
|
16247
|
+
}
|
|
16248
|
+
function usable(trigger, media) {
|
|
16249
|
+
const link = trigger instanceof HTMLAnchorElement;
|
|
16250
|
+
if (media === "video") return link;
|
|
16251
|
+
const image = imageIn(trigger) !== null;
|
|
16252
|
+
return media === "image" ? image : link || image;
|
|
14714
16253
|
}
|
|
14715
16254
|
function modalOptions(params, ctx) {
|
|
14716
16255
|
return {
|
|
14717
|
-
duration: params.timing.durationMs ?? params.ms("duration",
|
|
14718
|
-
scale: params.text("scale",
|
|
14719
|
-
ease: params.timing.easing ?? params.text("ease",
|
|
16256
|
+
duration: params.timing.durationMs ?? params.ms("duration", DEFAULT_DURATION_MS),
|
|
16257
|
+
scale: params.text("scale", DEFAULT_SCALE),
|
|
16258
|
+
ease: params.timing.easing ?? params.text("ease", DEFAULT_EASE),
|
|
14720
16259
|
reducedMotion: ctx.reducedMotion
|
|
14721
16260
|
};
|
|
14722
16261
|
}
|
|
14723
|
-
function showImage(item, view
|
|
16262
|
+
function showImage(item, view) {
|
|
14724
16263
|
view.src = item.src;
|
|
14725
16264
|
view.alt = item.image.alt;
|
|
14726
|
-
caption.textContent = item.caption;
|
|
14727
|
-
caption.hidden = item.caption === "";
|
|
14728
16265
|
const ratio = item.image.naturalWidth ? item.image.naturalHeight / item.image.naturalWidth : 1;
|
|
14729
16266
|
view.className = ratio > 1.5 ? "is-tall" : "is-fit";
|
|
14730
16267
|
}
|
|
16268
|
+
function videoFrame(item, aspect, doc) {
|
|
16269
|
+
const frame = doc.createElement("div");
|
|
16270
|
+
frame.className = `kui-lightbox-frame kui-lightbox-frame--${aspect || item.source.aspect}`;
|
|
16271
|
+
if (item.source.kind === "file") {
|
|
16272
|
+
const video = doc.createElement("video");
|
|
16273
|
+
video.src = item.source.embedUrl;
|
|
16274
|
+
video.controls = true;
|
|
16275
|
+
video.autoplay = true;
|
|
16276
|
+
video.playsInline = true;
|
|
16277
|
+
video.setAttribute("aria-label", item.title);
|
|
16278
|
+
frame.append(video);
|
|
16279
|
+
} else {
|
|
16280
|
+
const iframe = doc.createElement("iframe");
|
|
16281
|
+
iframe.src = item.source.embedUrl;
|
|
16282
|
+
iframe.title = item.title;
|
|
16283
|
+
iframe.allow = "autoplay; encrypted-media; picture-in-picture; fullscreen";
|
|
16284
|
+
iframe.allowFullscreen = true;
|
|
16285
|
+
frame.append(iframe);
|
|
16286
|
+
}
|
|
16287
|
+
return frame;
|
|
16288
|
+
}
|
|
16289
|
+
function pauseVideo(video) {
|
|
16290
|
+
if (video.readyState > 0) video.pause();
|
|
16291
|
+
}
|
|
16292
|
+
function stopMedia(frame) {
|
|
16293
|
+
const video = frame.querySelector("video");
|
|
16294
|
+
if (video) {
|
|
16295
|
+
pauseVideo(video);
|
|
16296
|
+
video.removeAttribute("src");
|
|
16297
|
+
}
|
|
16298
|
+
frame.replaceChildren();
|
|
16299
|
+
}
|
|
16300
|
+
function leave(state, node) {
|
|
16301
|
+
if (node === state.view) return;
|
|
16302
|
+
const video = node.querySelector("video");
|
|
16303
|
+
if (video) pauseVideo(video);
|
|
16304
|
+
else node.replaceChildren();
|
|
16305
|
+
}
|
|
16306
|
+
function land(state, item) {
|
|
16307
|
+
if (item.kind === "image") {
|
|
16308
|
+
showImage(item, state.view);
|
|
16309
|
+
return state.view;
|
|
16310
|
+
}
|
|
16311
|
+
const cached2 = state.players.get(item);
|
|
16312
|
+
if (cached2) {
|
|
16313
|
+
void cached2.querySelector("video").play().catch(() => {
|
|
16314
|
+
});
|
|
16315
|
+
return cached2;
|
|
16316
|
+
}
|
|
16317
|
+
const frame = videoFrame(item, state.aspect, state.figure.ownerDocument);
|
|
16318
|
+
if (item.source.kind === "file") state.players.set(item, frame);
|
|
16319
|
+
return frame;
|
|
16320
|
+
}
|
|
14731
16321
|
function renderGallery(state, focus) {
|
|
14732
|
-
|
|
16322
|
+
const item = state.items[state.index];
|
|
16323
|
+
if (state.shown?.item !== item) {
|
|
16324
|
+
if (state.shown) leave(state, state.shown.node);
|
|
16325
|
+
const node = land(state, item);
|
|
16326
|
+
state.shown = { item, node };
|
|
16327
|
+
state.caption.textContent = item.caption;
|
|
16328
|
+
state.caption.hidden = item.caption === "";
|
|
16329
|
+
state.figure.replaceChildren(node, state.caption);
|
|
16330
|
+
}
|
|
14733
16331
|
state.counter.textContent = `${state.index + 1} of ${state.items.length}`;
|
|
14734
16332
|
state.previous.disabled = !state.loop && state.index === 0;
|
|
14735
16333
|
state.next.disabled = !state.loop && state.index === state.items.length - 1;
|
|
@@ -14747,6 +16345,7 @@ var kuinetic = (() => {
|
|
|
14747
16345
|
}
|
|
14748
16346
|
function galleryKey(state, event) {
|
|
14749
16347
|
if (state.items.length < 2) return;
|
|
16348
|
+
if (event.target.closest("video")) return;
|
|
14750
16349
|
if (event.key === "ArrowLeft") moveGallery(state, -1, true);
|
|
14751
16350
|
else if (event.key === "ArrowRight") moveGallery(state, 1, true);
|
|
14752
16351
|
else if (event.key === "Home") {
|
|
@@ -14758,8 +16357,40 @@ var kuinetic = (() => {
|
|
|
14758
16357
|
} else return;
|
|
14759
16358
|
event.preventDefault();
|
|
14760
16359
|
}
|
|
14761
|
-
function
|
|
14762
|
-
const
|
|
16360
|
+
function swipeGallery(state, box) {
|
|
16361
|
+
const viewport = box.ownerDocument.defaultView?.visualViewport ?? null;
|
|
16362
|
+
const zoomed = () => (viewport?.scale ?? 1) > 1.01;
|
|
16363
|
+
const sync = () => {
|
|
16364
|
+
box.classList.toggle("is-swipeable", !zoomed());
|
|
16365
|
+
};
|
|
16366
|
+
sync();
|
|
16367
|
+
viewport?.addEventListener("resize", sync);
|
|
16368
|
+
const stop = recognise(box, {
|
|
16369
|
+
onSwipe(direction) {
|
|
16370
|
+
if (direction === "left" || direction === "right") moveGallery(state, swipeStep(direction), false);
|
|
16371
|
+
}
|
|
16372
|
+
}, {
|
|
16373
|
+
// A press on a button still clicks it: capture is taken only once the finger has moved.
|
|
16374
|
+
capturePointer: "drag",
|
|
16375
|
+
accept: (event) => event.pointerType !== "mouse" && !zoomed() && event.target.closest("video") === null
|
|
16376
|
+
});
|
|
16377
|
+
return () => {
|
|
16378
|
+
stop();
|
|
16379
|
+
viewport?.removeEventListener("resize", sync);
|
|
16380
|
+
};
|
|
16381
|
+
}
|
|
16382
|
+
function closeViewer(state) {
|
|
16383
|
+
state.view.removeAttribute("src");
|
|
16384
|
+
for (const frame of state.players.values()) stopMedia(frame);
|
|
16385
|
+
if (state.shown && state.shown.node !== state.view) stopMedia(state.shown.node);
|
|
16386
|
+
}
|
|
16387
|
+
function vocabulary(items) {
|
|
16388
|
+
const kinds = new Set(items.map((item) => item.kind));
|
|
16389
|
+
if (kinds.size > 1) return { noun: "item", label: "Media viewer" };
|
|
16390
|
+
return kinds.has("video") ? { noun: "video", label: "Video viewer" } : { noun: "image", label: "Image viewer" };
|
|
16391
|
+
}
|
|
16392
|
+
function galleryContent(items, initial, options) {
|
|
16393
|
+
const doc = items[initial].trigger.ownerDocument;
|
|
14763
16394
|
const box = doc.createElement("div");
|
|
14764
16395
|
box.className = "kui-lightbox-gallery";
|
|
14765
16396
|
const figure = doc.createElement("figure");
|
|
@@ -14767,7 +16398,6 @@ var kuinetic = (() => {
|
|
|
14767
16398
|
const view = doc.createElement("img");
|
|
14768
16399
|
view.decoding = "async";
|
|
14769
16400
|
const caption = doc.createElement("figcaption");
|
|
14770
|
-
figure.append(view, caption);
|
|
14771
16401
|
box.append(figure);
|
|
14772
16402
|
const counter = doc.createElement("span");
|
|
14773
16403
|
counter.className = "kui-lightbox-counter";
|
|
@@ -14777,149 +16407,244 @@ var kuinetic = (() => {
|
|
|
14777
16407
|
previous.type = next.type = "button";
|
|
14778
16408
|
previous.textContent = "\u2039";
|
|
14779
16409
|
next.textContent = "\u203A";
|
|
14780
|
-
|
|
14781
|
-
|
|
16410
|
+
const { noun, label } = vocabulary(items);
|
|
16411
|
+
previous.setAttribute("aria-label", `Previous ${noun}`);
|
|
16412
|
+
next.setAttribute("aria-label", `Next ${noun}`);
|
|
14782
16413
|
previous.className = "kui-lightbox-prev";
|
|
14783
16414
|
next.className = "kui-lightbox-next";
|
|
14784
|
-
const state = {
|
|
16415
|
+
const state = {
|
|
16416
|
+
items,
|
|
16417
|
+
index: initial,
|
|
16418
|
+
loop: options.loop,
|
|
16419
|
+
aspect: options.aspect,
|
|
16420
|
+
figure,
|
|
16421
|
+
view,
|
|
16422
|
+
caption,
|
|
16423
|
+
counter,
|
|
16424
|
+
previous,
|
|
16425
|
+
next,
|
|
16426
|
+
players: /* @__PURE__ */ new Map()
|
|
16427
|
+
};
|
|
14785
16428
|
if (items.length > 1) {
|
|
14786
16429
|
box.append(previous, next, counter);
|
|
14787
16430
|
previous.addEventListener("click", () => clickGallery(state, -1, previous));
|
|
14788
16431
|
next.addEventListener("click", () => clickGallery(state, 1, next));
|
|
14789
16432
|
}
|
|
14790
16433
|
renderGallery(state, false);
|
|
16434
|
+
const unswipe = items.length > 1 ? swipeGallery(state, box) : void 0;
|
|
16435
|
+
const { duration, scale, ease, reducedMotion } = options;
|
|
14791
16436
|
return {
|
|
14792
|
-
|
|
16437
|
+
duration,
|
|
16438
|
+
scale,
|
|
16439
|
+
ease,
|
|
16440
|
+
reducedMotion,
|
|
14793
16441
|
node: box,
|
|
14794
|
-
label
|
|
14795
|
-
|
|
16442
|
+
label,
|
|
16443
|
+
// The media and the controls keep the viewer open; the empty figure around them closes it.
|
|
16444
|
+
inside: (target) => box.contains(target) && target.closest("img, video, iframe, button") !== null,
|
|
16445
|
+
onClose: () => {
|
|
16446
|
+
unswipe?.();
|
|
16447
|
+
closeViewer(state);
|
|
16448
|
+
},
|
|
14796
16449
|
onKey: (event) => galleryKey(state, event)
|
|
14797
16450
|
};
|
|
14798
16451
|
}
|
|
14799
|
-
function stopMedia(frame) {
|
|
14800
|
-
const video = frame.querySelector("video");
|
|
14801
|
-
if (video) {
|
|
14802
|
-
if (video.readyState > 0) video.pause();
|
|
14803
|
-
video.removeAttribute("src");
|
|
14804
|
-
}
|
|
14805
|
-
frame.replaceChildren();
|
|
14806
|
-
}
|
|
14807
|
-
function videoContent(link, source, options, aspect) {
|
|
14808
|
-
const doc = link.ownerDocument;
|
|
14809
|
-
const frame = doc.createElement("div");
|
|
14810
|
-
frame.className = `kui-lightbox-frame kui-lightbox-frame--${aspect}`;
|
|
14811
|
-
const poster = link.querySelector("img");
|
|
14812
|
-
const title = link.title || poster?.getAttribute("alt") || link.getAttribute("aria-label")?.trim() || link.textContent?.trim() || "Video";
|
|
14813
|
-
if (source.kind === "file") {
|
|
14814
|
-
const video = doc.createElement("video");
|
|
14815
|
-
video.src = source.embedUrl;
|
|
14816
|
-
video.controls = true;
|
|
14817
|
-
video.autoplay = true;
|
|
14818
|
-
video.playsInline = true;
|
|
14819
|
-
video.setAttribute("aria-label", title);
|
|
14820
|
-
frame.append(video);
|
|
14821
|
-
} else {
|
|
14822
|
-
const iframe = doc.createElement("iframe");
|
|
14823
|
-
iframe.src = source.embedUrl;
|
|
14824
|
-
iframe.title = title;
|
|
14825
|
-
iframe.allow = "autoplay; encrypted-media; picture-in-picture; fullscreen";
|
|
14826
|
-
iframe.allowFullscreen = true;
|
|
14827
|
-
frame.append(iframe);
|
|
14828
|
-
}
|
|
14829
|
-
return { ...options, node: frame, label: "Video viewer", onClose: () => stopMedia(frame) };
|
|
14830
|
-
}
|
|
14831
16452
|
function primaryClick(event) {
|
|
14832
16453
|
return event.button === 0 && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey;
|
|
14833
16454
|
}
|
|
14834
|
-
function
|
|
16455
|
+
function openAt(triggers, trigger, wiring) {
|
|
16456
|
+
const items = [];
|
|
16457
|
+
let initial = -1;
|
|
16458
|
+
for (const candidate of triggers) {
|
|
16459
|
+
const item = itemFor(candidate, wiring.media, wiring.captionSource);
|
|
16460
|
+
if (!item) continue;
|
|
16461
|
+
if (candidate === trigger) initial = items.length;
|
|
16462
|
+
items.push(item);
|
|
16463
|
+
}
|
|
16464
|
+
if (initial < 0) {
|
|
16465
|
+
const href = trigger.getAttribute("href") ?? "";
|
|
16466
|
+
if (!wiring.warned.has(href)) wiring.ctx.warn(`${effectName(wiring.media)} cannot embed "${href}"; the link will navigate normally`);
|
|
16467
|
+
wiring.warned.add(href);
|
|
16468
|
+
return false;
|
|
16469
|
+
}
|
|
16470
|
+
wiring.shell.open({ ...galleryContent(items, initial, wiring.options), opener: trigger });
|
|
16471
|
+
return true;
|
|
16472
|
+
}
|
|
16473
|
+
function wireTriggers(triggers, wiring) {
|
|
14835
16474
|
const restore = [];
|
|
14836
|
-
|
|
14837
|
-
const bare =
|
|
16475
|
+
for (const trigger of triggers) {
|
|
16476
|
+
const bare = trigger instanceof HTMLImageElement;
|
|
14838
16477
|
if (bare) {
|
|
14839
|
-
const attrs = createAttributeLedger(
|
|
16478
|
+
const attrs = createAttributeLedger(trigger);
|
|
14840
16479
|
attrs.set("role", "button");
|
|
14841
16480
|
attrs.set("tabindex", "0");
|
|
14842
|
-
attrs.set("aria-label",
|
|
16481
|
+
attrs.set("aria-label", trigger.alt ? `Open larger image: ${trigger.alt}` : "Open larger image");
|
|
14843
16482
|
restore.push(() => attrs.restore());
|
|
14844
|
-
|
|
16483
|
+
trigger.addEventListener("keydown", (event) => {
|
|
14845
16484
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
14846
16485
|
event.preventDefault();
|
|
14847
|
-
|
|
16486
|
+
openAt(triggers, trigger, wiring);
|
|
14848
16487
|
}, { signal: wiring.signal });
|
|
14849
16488
|
}
|
|
14850
|
-
|
|
16489
|
+
trigger.addEventListener("click", (event) => {
|
|
14851
16490
|
if (!primaryClick(event)) return;
|
|
14852
|
-
if (
|
|
14853
|
-
wiring.shell.open(galleryContent(items, index, loop2, wiring.options));
|
|
16491
|
+
if (openAt(triggers, trigger, wiring)) event.preventDefault();
|
|
14854
16492
|
}, { signal: wiring.signal });
|
|
14855
|
-
}
|
|
16493
|
+
}
|
|
14856
16494
|
return restore;
|
|
14857
16495
|
}
|
|
14858
|
-
function
|
|
14859
|
-
const
|
|
14860
|
-
|
|
14861
|
-
|
|
14862
|
-
|
|
14863
|
-
|
|
14864
|
-
|
|
14865
|
-
|
|
14866
|
-
|
|
14867
|
-
|
|
14868
|
-
|
|
14869
|
-
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
16496
|
+
function cardItem(card) {
|
|
16497
|
+
const link = card.matches("a[href]") ? card : card.querySelector("a[href]");
|
|
16498
|
+
return link && itemFor(link, "mixed", "figcaption") || itemFor(card, "mixed", "figcaption");
|
|
16499
|
+
}
|
|
16500
|
+
var OWN_CLICK = "button, input, select, textarea, label, summary, [contenteditable]";
|
|
16501
|
+
function attachDeckLightbox(request) {
|
|
16502
|
+
const { host, cards, doc, reducedMotion } = request;
|
|
16503
|
+
const shell = acquireModalShell(doc);
|
|
16504
|
+
const options = {
|
|
16505
|
+
duration: DEFAULT_DURATION_MS,
|
|
16506
|
+
scale: DEFAULT_SCALE,
|
|
16507
|
+
ease: DEFAULT_EASE,
|
|
16508
|
+
reducedMotion,
|
|
16509
|
+
loop: true,
|
|
16510
|
+
aspect: ""
|
|
16511
|
+
};
|
|
16512
|
+
const turnedAway = (card) => card.getAttribute("data-kui-ring-face") === "back";
|
|
16513
|
+
const openAt2 = (pick, faceChecked = false) => {
|
|
16514
|
+
const all = [...cards()];
|
|
16515
|
+
const card = all.find(pick);
|
|
16516
|
+
if (!card || !faceChecked && turnedAway(card)) return false;
|
|
16517
|
+
const items = [];
|
|
16518
|
+
let initial = -1;
|
|
16519
|
+
for (const candidate of all) {
|
|
16520
|
+
const item = cardItem(candidate);
|
|
16521
|
+
if (!item) continue;
|
|
16522
|
+
if (candidate === card) initial = items.length;
|
|
16523
|
+
items.push(item);
|
|
16524
|
+
}
|
|
16525
|
+
if (initial < 0) return false;
|
|
16526
|
+
shell.open({ ...galleryContent(items, initial, options), opener: card });
|
|
16527
|
+
return true;
|
|
16528
|
+
};
|
|
16529
|
+
let pressed = null;
|
|
16530
|
+
const cardUnder = (event) => {
|
|
16531
|
+
const all = [...cards()];
|
|
16532
|
+
const hits = typeof doc.elementsFromPoint === "function" ? doc.elementsFromPoint(event.clientX, event.clientY) : [];
|
|
16533
|
+
for (const node of [...hits, event.target]) {
|
|
16534
|
+
const card = all.find((candidate) => candidate.contains(node));
|
|
16535
|
+
if (card) return turnedAway(card) ? null : { card, node };
|
|
16536
|
+
}
|
|
16537
|
+
return null;
|
|
16538
|
+
};
|
|
16539
|
+
const onPress = (event) => {
|
|
16540
|
+
pressed = event.button === 0 ? cardUnder(event) : null;
|
|
16541
|
+
};
|
|
16542
|
+
const onClick = (event) => {
|
|
16543
|
+
const press = event.detail > 0 ? pressed : null;
|
|
16544
|
+
pressed = null;
|
|
16545
|
+
const target = event.target;
|
|
16546
|
+
if (event.defaultPrevented || !primaryClick(event) || target.closest(OWN_CLICK)) return;
|
|
16547
|
+
if (press?.node.closest(OWN_CLICK)) return;
|
|
16548
|
+
const opened = press ? openAt2((card) => card === press.card, true) : openAt2((card) => card.contains(target));
|
|
16549
|
+
if (opened) event.preventDefault();
|
|
16550
|
+
};
|
|
16551
|
+
const onKeyDown = (event) => {
|
|
16552
|
+
const key = event.key;
|
|
16553
|
+
if (event.target !== host || key !== "Enter" && key !== " ") return;
|
|
16554
|
+
if (openAt2((card) => card.getAttribute(STEP_STATE_ATTR) === "active")) event.preventDefault();
|
|
16555
|
+
};
|
|
16556
|
+
host.addEventListener("pointerdown", onPress);
|
|
16557
|
+
host.addEventListener("click", onClick);
|
|
16558
|
+
host.addEventListener("keydown", onKeyDown);
|
|
16559
|
+
return () => {
|
|
16560
|
+
host.removeEventListener("pointerdown", onPress);
|
|
16561
|
+
host.removeEventListener("click", onClick);
|
|
16562
|
+
host.removeEventListener("keydown", onKeyDown);
|
|
16563
|
+
shell.release();
|
|
16564
|
+
};
|
|
14875
16565
|
}
|
|
14876
16566
|
function prepareLightbox(el, params, ctx) {
|
|
14877
16567
|
const media = params.text("media", "image");
|
|
14878
|
-
const
|
|
14879
|
-
if (
|
|
16568
|
+
const triggers = triggersFor(targets(el, params, ctx, media), media);
|
|
16569
|
+
if (triggers.length === 0) return () => {
|
|
14880
16570
|
};
|
|
14881
16571
|
const controller = new AbortController();
|
|
14882
16572
|
const shell = acquireModalShell(ctx.doc);
|
|
14883
|
-
const
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
16573
|
+
const options = { ...modalOptions(params, ctx), loop: params.is("loop"), aspect: params.text("aspect") };
|
|
16574
|
+
const restore = wireTriggers(triggers, {
|
|
16575
|
+
shell,
|
|
16576
|
+
options,
|
|
16577
|
+
signal: controller.signal,
|
|
16578
|
+
media,
|
|
16579
|
+
captionSource: params.text("caption", "figcaption"),
|
|
16580
|
+
ctx,
|
|
16581
|
+
warned: /* @__PURE__ */ new Set()
|
|
16582
|
+
});
|
|
14891
16583
|
return continuousSetup(() => {
|
|
14892
16584
|
controller.abort();
|
|
14893
16585
|
for (const undo of restore) undo();
|
|
14894
16586
|
shell.release();
|
|
14895
16587
|
});
|
|
14896
16588
|
}
|
|
16589
|
+
var TRIGGERS_CHANNEL = "lightbox:triggers";
|
|
14897
16590
|
var LIGHTBOX_PRIMITIVE = widgetPrimitive(
|
|
14898
16591
|
"lightbox",
|
|
14899
|
-
{ channels: [
|
|
16592
|
+
{ channels: [TRIGGERS_CHANNEL], parameters: paramsSchema, perfClass: "paint" },
|
|
14900
16593
|
withTimingContract("lightbox", { honours: ["duration", "ease"], because: "the dialog opens immediately, so it has no delay phase" }, deferPrepare(prepareLightbox))
|
|
14901
16594
|
);
|
|
14902
16595
|
var LIGHTBOX_PRESETS = [
|
|
14903
16596
|
{ name: "lightbox", primitive: "lightbox", params: { media: "image" } },
|
|
14904
|
-
|
|
16597
|
+
// A video row has never shown captions; `caption:figcaption` opts one in.
|
|
16598
|
+
{ name: "video-lightbox", primitive: "lightbox", params: { media: "video", caption: "none" } }
|
|
14905
16599
|
];
|
|
14906
16600
|
|
|
14907
16601
|
// src/showcase/compare.ts
|
|
14908
16602
|
var COMPARE_AXES = ["x", "y"];
|
|
14909
16603
|
var COMPARE_AXIS_ATTR = "data-kui-compare-axis";
|
|
16604
|
+
var COMPARE_DIVIDERS_ATTR = "data-kui-compare-dividers";
|
|
14910
16605
|
var compareParams = {
|
|
14911
16606
|
position: {
|
|
14912
16607
|
type: "percentage",
|
|
14913
16608
|
default: "50%",
|
|
14914
16609
|
cssProperty: "--kui-compare"
|
|
14915
16610
|
},
|
|
16611
|
+
/*
|
|
16612
|
+
* Where each divider of a three-or-more frame starts, as one quoted, space-separated list —
|
|
16613
|
+
* `positions:'20% 45% 80%'` — because a comma would start a new step, not a new value. Default
|
|
16614
|
+
* (empty) is an even split. `position:` stays the two-media spelling: it is a single percentage
|
|
16615
|
+
* and every existing page relies on it validating as one.
|
|
16616
|
+
*/
|
|
16617
|
+
positions: { type: "text", default: "", cssProperty: "--kui-compare-positions" },
|
|
14916
16618
|
axis: {
|
|
14917
16619
|
type: "keyword",
|
|
14918
16620
|
default: "x",
|
|
14919
16621
|
cssProperty: "--kui-compare-axis",
|
|
14920
16622
|
keywords: [...COMPARE_AXES]
|
|
14921
|
-
}
|
|
16623
|
+
},
|
|
16624
|
+
/*
|
|
16625
|
+
* The auto-drag, in `background-media`'s words (`src/effects/catalog/media.ts`): `always` starts
|
|
16626
|
+
* at load, `in-view` the first time the slider is on screen, `never` (default) not at all.
|
|
16627
|
+
*
|
|
16628
|
+
* Not `on:load` / `on:enter`, because the activation grammar already has a job here: it is what
|
|
16629
|
+
* *builds* the slider, and a widget accepts only `on:load` (`src/showcase/shared.ts`) so the
|
|
16630
|
+
* control exists before a keyboard user tabs to it. `on:enter` would defer the widget, not the
|
|
16631
|
+
* motion — so the motion's start is its own parameter.
|
|
16632
|
+
*/
|
|
16633
|
+
autoplay: {
|
|
16634
|
+
type: "keyword",
|
|
16635
|
+
default: "never",
|
|
16636
|
+
cssProperty: "--kui-compare-autoplay",
|
|
16637
|
+
keywords: ["never", "in-view", "always"]
|
|
16638
|
+
},
|
|
16639
|
+
/** One edge-to-edge sweep. The positional spelling (`compare 4s`) means the same thing. */
|
|
16640
|
+
duration: { type: "time", default: "3s", cssProperty: "--kui-compare-duration" },
|
|
16641
|
+
/** Head toward 0% first instead of 100%. */
|
|
16642
|
+
reverse: { type: "keyword", default: "false", cssProperty: "--kui-compare-reverse", keywords: ["true", "false"] },
|
|
16643
|
+
/** Sweep back and forth until someone takes the handle, instead of one pass. */
|
|
16644
|
+
loop: { type: "keyword", default: "false", cssProperty: "--kui-compare-loop", keywords: ["true", "false"] }
|
|
14922
16645
|
};
|
|
16646
|
+
var MIN_SWEEP_MS = 250;
|
|
16647
|
+
var MAX_FRAME_MS2 = 100;
|
|
14923
16648
|
var VERTICAL_KEY_DELTA = { ArrowUp: -1, ArrowDown: 1 };
|
|
14924
16649
|
function getMediaName(item, fallback) {
|
|
14925
16650
|
const alt = item.localName === "picture" ? item.querySelector("img")?.getAttribute("alt") : item.getAttribute("alt");
|
|
@@ -14935,76 +16660,402 @@ var kuinetic = (() => {
|
|
|
14935
16660
|
}
|
|
14936
16661
|
return "Compare";
|
|
14937
16662
|
}
|
|
14938
|
-
function
|
|
16663
|
+
function createRange(doc, label, value, axis) {
|
|
14939
16664
|
const range = doc.createElement("input");
|
|
14940
16665
|
range.type = "range";
|
|
14941
16666
|
range.className = "kui-compare-range";
|
|
14942
16667
|
range.min = "0";
|
|
14943
16668
|
range.max = "100";
|
|
14944
16669
|
range.step = "1";
|
|
14945
|
-
range.value = String(
|
|
14946
|
-
range.setAttribute("aria-label", label);
|
|
14947
|
-
range.setAttribute("aria-valuetext",
|
|
16670
|
+
range.value = String(value);
|
|
16671
|
+
range.setAttribute("aria-label", label.name);
|
|
16672
|
+
range.setAttribute("aria-valuetext", label.valueText);
|
|
14948
16673
|
if (axis === "y") range.setAttribute("aria-orientation", "vertical");
|
|
16674
|
+
return range;
|
|
16675
|
+
}
|
|
16676
|
+
function createHandle(doc) {
|
|
14949
16677
|
const handle = doc.createElement("span");
|
|
14950
16678
|
handle.className = "kui-compare-handle";
|
|
14951
16679
|
handle.setAttribute("aria-hidden", "true");
|
|
14952
|
-
return
|
|
16680
|
+
return handle;
|
|
14953
16681
|
}
|
|
14954
|
-
function
|
|
14955
|
-
|
|
14956
|
-
|
|
14957
|
-
|
|
16682
|
+
function verticalKeys(range, axis, onInput) {
|
|
16683
|
+
return (event) => {
|
|
16684
|
+
const delta = VERTICAL_KEY_DELTA[event.key];
|
|
16685
|
+
if (axis !== "y" || delta === void 0) return;
|
|
16686
|
+
event.preventDefault();
|
|
16687
|
+
if (delta < 0) range.stepDown();
|
|
16688
|
+
else range.stepUp();
|
|
16689
|
+
onInput();
|
|
16690
|
+
};
|
|
16691
|
+
}
|
|
16692
|
+
function sweepPercent(phase) {
|
|
16693
|
+
return 50 - 50 * Math.cos(Math.PI * phase / 100);
|
|
16694
|
+
}
|
|
16695
|
+
function startPhase(percent, reverse) {
|
|
16696
|
+
const outbound = 100 / Math.PI * Math.acos(1 - percent / 50);
|
|
16697
|
+
return reverse ? 200 - outbound : outbound;
|
|
16698
|
+
}
|
|
16699
|
+
function sweepDuration(params, ctx) {
|
|
16700
|
+
const authored = params.timing.durationMs ?? params.ms("duration", 3e3);
|
|
16701
|
+
if (authored >= MIN_SWEEP_MS) return authored;
|
|
16702
|
+
ctx.warn(`compare duration: minimum is ${MIN_SWEEP_MS}ms; clamped to ${MIN_SWEEP_MS}ms`);
|
|
16703
|
+
return MIN_SWEEP_MS;
|
|
16704
|
+
}
|
|
16705
|
+
function bindAutoDrag(options) {
|
|
16706
|
+
const { host, grab, ctx, mode, loop: loop2 } = options;
|
|
16707
|
+
if (mode === "never" || ctx.reducedMotion) return () => {
|
|
16708
|
+
};
|
|
16709
|
+
const run = runAutoDrag(options, () => stop());
|
|
16710
|
+
let observer = null;
|
|
16711
|
+
if (mode === "in-view" || loop2) {
|
|
16712
|
+
observer = observeOnscreen(ctx.win, host, run.setOnscreen);
|
|
14958
16713
|
}
|
|
14959
|
-
const
|
|
14960
|
-
|
|
14961
|
-
|
|
16714
|
+
const stop = () => {
|
|
16715
|
+
run.stop();
|
|
16716
|
+
observer?.disconnect();
|
|
16717
|
+
observer = null;
|
|
16718
|
+
for (const target of grab) {
|
|
16719
|
+
target.removeEventListener("pointerdown", stop);
|
|
16720
|
+
target.removeEventListener("focus", stop);
|
|
16721
|
+
}
|
|
16722
|
+
};
|
|
16723
|
+
for (const target of grab) {
|
|
16724
|
+
target.addEventListener("pointerdown", stop);
|
|
16725
|
+
target.addEventListener("focus", stop);
|
|
16726
|
+
}
|
|
16727
|
+
run.setOnscreen(mode === "always" || observer === null);
|
|
16728
|
+
return stop;
|
|
16729
|
+
}
|
|
16730
|
+
function sweepPlan(options) {
|
|
16731
|
+
const { write, homes, bounds, durationMs, reverse, loop: loop2 } = options;
|
|
16732
|
+
const order = homes.map((_, index) => index);
|
|
16733
|
+
if (reverse) order.reverse();
|
|
16734
|
+
const cycleMs = 2 * durationMs;
|
|
16735
|
+
const totalMs = cycleMs * order.length;
|
|
16736
|
+
const phases = homes.map((home, index) => {
|
|
16737
|
+
const [lo, hi] = bounds[index];
|
|
16738
|
+
return hi > lo ? startPhase(100 * (home - lo) / (hi - lo), reverse) : 0;
|
|
16739
|
+
});
|
|
16740
|
+
const park = (slot2) => write(order[slot2], homes[order[slot2]]);
|
|
16741
|
+
let slot = 0;
|
|
16742
|
+
return {
|
|
16743
|
+
show(elapsed) {
|
|
16744
|
+
if (!loop2 && elapsed >= totalMs) {
|
|
16745
|
+
park(slot);
|
|
16746
|
+
return false;
|
|
16747
|
+
}
|
|
16748
|
+
const into = elapsed % totalMs;
|
|
16749
|
+
const next = Math.floor(into / cycleMs);
|
|
16750
|
+
if (next !== slot) park(slot);
|
|
16751
|
+
slot = next;
|
|
16752
|
+
const index = order[slot];
|
|
16753
|
+
const [lo, hi] = bounds[index];
|
|
16754
|
+
const phase = phases[index] + 100 * (into - slot * cycleMs) / durationMs;
|
|
16755
|
+
write(index, lo + (hi - lo) * sweepPercent(phase) / 100);
|
|
16756
|
+
return true;
|
|
16757
|
+
}
|
|
16758
|
+
};
|
|
16759
|
+
}
|
|
16760
|
+
function observeOnscreen(win, host, onChange) {
|
|
16761
|
+
const Observer = win.IntersectionObserver;
|
|
16762
|
+
if (!Observer) return null;
|
|
16763
|
+
const observer = new Observer((entries) => {
|
|
16764
|
+
for (const entry of entries) onChange(entry.isIntersecting);
|
|
16765
|
+
});
|
|
16766
|
+
observer.observe(host);
|
|
16767
|
+
return observer;
|
|
16768
|
+
}
|
|
16769
|
+
function runAutoDrag(options, finish) {
|
|
16770
|
+
const win = options.ctx.win;
|
|
16771
|
+
const plan = sweepPlan(options);
|
|
16772
|
+
let elapsed = 0;
|
|
16773
|
+
let lastFrameAt = null;
|
|
16774
|
+
let frame = 0;
|
|
16775
|
+
let done = false;
|
|
16776
|
+
let onscreen = false;
|
|
16777
|
+
const update = () => {
|
|
16778
|
+
const active = !done && onscreen;
|
|
16779
|
+
if (active && frame === 0) {
|
|
16780
|
+
lastFrameAt = null;
|
|
16781
|
+
frame = win.requestAnimationFrame(tick);
|
|
16782
|
+
} else if (!active && frame !== 0) {
|
|
16783
|
+
win.cancelAnimationFrame(frame);
|
|
16784
|
+
frame = 0;
|
|
16785
|
+
}
|
|
16786
|
+
};
|
|
16787
|
+
function tick(now) {
|
|
16788
|
+
frame = 0;
|
|
16789
|
+
if (lastFrameAt !== null) elapsed += Math.min(now - lastFrameAt, MAX_FRAME_MS2);
|
|
16790
|
+
lastFrameAt = now;
|
|
16791
|
+
if (plan.show(elapsed)) frame = win.requestAnimationFrame(tick);
|
|
16792
|
+
else finish();
|
|
16793
|
+
}
|
|
16794
|
+
return {
|
|
16795
|
+
setOnscreen(next) {
|
|
16796
|
+
onscreen = next;
|
|
16797
|
+
update();
|
|
16798
|
+
},
|
|
16799
|
+
stop() {
|
|
16800
|
+
done = true;
|
|
16801
|
+
update();
|
|
16802
|
+
}
|
|
16803
|
+
};
|
|
16804
|
+
}
|
|
16805
|
+
function buildPair({ el, media, params, ctx, axis, timing: timing3 }) {
|
|
14962
16806
|
const initialPercent = Math.round(params.num("position", 0.5) * 100);
|
|
14963
16807
|
const style = createStyleLedger(el);
|
|
14964
16808
|
style.set("--kui-compare", params.text("position", "50%"));
|
|
14965
|
-
const
|
|
14966
|
-
|
|
14967
|
-
|
|
16809
|
+
const doc = el.ownerDocument;
|
|
16810
|
+
const range = createRange(
|
|
16811
|
+
doc,
|
|
16812
|
+
{ name: resolveCompareLabel(el, media), valueText: `${initialPercent}% after` },
|
|
14968
16813
|
initialPercent,
|
|
14969
16814
|
axis
|
|
14970
16815
|
);
|
|
16816
|
+
const handle = createHandle(doc);
|
|
14971
16817
|
const onInput = () => {
|
|
14972
16818
|
const val = range.value;
|
|
14973
16819
|
style.set("--kui-compare", `${val}%`);
|
|
14974
16820
|
range.setAttribute("aria-valuetext", `${val}% after`);
|
|
14975
16821
|
};
|
|
14976
|
-
const
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
if (delta < 0) range.stepDown();
|
|
14981
|
-
else range.stepUp();
|
|
14982
|
-
onInput();
|
|
16822
|
+
const writeAuto = (_index, percent) => {
|
|
16823
|
+
range.value = String(Math.round(percent));
|
|
16824
|
+
style.set("--kui-compare", `${Number(percent.toFixed(3))}%`);
|
|
16825
|
+
range.setAttribute("aria-valuetext", `${range.value}% after`);
|
|
14983
16826
|
};
|
|
16827
|
+
const onKeyDown = verticalKeys(range, axis, onInput);
|
|
14984
16828
|
range.addEventListener("input", onInput);
|
|
14985
16829
|
range.addEventListener("keydown", onKeyDown);
|
|
14986
16830
|
el.appendChild(range);
|
|
14987
16831
|
el.appendChild(handle);
|
|
14988
|
-
|
|
16832
|
+
const stopAutoDrag = bindAutoDrag({
|
|
16833
|
+
...timing3,
|
|
16834
|
+
host: el,
|
|
16835
|
+
grab: [range],
|
|
16836
|
+
ctx,
|
|
16837
|
+
write: writeAuto,
|
|
16838
|
+
homes: [params.num("position", 0.5) * 100],
|
|
16839
|
+
bounds: [[0, 100]]
|
|
16840
|
+
});
|
|
16841
|
+
return () => {
|
|
16842
|
+
stopAutoDrag();
|
|
14989
16843
|
range.removeEventListener("input", onInput);
|
|
14990
16844
|
range.removeEventListener("keydown", onKeyDown);
|
|
14991
16845
|
range.remove();
|
|
14992
16846
|
handle.remove();
|
|
14993
|
-
attrs.restore();
|
|
14994
16847
|
style.restore();
|
|
16848
|
+
};
|
|
16849
|
+
}
|
|
16850
|
+
var PERCENT_TOKEN = /^(\d+(?:\.\d+)?)%$/;
|
|
16851
|
+
function resolvePositions(authored, count, warn) {
|
|
16852
|
+
const even = Array.from({ length: count }, (_, k) => Math.round(100 * (k + 1) / (count + 1)));
|
|
16853
|
+
if (authored.trim() === "") return even;
|
|
16854
|
+
const tokens = authored.trim().split(/\s+/);
|
|
16855
|
+
const values = tokens.map((token) => PERCENT_TOKEN.exec(token)?.[1]);
|
|
16856
|
+
const numbers = values.map(Number);
|
|
16857
|
+
const usable2 = tokens.length === count && values.every((value) => value !== void 0) && numbers.every((value, k) => value <= 100 && (k === 0 || value >= numbers[k - 1]));
|
|
16858
|
+
if (usable2) return numbers.map(Math.round);
|
|
16859
|
+
const example = even.map((value) => String(value) + "%").join(" ");
|
|
16860
|
+
warn(
|
|
16861
|
+
`compare positions: expected ${count} ascending percentages between 0% and 100% for ${count + 1} media (e.g. positions:'${example}'); using an even split`
|
|
16862
|
+
);
|
|
16863
|
+
return even;
|
|
16864
|
+
}
|
|
16865
|
+
function buildMulti({ el, media, params, ctx, axis, timing: timing3 }) {
|
|
16866
|
+
if (params.text("position", "50%") !== "50%") {
|
|
16867
|
+
ctx.warn(`compare position: sets the one divider of two media; with ${media.length}, use positions:`);
|
|
16868
|
+
}
|
|
16869
|
+
const positions = resolvePositions(params.text("positions", ""), media.length - 1, ctx.warn);
|
|
16870
|
+
const homes = [...positions];
|
|
16871
|
+
const attrs = createAttributeLedger(el);
|
|
16872
|
+
attrs.set(COMPARE_DIVIDERS_ATTR, String(homes.length));
|
|
16873
|
+
const dividers = createDividers(el, media, axis, positions);
|
|
16874
|
+
const surface = bindSurface(el, axis, dividers);
|
|
16875
|
+
const stopAutoDrag = bindAutoDrag({
|
|
16876
|
+
...timing3,
|
|
16877
|
+
host: el,
|
|
16878
|
+
grab: [surface.node, ...dividers.ranges],
|
|
16879
|
+
ctx,
|
|
16880
|
+
write: dividers.render,
|
|
16881
|
+
homes,
|
|
16882
|
+
bounds: homes.map((_, k) => boundsOf(homes, k))
|
|
16883
|
+
});
|
|
16884
|
+
return () => {
|
|
16885
|
+
stopAutoDrag();
|
|
16886
|
+
surface.destroy();
|
|
16887
|
+
dividers.destroy();
|
|
16888
|
+
attrs.restore();
|
|
16889
|
+
};
|
|
16890
|
+
}
|
|
16891
|
+
function boundsOf(positions, k) {
|
|
16892
|
+
return [k > 0 ? positions[k - 1] : 0, k < positions.length - 1 ? positions[k + 1] : 100];
|
|
16893
|
+
}
|
|
16894
|
+
function dividerLabel(el, media, k) {
|
|
16895
|
+
const count = media.length - 1;
|
|
16896
|
+
const authored = el.getAttribute("aria-label")?.trim();
|
|
16897
|
+
if (authored) return `${authored}: divider ${k + 1} of ${count}`;
|
|
16898
|
+
const before = getMediaName(media[k], `Image ${k + 1}`);
|
|
16899
|
+
const after = getMediaName(media[k + 1], `Image ${k + 2}`);
|
|
16900
|
+
return `Divider ${k + 1} of ${count}: ${before} / ${after}`;
|
|
16901
|
+
}
|
|
16902
|
+
function createDividers(el, media, axis, positions) {
|
|
16903
|
+
const doc = el.ownerDocument;
|
|
16904
|
+
const count = positions.length;
|
|
16905
|
+
const clip = axis === "y" ? "inset(var(--kui-compare) 0 0 0)" : "inset(0 0 0 var(--kui-compare))";
|
|
16906
|
+
const mediaStyles = media.slice(1).map((item) => createStyleLedger(item));
|
|
16907
|
+
for (const ledger of mediaStyles) ledger.set("clip-path", clip);
|
|
16908
|
+
const ranges = [];
|
|
16909
|
+
const handles = [];
|
|
16910
|
+
const cleanups = [];
|
|
16911
|
+
const render = (k, shown) => {
|
|
16912
|
+
positions[k] = Math.round(shown);
|
|
16913
|
+
const css = `${Number(shown.toFixed(3))}%`;
|
|
16914
|
+
handles[k].style.setProperty("--kui-compare", css);
|
|
16915
|
+
mediaStyles[k].set("--kui-compare", css);
|
|
16916
|
+
ranges[k].value = String(positions[k]);
|
|
16917
|
+
ranges[k].setAttribute("aria-valuetext", `${positions[k]}%`);
|
|
16918
|
+
if (k > 0) ranges[k - 1].max = String(positions[k]);
|
|
16919
|
+
if (k < count - 1) ranges[k + 1].min = String(positions[k]);
|
|
16920
|
+
};
|
|
16921
|
+
for (let k = 0; k < count; k++) {
|
|
16922
|
+
const range = createRange(doc, { name: dividerLabel(el, media, k), valueText: `${positions[k]}%` }, positions[k], axis);
|
|
16923
|
+
const handle = createHandle(doc);
|
|
16924
|
+
ranges.push(range);
|
|
16925
|
+
handles.push(handle);
|
|
16926
|
+
el.append(range, handle);
|
|
16927
|
+
const onInput = () => render(k, Number(range.value));
|
|
16928
|
+
const onKeyDown = verticalKeys(range, axis, onInput);
|
|
16929
|
+
range.addEventListener("input", onInput);
|
|
16930
|
+
range.addEventListener("keydown", onKeyDown);
|
|
16931
|
+
cleanups.push(() => {
|
|
16932
|
+
range.removeEventListener("input", onInput);
|
|
16933
|
+
range.removeEventListener("keydown", onKeyDown);
|
|
16934
|
+
});
|
|
16935
|
+
}
|
|
16936
|
+
for (let k = 0; k < count; k++) {
|
|
16937
|
+
const [lo, hi] = boundsOf(positions, k);
|
|
16938
|
+
ranges[k].min = String(lo);
|
|
16939
|
+
ranges[k].max = String(hi);
|
|
16940
|
+
render(k, positions[k]);
|
|
16941
|
+
}
|
|
16942
|
+
return {
|
|
16943
|
+
ranges,
|
|
16944
|
+
render,
|
|
16945
|
+
moveTo(k, percent) {
|
|
16946
|
+
const [lo, hi] = boundsOf(positions, k);
|
|
16947
|
+
render(k, Math.min(hi, Math.max(lo, percent)));
|
|
16948
|
+
},
|
|
16949
|
+
nearest: (percent) => nearestDivider(positions, percent),
|
|
16950
|
+
destroy() {
|
|
16951
|
+
for (const cleanup of cleanups) cleanup();
|
|
16952
|
+
for (const node of [...ranges, ...handles]) node.remove();
|
|
16953
|
+
for (const ledger of mediaStyles) ledger.restore();
|
|
16954
|
+
}
|
|
16955
|
+
};
|
|
16956
|
+
}
|
|
16957
|
+
function nearestDivider(positions, percent) {
|
|
16958
|
+
let best = 0;
|
|
16959
|
+
for (let k = 1; k < positions.length; k++) {
|
|
16960
|
+
const distance3 = Math.abs(positions[k] - percent);
|
|
16961
|
+
const bestDistance = Math.abs(positions[best] - percent);
|
|
16962
|
+
if (distance3 < bestDistance || distance3 === bestDistance && percent >= positions[k]) best = k;
|
|
16963
|
+
}
|
|
16964
|
+
return best;
|
|
16965
|
+
}
|
|
16966
|
+
function bindSurface(el, axis, dividers) {
|
|
16967
|
+
const surface = el.ownerDocument.createElement("div");
|
|
16968
|
+
surface.className = "kui-compare-surface";
|
|
16969
|
+
surface.setAttribute("aria-hidden", "true");
|
|
16970
|
+
el.appendChild(surface);
|
|
16971
|
+
let dragging = -1;
|
|
16972
|
+
let pointerId = null;
|
|
16973
|
+
const percentAt = (event) => {
|
|
16974
|
+
const rect = el.getBoundingClientRect();
|
|
16975
|
+
const along = axis === "y" ? event.clientY - rect.top : event.clientX - rect.left;
|
|
16976
|
+
const size = axis === "y" ? rect.height : rect.width;
|
|
16977
|
+
return size > 0 ? 100 * along / size : 0;
|
|
16978
|
+
};
|
|
16979
|
+
const onPointerDown = (event) => {
|
|
16980
|
+
if (event.button > 0 || pointerId !== null) return;
|
|
16981
|
+
pointerId = event.pointerId;
|
|
16982
|
+
const percent = percentAt(event);
|
|
16983
|
+
dragging = dividers.nearest(percent);
|
|
16984
|
+
event.preventDefault();
|
|
16985
|
+
surface.setPointerCapture?.(event.pointerId);
|
|
16986
|
+
dividers.ranges[dragging].focus({ preventScroll: true });
|
|
16987
|
+
dividers.moveTo(dragging, percent);
|
|
16988
|
+
};
|
|
16989
|
+
const onPointerMove = (event) => {
|
|
16990
|
+
if (dragging >= 0 && event.pointerId === pointerId) dividers.moveTo(dragging, percentAt(event));
|
|
16991
|
+
};
|
|
16992
|
+
const onPointerUp = (event) => {
|
|
16993
|
+
if (event.pointerId !== pointerId) return;
|
|
16994
|
+
dragging = -1;
|
|
16995
|
+
pointerId = null;
|
|
16996
|
+
};
|
|
16997
|
+
const onLostCapture = (event) => {
|
|
16998
|
+
if (event.target !== surface) return;
|
|
16999
|
+
onPointerUp(event);
|
|
17000
|
+
};
|
|
17001
|
+
const listeners = [
|
|
17002
|
+
["pointerdown", onPointerDown],
|
|
17003
|
+
["pointermove", onPointerMove],
|
|
17004
|
+
["pointerup", onPointerUp],
|
|
17005
|
+
["pointercancel", onPointerUp],
|
|
17006
|
+
["lostpointercapture", onLostCapture]
|
|
17007
|
+
];
|
|
17008
|
+
for (const [type, listener] of listeners) surface.addEventListener(type, listener);
|
|
17009
|
+
return {
|
|
17010
|
+
node: surface,
|
|
17011
|
+
destroy() {
|
|
17012
|
+
for (const [type, listener] of listeners) surface.removeEventListener(type, listener);
|
|
17013
|
+
surface.remove();
|
|
17014
|
+
}
|
|
17015
|
+
};
|
|
17016
|
+
}
|
|
17017
|
+
function prepareCompare(el, params, ctx) {
|
|
17018
|
+
const media = Array.from(el.querySelectorAll(":scope > :is(img, picture, video)"));
|
|
17019
|
+
if (media.length < 2) {
|
|
17020
|
+
ctx.warn("compare requires at least two media children (img, picture, or video)");
|
|
17021
|
+
}
|
|
17022
|
+
const axis = params.text("axis", "x");
|
|
17023
|
+
const attrs = createAttributeLedger(el);
|
|
17024
|
+
attrs.set(COMPARE_AXIS_ATTR, axis);
|
|
17025
|
+
const build2 = media.length > 2 ? buildMulti : buildPair;
|
|
17026
|
+
const teardown = build2({
|
|
17027
|
+
el,
|
|
17028
|
+
media,
|
|
17029
|
+
params,
|
|
17030
|
+
ctx,
|
|
17031
|
+
axis,
|
|
17032
|
+
timing: {
|
|
17033
|
+
mode: params.text("autoplay", "never"),
|
|
17034
|
+
durationMs: sweepDuration(params, ctx),
|
|
17035
|
+
reverse: params.is("reverse"),
|
|
17036
|
+
loop: params.is("loop")
|
|
17037
|
+
}
|
|
17038
|
+
});
|
|
17039
|
+
return continuousSetup(() => {
|
|
17040
|
+
teardown();
|
|
17041
|
+
attrs.restore();
|
|
14995
17042
|
});
|
|
14996
17043
|
}
|
|
14997
17044
|
var COMPARE_PRIMITIVE = widgetPrimitive(
|
|
14998
17045
|
"compare",
|
|
14999
17046
|
{
|
|
15000
|
-
|
|
17047
|
+
// The layers it stacks and the range, handle and surface it appends; the axis and divider
|
|
17048
|
+
// markers it writes on the host. Not `clip`: its `clip-path` is on the after layer, never the
|
|
17049
|
+
// host, so a `wipe-up` revealing the whole slider is not a fight over one property.
|
|
17050
|
+
channels: [SUBTREE_CHANNEL, attributeChannel(COMPARE_AXIS_ATTR), attributeChannel(COMPARE_DIVIDERS_ATTR)],
|
|
15001
17051
|
parameters: compareParams,
|
|
15002
17052
|
perfClass: "paint"
|
|
15003
17053
|
},
|
|
15004
17054
|
withTimingContract(
|
|
15005
17055
|
"compare",
|
|
15006
17056
|
{
|
|
15007
|
-
|
|
17057
|
+
honours: ["duration"],
|
|
17058
|
+
because: "its only motion is the autoplay: sweep, which starts on autoplay: rather than after a delay and eases into each edge on its own curve"
|
|
15008
17059
|
},
|
|
15009
17060
|
deferPrepare(prepareCompare)
|
|
15010
17061
|
)
|
|
@@ -15178,7 +17229,8 @@ var kuinetic = (() => {
|
|
|
15178
17229
|
var HOTSPOTS_PRIMITIVE = widgetPrimitive(
|
|
15179
17230
|
"hotspots",
|
|
15180
17231
|
{
|
|
15181
|
-
|
|
17232
|
+
// The marker buttons it appends, placed in percent of the host, and the marker style it writes.
|
|
17233
|
+
channels: [SUBTREE_CHANNEL, attributeChannel("data-kui-hotspot-marker")],
|
|
15182
17234
|
parameters: hotspotsParams,
|
|
15183
17235
|
perfClass: "layout"
|
|
15184
17236
|
},
|
|
@@ -15382,7 +17434,7 @@ var kuinetic = (() => {
|
|
|
15382
17434
|
hovered = true;
|
|
15383
17435
|
update();
|
|
15384
17436
|
};
|
|
15385
|
-
const
|
|
17437
|
+
const leave2 = () => {
|
|
15386
17438
|
hovered = false;
|
|
15387
17439
|
update();
|
|
15388
17440
|
};
|
|
@@ -15399,7 +17451,7 @@ var kuinetic = (() => {
|
|
|
15399
17451
|
update();
|
|
15400
17452
|
};
|
|
15401
17453
|
el.addEventListener("pointerenter", enter);
|
|
15402
|
-
el.addEventListener("pointerleave",
|
|
17454
|
+
el.addEventListener("pointerleave", leave2);
|
|
15403
17455
|
el.addEventListener("focusin", focus);
|
|
15404
17456
|
el.addEventListener("focusout", blur);
|
|
15405
17457
|
ctx.doc.addEventListener("visibilitychange", update);
|
|
@@ -15408,7 +17460,7 @@ var kuinetic = (() => {
|
|
|
15408
17460
|
return () => {
|
|
15409
17461
|
if (timer !== null) clearTimeout(timer);
|
|
15410
17462
|
el.removeEventListener("pointerenter", enter);
|
|
15411
|
-
el.removeEventListener("pointerleave",
|
|
17463
|
+
el.removeEventListener("pointerleave", leave2);
|
|
15412
17464
|
el.removeEventListener("focusin", focus);
|
|
15413
17465
|
el.removeEventListener("focusout", blur);
|
|
15414
17466
|
ctx.doc.removeEventListener("visibilitychange", update);
|
|
@@ -15440,7 +17492,7 @@ var kuinetic = (() => {
|
|
|
15440
17492
|
return recognise(el, { onSwipe: (direction) => {
|
|
15441
17493
|
if (direction === "left") index.next();
|
|
15442
17494
|
if (direction === "right") index.prev();
|
|
15443
|
-
} }, { axis: "x", capturePointer:
|
|
17495
|
+
} }, { axis: "x", capturePointer: "drag" });
|
|
15444
17496
|
}
|
|
15445
17497
|
function prepareSlideshow(el, params, ctx) {
|
|
15446
17498
|
const selector = resolveTarget(
|
|
@@ -15474,7 +17526,6 @@ var kuinetic = (() => {
|
|
|
15474
17526
|
el,
|
|
15475
17527
|
params,
|
|
15476
17528
|
ctx,
|
|
15477
|
-
scope,
|
|
15478
17529
|
resolveSteps,
|
|
15479
17530
|
name: "slideshow",
|
|
15480
17531
|
clickFallback: false,
|
|
@@ -15502,7 +17553,19 @@ var kuinetic = (() => {
|
|
|
15502
17553
|
}
|
|
15503
17554
|
var SLIDESHOW_PRIMITIVE = widgetPrimitive(
|
|
15504
17555
|
"slideshow",
|
|
15505
|
-
|
|
17556
|
+
// The controls it inserts, then what it writes on the host: `createStepIndex`'s `data-kui-step`,
|
|
17557
|
+
// as every step deck does, and `decorateHost`'s mode and carousel roles.
|
|
17558
|
+
{
|
|
17559
|
+
channels: [
|
|
17560
|
+
SUBTREE_CHANNEL,
|
|
17561
|
+
attributeChannel("data-kui-step"),
|
|
17562
|
+
attributeChannel(SLIDE_MODE_ATTR),
|
|
17563
|
+
attributeChannel("role"),
|
|
17564
|
+
attributeChannel("aria-roledescription")
|
|
17565
|
+
],
|
|
17566
|
+
parameters: slideshowParams,
|
|
17567
|
+
perfClass: "layout"
|
|
17568
|
+
},
|
|
15506
17569
|
withTimingContract(
|
|
15507
17570
|
"slideshow",
|
|
15508
17571
|
{ honours: ["duration", "ease"], because: "the slideshow has no delayed start" },
|
|
@@ -15664,7 +17727,14 @@ var kuinetic = (() => {
|
|
|
15664
17727
|
}
|
|
15665
17728
|
var SCROLL_STORY_PRIMITIVE = widgetPrimitive(
|
|
15666
17729
|
"scroll-story",
|
|
15667
|
-
|
|
17730
|
+
// Its stylesheet lays out the host's first and last child, so nothing else may insert one. The
|
|
17731
|
+
// step index on the host (`applyStep`) is claimed so a `carousel` or `scroll-progress` beside it
|
|
17732
|
+
// is refused rather than left to overwrite it.
|
|
17733
|
+
{
|
|
17734
|
+
channels: [SUBTREE_CHANNEL, attributeChannel("data-kui-step"), attributeChannel(STORY_SIDE_ATTR)],
|
|
17735
|
+
parameters: scrollStoryParams,
|
|
17736
|
+
perfClass: "layout"
|
|
17737
|
+
},
|
|
15668
17738
|
withTimingContract(
|
|
15669
17739
|
"scroll-story",
|
|
15670
17740
|
{ because: "scroll position" },
|
|
@@ -15767,7 +17837,8 @@ var kuinetic = (() => {
|
|
|
15767
17837
|
}
|
|
15768
17838
|
var SLOW_MO_PRIMITIVE = widgetPrimitive(
|
|
15769
17839
|
"slow-mo",
|
|
15770
|
-
|
|
17840
|
+
// The toggle it prepends into the host, and the time scale it writes there.
|
|
17841
|
+
{ channels: [SUBTREE_CHANNEL, attributeChannel(TIME_SCALE_ATTR)], parameters: slowMoParams, perfClass: "compositor" },
|
|
15771
17842
|
withTimingContract(
|
|
15772
17843
|
"slow-mo",
|
|
15773
17844
|
{ because: "it changes playback rate rather than defining an animation timeline" },
|
|
@@ -15796,6 +17867,7 @@ var kuinetic = (() => {
|
|
|
15796
17867
|
...SLOW_MO_PRESETS
|
|
15797
17868
|
];
|
|
15798
17869
|
function registerShowcase(target) {
|
|
17870
|
+
provideDeckViewer(attachDeckLightbox);
|
|
15799
17871
|
return registerInto(target, SHOWCASE_PRIMITIVES, SHOWCASE_PRESETS, "Showcase");
|
|
15800
17872
|
}
|
|
15801
17873
|
|
|
@@ -15824,6 +17896,16 @@ var kuinetic = (() => {
|
|
|
15824
17896
|
return new Animator({ registry: createRegistry(), ...options });
|
|
15825
17897
|
}
|
|
15826
17898
|
var src_default = kuinetic;
|
|
17899
|
+
var catalog;
|
|
17900
|
+
function describe2(name, options = {}) {
|
|
17901
|
+
return describeEffect(options.registry ?? (catalog ??= createRegistry()), name, options.notes);
|
|
17902
|
+
}
|
|
17903
|
+
function describeSteps(value, options = {}) {
|
|
17904
|
+
return describeAttribute(options.registry ?? (catalog ??= createRegistry()), value, options.notes);
|
|
17905
|
+
}
|
|
17906
|
+
function describeElement(value, options = {}) {
|
|
17907
|
+
return describeElementAttribute(options.registry ?? (catalog ??= createRegistry()), value, options.notes);
|
|
17908
|
+
}
|
|
15827
17909
|
return __toCommonJS(src_exports);
|
|
15828
17910
|
})();
|
|
15829
17911
|
|
|
@@ -15909,17 +17991,24 @@ var __kuineticBoot = (() => {
|
|
|
15909
17991
|
}
|
|
15910
17992
|
}
|
|
15911
17993
|
function guarded(runtime, original, core, options) {
|
|
17994
|
+
const wantsShared = !options || Object.entries(options).every(([key, value]) => key === "observe" && value === true);
|
|
17995
|
+
if (runtime.manual) {
|
|
17996
|
+
const made = original.call(core, options);
|
|
17997
|
+
adoptManual(runtime, made);
|
|
17998
|
+
return made;
|
|
17999
|
+
}
|
|
15912
18000
|
if (!runtime.started) {
|
|
15913
18001
|
const replacement = original.call(core, options);
|
|
15914
18002
|
adopt(runtime, replacement);
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15918
|
-
|
|
18003
|
+
if (wantsShared) {
|
|
18004
|
+
warnManualCall(
|
|
18005
|
+
runtime,
|
|
18006
|
+
"yours is now the one this page uses, exactly as it would have been before the script tag started one, so the call is safe to delete"
|
|
18007
|
+
);
|
|
18008
|
+
}
|
|
15919
18009
|
return replacement;
|
|
15920
18010
|
}
|
|
15921
18011
|
const shared = runtime.animator;
|
|
15922
|
-
const wantsShared = !options || Object.entries(options).every(([key, value]) => key === "observe" && value === true);
|
|
15923
18012
|
if (shared && wantsShared) {
|
|
15924
18013
|
warnManualCall(runtime, "you have been handed the one it already made, so this call is safe to delete");
|
|
15925
18014
|
return shared;
|
|
@@ -15946,6 +18035,40 @@ var __kuineticBoot = (() => {
|
|
|
15946
18035
|
runtime.animator = animator;
|
|
15947
18036
|
globalScope().__kuinetic = animator;
|
|
15948
18037
|
}
|
|
18038
|
+
function adoptManual(runtime, animator) {
|
|
18039
|
+
if (!runtime.animator) adopt(runtime, animator);
|
|
18040
|
+
const entry = { animator, started: false };
|
|
18041
|
+
const list = runtime.handBuilt ??= [];
|
|
18042
|
+
list.push(entry);
|
|
18043
|
+
const start = animator.start.bind(animator);
|
|
18044
|
+
animator.start = () => {
|
|
18045
|
+
entry.started = true;
|
|
18046
|
+
return start();
|
|
18047
|
+
};
|
|
18048
|
+
if (typeof animator.destroy === "function") {
|
|
18049
|
+
const destroy = animator.destroy.bind(animator);
|
|
18050
|
+
animator.destroy = () => {
|
|
18051
|
+
const at = list.indexOf(entry);
|
|
18052
|
+
if (at >= 0) list.splice(at, 1);
|
|
18053
|
+
return destroy();
|
|
18054
|
+
};
|
|
18055
|
+
}
|
|
18056
|
+
for (const tier of runtime.pending) registerTier(tier, animator);
|
|
18057
|
+
}
|
|
18058
|
+
function registerTier(entry, animator) {
|
|
18059
|
+
try {
|
|
18060
|
+
entry.register(animator);
|
|
18061
|
+
} catch (error) {
|
|
18062
|
+
warn(`the "${entry.tier}" tier failed to register: ${String(error)}`);
|
|
18063
|
+
}
|
|
18064
|
+
}
|
|
18065
|
+
function enlistOnManual(runtime, entry) {
|
|
18066
|
+
runtime.pending.push(entry);
|
|
18067
|
+
for (const { animator, started } of [...runtime.handBuilt ?? []]) {
|
|
18068
|
+
registerTier(entry, animator);
|
|
18069
|
+
if (started) recompile(animator);
|
|
18070
|
+
}
|
|
18071
|
+
}
|
|
15949
18072
|
function createAnimator(runtime, core) {
|
|
15950
18073
|
adopt(runtime, core.kuinetic({ observe: true }));
|
|
15951
18074
|
}
|
|
@@ -15975,13 +18098,7 @@ var __kuineticBoot = (() => {
|
|
|
15975
18098
|
}
|
|
15976
18099
|
function drain(runtime, animator) {
|
|
15977
18100
|
const pending = runtime.pending.splice(0, runtime.pending.length);
|
|
15978
|
-
for (const entry of pending)
|
|
15979
|
-
try {
|
|
15980
|
-
entry.register(animator);
|
|
15981
|
-
} catch (error) {
|
|
15982
|
-
warn(`the "${entry.tier}" tier failed to register: ${String(error)}`);
|
|
15983
|
-
}
|
|
15984
|
-
}
|
|
18101
|
+
for (const entry of pending) registerTier(entry, animator);
|
|
15985
18102
|
return pending.length;
|
|
15986
18103
|
}
|
|
15987
18104
|
function runBoot(runtime) {
|
|
@@ -16003,13 +18120,6 @@ var __kuineticBoot = (() => {
|
|
|
16003
18120
|
}
|
|
16004
18121
|
if (registered) recompile(animator);
|
|
16005
18122
|
}
|
|
16006
|
-
function optedOut(runtime, options) {
|
|
16007
|
-
if (taggedManual()) {
|
|
16008
|
-
if (options.core) runtime.manual = true;
|
|
16009
|
-
return true;
|
|
16010
|
-
}
|
|
16011
|
-
return runtime.manual;
|
|
16012
|
-
}
|
|
16013
18123
|
function enlist(runtime, options) {
|
|
16014
18124
|
if (options.core) {
|
|
16015
18125
|
if (runtime.animator) return;
|
|
@@ -16023,7 +18133,17 @@ var __kuineticBoot = (() => {
|
|
|
16023
18133
|
if (typeof document === "undefined" || typeof globalThis === "undefined") return;
|
|
16024
18134
|
const runtime = getRuntime();
|
|
16025
18135
|
checkVersion(runtime, options.tier);
|
|
16026
|
-
if (
|
|
18136
|
+
if (taggedManual()) {
|
|
18137
|
+
if (options.core) {
|
|
18138
|
+
runtime.manual = true;
|
|
18139
|
+
installGuard(runtime, options.core, options.globalName);
|
|
18140
|
+
}
|
|
18141
|
+
return;
|
|
18142
|
+
}
|
|
18143
|
+
if (runtime.manual) {
|
|
18144
|
+
if (options.register) enlistOnManual(runtime, { tier: options.tier, register: options.register });
|
|
18145
|
+
return;
|
|
18146
|
+
}
|
|
16027
18147
|
enlist(runtime, options);
|
|
16028
18148
|
schedule(runtime);
|
|
16029
18149
|
}
|