kuinetic 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/esm/{chunk-K2M7O6W5.mjs → chunk-FASAEQDR.mjs} +32 -1
- package/dist/esm/{chunk-XDDJVANU.mjs → chunk-FYMZW5N2.mjs} +2301 -528
- package/dist/esm/{chunk-BBQ22D7A.mjs → chunk-MZQG2VMG.mjs} +202 -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.all.js +2502 -501
- package/dist/kuinetic.all.min.js +2 -2
- package/dist/kuinetic.css +238 -65
- package/dist/kuinetic.js +2501 -500
- package/dist/kuinetic.min.css +1 -1
- package/dist/kuinetic.min.js +1 -1
- package/dist/kuinetic.notes.js +1 -0
- package/dist/types/3d/flattening.d.ts +8 -7
- package/dist/types/core/deck-viewer.d.ts +87 -0
- package/dist/types/core/describe.d.ts +174 -0
- package/dist/types/core/gesture.d.ts +10 -0
- package/dist/types/core/index.d.ts +6 -2
- 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 +7 -3
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() {
|
|
@@ -1255,6 +1271,10 @@ var kuinetic = (() => {
|
|
|
1255
1271
|
*/
|
|
1256
1272
|
skew: "skew"
|
|
1257
1273
|
};
|
|
1274
|
+
function attributeChannel(attribute) {
|
|
1275
|
+
return `attr:${attribute}`;
|
|
1276
|
+
}
|
|
1277
|
+
var SUBTREE_CHANNEL = "subtree";
|
|
1258
1278
|
function inertInstance(destroy = () => {
|
|
1259
1279
|
}) {
|
|
1260
1280
|
return {
|
|
@@ -6539,8 +6559,8 @@ var kuinetic = (() => {
|
|
|
6539
6559
|
shouldObserve: options.observe ?? false
|
|
6540
6560
|
};
|
|
6541
6561
|
}
|
|
6542
|
-
function resolveJsEffectPreparer(
|
|
6543
|
-
return
|
|
6562
|
+
function resolveJsEffectPreparer(provided2, deps) {
|
|
6563
|
+
return provided2 ?? createJsEffectPreparer(deps);
|
|
6544
6564
|
}
|
|
6545
6565
|
function defaultRootResolver(root) {
|
|
6546
6566
|
const doc = isElementNode(root) ? root.ownerDocument : root;
|
|
@@ -6557,11 +6577,214 @@ var kuinetic = (() => {
|
|
|
6557
6577
|
}
|
|
6558
6578
|
}
|
|
6559
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
|
+
|
|
6560
6784
|
// src/effects/shared.ts
|
|
6561
6785
|
var TRIGGER_DELAY_PARAM = {
|
|
6562
6786
|
delay: { type: "time", default: "0ms", cssProperty: "--kui-delay" }
|
|
6563
6787
|
};
|
|
6564
|
-
var ALL_TIMING_TOKENS = ["duration", "delay", "ease"];
|
|
6565
6788
|
function authoredTiming(params, token) {
|
|
6566
6789
|
if (token === "ease") return params.timing.easing;
|
|
6567
6790
|
const ms = token === "duration" ? params.timing.durationMs : params.timing.delayMs;
|
|
@@ -6589,20 +6812,20 @@ var kuinetic = (() => {
|
|
|
6589
6812
|
}
|
|
6590
6813
|
}
|
|
6591
6814
|
function withTimingContract(id, contract, prepare) {
|
|
6592
|
-
return (el, params, ctx) => {
|
|
6815
|
+
return declareTimingContract((el, params, ctx) => {
|
|
6593
6816
|
warnUnhonouredTiming(id, contract, params, ctx.warn);
|
|
6594
6817
|
return prepare(el, params, ctx);
|
|
6595
|
-
};
|
|
6818
|
+
}, contract);
|
|
6596
6819
|
}
|
|
6597
6820
|
function stylesheetTimingPrepare(id, contract) {
|
|
6598
|
-
return (el, params, ctx) => {
|
|
6821
|
+
return declareTimingContract((el, params, ctx) => {
|
|
6599
6822
|
warnUnhonouredTiming(id, contract, params, ctx.warn);
|
|
6600
6823
|
return deferredInstance(() => {
|
|
6601
6824
|
mirrorTimingToCss(id, contract.honours ?? [], params, ctx);
|
|
6602
6825
|
return () => {
|
|
6603
6826
|
};
|
|
6604
6827
|
});
|
|
6605
|
-
};
|
|
6828
|
+
}, contract);
|
|
6606
6829
|
}
|
|
6607
6830
|
var TIMELINE_AGNOSTIC = ["time", "view", "scroll", "pin"];
|
|
6608
6831
|
var COMMON = {
|
|
@@ -6679,12 +6902,12 @@ var kuinetic = (() => {
|
|
|
6679
6902
|
* What the ring rules actually write (`ambient.css`) beyond the gradient: `mask` +
|
|
6680
6903
|
* `mask-composite`, which subtract the element's own content box to leave a ring — the same
|
|
6681
6904
|
* physical property `media-mask` claims under the `'mask'` channel — and `position: relative`
|
|
6682
|
-
* plus a `padding` that *is* the ring's thickness
|
|
6683
|
-
*
|
|
6684
|
-
* `
|
|
6685
|
-
*
|
|
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`.
|
|
6686
6909
|
*/
|
|
6687
|
-
cssPrimitive("ambient-gradient-ring", [CHANNEL.background, "mask", "
|
|
6910
|
+
cssPrimitive("ambient-gradient-ring", [CHANNEL.background, "mask", "position", "padding"], {
|
|
6688
6911
|
parameters: drift,
|
|
6689
6912
|
defaultActivation: "load",
|
|
6690
6913
|
reducedMotion: "disable",
|
|
@@ -7362,22 +7585,287 @@ var kuinetic = (() => {
|
|
|
7362
7585
|
var STATE_PRIMITIVES = [...PRESS_PRIMITIVES, ...GROUP_PRIMITIVES];
|
|
7363
7586
|
var STATE_PRESETS = [...PRESS_PRESETS, ...GROUP_PRESETS];
|
|
7364
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
|
+
|
|
7365
7634
|
// src/effects/catalog/interaction-reveal.ts
|
|
7366
|
-
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
|
+
};
|
|
7367
7640
|
return {
|
|
7368
7641
|
id,
|
|
7369
7642
|
renderer: "javascript",
|
|
7370
|
-
|
|
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,
|
|
7371
7645
|
parameters,
|
|
7372
7646
|
supportedTimelines: ["time"],
|
|
7373
7647
|
supportedActivations: ["load"],
|
|
7374
7648
|
defaultActivation: "load",
|
|
7375
7649
|
perfClass,
|
|
7376
7650
|
reducedMotion: "shorten",
|
|
7377
|
-
prepare:
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
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
|
+
}
|
|
7381
7869
|
};
|
|
7382
7870
|
}
|
|
7383
7871
|
var labelSwapParams = {
|
|
@@ -7410,10 +7898,34 @@ var kuinetic = (() => {
|
|
|
7410
7898
|
duration: { type: "time", default: "160ms", cssProperty: "--kui-duration" },
|
|
7411
7899
|
delay: { type: "time", default: "1000ms", cssProperty: "--kui-delay" },
|
|
7412
7900
|
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" },
|
|
7413
|
-
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" }
|
|
7414
7917
|
};
|
|
7415
7918
|
var HOVER_INTENT_PRIMITIVES = [
|
|
7416
|
-
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
|
+
})
|
|
7417
7929
|
];
|
|
7418
7930
|
var HOVER_INTENT_PRESETS = [
|
|
7419
7931
|
{ name: "hover-intent", phase: "state", primitive: "hover-intent" }
|
|
@@ -7431,16 +7943,65 @@ var kuinetic = (() => {
|
|
|
7431
7943
|
finite: true,
|
|
7432
7944
|
minimum: 0,
|
|
7433
7945
|
maximum: 1
|
|
7434
|
-
}
|
|
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" }
|
|
7435
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
|
+
}
|
|
7436
7973
|
var ANCHORED_PREVIEW_PRIMITIVES = [
|
|
7437
|
-
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
|
+
})
|
|
7438
7984
|
];
|
|
7439
7985
|
var ANCHORED_PREVIEW_PRESETS = [
|
|
7440
7986
|
{ name: "anchored-preview", phase: "state", primitive: "anchored-preview" },
|
|
7441
|
-
{
|
|
7442
|
-
|
|
7443
|
-
|
|
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
|
+
}
|
|
7444
8005
|
];
|
|
7445
8006
|
var searchExpandParams = {
|
|
7446
8007
|
duration: { type: "time", default: "260ms", cssProperty: "--kui-duration" },
|
|
@@ -7458,7 +8019,7 @@ var kuinetic = (() => {
|
|
|
7458
8019
|
"search-expand",
|
|
7459
8020
|
["expand", "discrete", "search-field"],
|
|
7460
8021
|
searchExpandParams,
|
|
7461
|
-
"layout"
|
|
8022
|
+
{ perfClass: "layout" }
|
|
7462
8023
|
)
|
|
7463
8024
|
];
|
|
7464
8025
|
var SEARCH_EXPAND_PRESETS = [
|
|
@@ -7874,7 +8435,10 @@ var kuinetic = (() => {
|
|
|
7874
8435
|
label: { type: "text", default: "", cssProperty: "--kui-label" }
|
|
7875
8436
|
};
|
|
7876
8437
|
var POINTER_PRIMITIVES = [
|
|
7877
|
-
|
|
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)),
|
|
7878
8442
|
pointerPrimitive("tilt-parallax", ["translate"], parallaxParams, deferPrepare(prepareTiltParallax)),
|
|
7879
8443
|
pointerPrimitive("cursor-follow", ["translate"], cursorDotParams, deferPrepare(prepareCursorFollow)),
|
|
7880
8444
|
pointerPrimitive("cursor-lag", ["translate"], cursorDotParams, deferPrepare(prepareCursorLag)),
|
|
@@ -7883,7 +8447,13 @@ var kuinetic = (() => {
|
|
|
7883
8447
|
// `pseudo-before`: the glow overlay is a `::before` rule in `interaction.css`, so this primitive
|
|
7884
8448
|
// owns that box the same way the border family does. See `border-draw`'s comment in
|
|
7885
8449
|
// `HOVER_PRIMITIVES` for what the token means and why it was added here in the same change.
|
|
7886
|
-
|
|
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
|
+
)
|
|
7887
8457
|
];
|
|
7888
8458
|
function prepareCursorFollow(el, params, ctx) {
|
|
7889
8459
|
return prepareCursorDot(el, params, ctx, "kui-cursor-dot-follow");
|
|
@@ -8363,52 +8933,6 @@ var kuinetic = (() => {
|
|
|
8363
8933
|
};
|
|
8364
8934
|
}
|
|
8365
8935
|
|
|
8366
|
-
// src/core/element-size.ts
|
|
8367
|
-
function watchElementSize(win, onChange) {
|
|
8368
|
-
const ResizeObserverCtor = win.ResizeObserver;
|
|
8369
|
-
const observer = ResizeObserverCtor ? new ResizeObserverCtor(onChange) : void 0;
|
|
8370
|
-
const elements = /* @__PURE__ */ new Set();
|
|
8371
|
-
win.addEventListener("resize", onChange, { passive: true });
|
|
8372
|
-
return {
|
|
8373
|
-
observe(element) {
|
|
8374
|
-
if (elements.has(element)) return;
|
|
8375
|
-
elements.add(element);
|
|
8376
|
-
observer?.observe(element);
|
|
8377
|
-
},
|
|
8378
|
-
unobserve(element) {
|
|
8379
|
-
elements.delete(element);
|
|
8380
|
-
observer?.unobserve(element);
|
|
8381
|
-
},
|
|
8382
|
-
disconnect() {
|
|
8383
|
-
win.removeEventListener("resize", onChange);
|
|
8384
|
-
observer?.disconnect();
|
|
8385
|
-
elements.clear();
|
|
8386
|
-
}
|
|
8387
|
-
};
|
|
8388
|
-
}
|
|
8389
|
-
function frameScheduler(win, callback) {
|
|
8390
|
-
let frame;
|
|
8391
|
-
let timeout;
|
|
8392
|
-
const run = () => {
|
|
8393
|
-
frame = void 0;
|
|
8394
|
-
timeout = void 0;
|
|
8395
|
-
callback();
|
|
8396
|
-
};
|
|
8397
|
-
return {
|
|
8398
|
-
request() {
|
|
8399
|
-
if (frame !== void 0 || timeout !== void 0) return;
|
|
8400
|
-
if (win.requestAnimationFrame) frame = win.requestAnimationFrame(run);
|
|
8401
|
-
else timeout = win.setTimeout(run, 16);
|
|
8402
|
-
},
|
|
8403
|
-
cancel() {
|
|
8404
|
-
if (frame !== void 0) win.cancelAnimationFrame(frame);
|
|
8405
|
-
if (timeout !== void 0) win.clearTimeout(timeout);
|
|
8406
|
-
frame = void 0;
|
|
8407
|
-
timeout = void 0;
|
|
8408
|
-
}
|
|
8409
|
-
};
|
|
8410
|
-
}
|
|
8411
|
-
|
|
8412
8936
|
// src/effects/catalog/media-shared.ts
|
|
8413
8937
|
var AXIS_DEGREES = { vertical: 0, horizontal: 90 };
|
|
8414
8938
|
var UNIT_DEGREES = { deg: 1, d: 1, grad: 0.9, rad: 180 / Math.PI, turn: 360 };
|
|
@@ -8767,14 +9291,14 @@ var kuinetic = (() => {
|
|
|
8767
9291
|
settle2 = resolve;
|
|
8768
9292
|
});
|
|
8769
9293
|
let landed = false;
|
|
8770
|
-
const
|
|
9294
|
+
const land2 = () => {
|
|
8771
9295
|
if (landed) return;
|
|
8772
9296
|
landed = true;
|
|
8773
9297
|
stage.classList.remove("kui-slat-animating");
|
|
8774
9298
|
built.restore();
|
|
8775
9299
|
settle2();
|
|
8776
9300
|
};
|
|
8777
|
-
const timer = ctx.win.setTimeout(
|
|
9301
|
+
const timer = ctx.win.setTimeout(land2, slatAssembleFinishMs(params, count));
|
|
8778
9302
|
return {
|
|
8779
9303
|
cleanup: () => {
|
|
8780
9304
|
ctx.win.clearTimeout(timer);
|
|
@@ -8785,7 +9309,7 @@ var kuinetic = (() => {
|
|
|
8785
9309
|
finished,
|
|
8786
9310
|
finish: () => {
|
|
8787
9311
|
ctx.win.clearTimeout(timer);
|
|
8788
|
-
|
|
9312
|
+
land2();
|
|
8789
9313
|
}
|
|
8790
9314
|
};
|
|
8791
9315
|
}
|
|
@@ -8920,7 +9444,9 @@ var kuinetic = (() => {
|
|
|
8920
9444
|
{
|
|
8921
9445
|
id: "slat-assemble",
|
|
8922
9446
|
renderer: "javascript",
|
|
8923
|
-
|
|
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],
|
|
8924
9450
|
parameters: slatParams,
|
|
8925
9451
|
supportedTimelines: ["time"],
|
|
8926
9452
|
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
@@ -8944,12 +9470,15 @@ var kuinetic = (() => {
|
|
|
8944
9470
|
* and it is what makes `background-media, video-scrub` on one element a reported conflict
|
|
8945
9471
|
* rather than two effects silently fighting over the same picture.
|
|
8946
9472
|
*
|
|
8947
|
-
* `
|
|
8948
|
-
*
|
|
8949
|
-
*
|
|
8950
|
-
*
|
|
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.
|
|
8951
9480
|
*/
|
|
8952
|
-
channels: ["media", "
|
|
9481
|
+
channels: ["media", "position", "isolation", SUBTREE_CHANNEL],
|
|
8953
9482
|
renderer: "javascript",
|
|
8954
9483
|
parameters: backgroundMediaParams,
|
|
8955
9484
|
// Not a claim to support four timelines — an abstention. A backdrop is not driven by progress
|
|
@@ -9478,7 +10007,9 @@ var kuinetic = (() => {
|
|
|
9478
10007
|
return {
|
|
9479
10008
|
id,
|
|
9480
10009
|
renderer: "javascript",
|
|
9481
|
-
|
|
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],
|
|
9482
10013
|
parameters,
|
|
9483
10014
|
supportedTimelines: ["time"],
|
|
9484
10015
|
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
@@ -9866,7 +10397,9 @@ var kuinetic = (() => {
|
|
|
9866
10397
|
return {
|
|
9867
10398
|
id,
|
|
9868
10399
|
renderer: "javascript",
|
|
9869
|
-
|
|
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],
|
|
9870
10403
|
parameters: options.parameters,
|
|
9871
10404
|
supportedTimelines: ["time"],
|
|
9872
10405
|
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
@@ -9996,21 +10529,21 @@ var kuinetic = (() => {
|
|
|
9996
10529
|
const finished = new Promise((resolve) => {
|
|
9997
10530
|
settle2 = resolve;
|
|
9998
10531
|
});
|
|
9999
|
-
const
|
|
10532
|
+
const land2 = () => {
|
|
10000
10533
|
ctx.win.clearTimeout(timer);
|
|
10001
10534
|
timer = void 0;
|
|
10002
10535
|
layers.decorative.removeAttribute("aria-hidden");
|
|
10003
10536
|
layers.srOnly.remove();
|
|
10004
10537
|
settle2();
|
|
10005
10538
|
};
|
|
10006
|
-
let timer = ctx.win.setTimeout(
|
|
10539
|
+
let timer = ctx.win.setTimeout(land2, splitRevealFinishMs(params, count));
|
|
10007
10540
|
return {
|
|
10008
10541
|
finished,
|
|
10009
|
-
land,
|
|
10542
|
+
land: land2,
|
|
10010
10543
|
rearm(nextCount) {
|
|
10011
10544
|
if (timer === void 0) return;
|
|
10012
10545
|
ctx.win.clearTimeout(timer);
|
|
10013
|
-
timer = ctx.win.setTimeout(
|
|
10546
|
+
timer = ctx.win.setTimeout(land2, splitRevealFinishMs(params, nextCount));
|
|
10014
10547
|
},
|
|
10015
10548
|
cancel() {
|
|
10016
10549
|
if (timer !== void 0) ctx.win.clearTimeout(timer);
|
|
@@ -10249,7 +10782,8 @@ var kuinetic = (() => {
|
|
|
10249
10782
|
prepare: deferPrepare(prepareTypewriter),
|
|
10250
10783
|
perfClass: "continuous"
|
|
10251
10784
|
}),
|
|
10252
|
-
|
|
10785
|
+
// `min-width`/`min-height`: the rest box it pins while the glyphs churn (`prepareScramble`).
|
|
10786
|
+
jsTextPrimitive("scramble-text", ["content", "min-width", "min-height"], {
|
|
10253
10787
|
parameters: scrambleParams,
|
|
10254
10788
|
prepare: deferPrepare(prepareScramble),
|
|
10255
10789
|
perfClass: "continuous"
|
|
@@ -10658,6 +11192,36 @@ var kuinetic = (() => {
|
|
|
10658
11192
|
return registry;
|
|
10659
11193
|
}
|
|
10660
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
|
+
|
|
10661
11225
|
// src/effects/step-marking.ts
|
|
10662
11226
|
var STEP_STATE_ATTR = "data-kui-step-state";
|
|
10663
11227
|
var STEP_OFFSET_ATTR = "data-kui-step-offset";
|
|
@@ -10746,28 +11310,49 @@ var kuinetic = (() => {
|
|
|
10746
11310
|
}
|
|
10747
11311
|
return Math.max(1, ...groups.values());
|
|
10748
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
|
+
}
|
|
10749
11320
|
function delegateControls(request) {
|
|
10750
|
-
const { el, ctx,
|
|
11321
|
+
const { el, ctx, groups } = request;
|
|
10751
11322
|
if (groups.length === 0) return () => {
|
|
10752
11323
|
};
|
|
10753
|
-
const
|
|
10754
|
-
const onClick = (event) => {
|
|
10755
|
-
const from = event.target;
|
|
10756
|
-
if (typeof from?.closest !== "function") return;
|
|
11324
|
+
const press = (from, inside) => {
|
|
10757
11325
|
for (const { selector, run } of groups) {
|
|
10758
|
-
const matches =
|
|
11326
|
+
const matches = queryControls(el, ctx, selector);
|
|
10759
11327
|
const matched = new Set(matches);
|
|
11328
|
+
const stop = inside ? el : null;
|
|
10760
11329
|
let node = from;
|
|
10761
|
-
while (node && !matched.has(node)) node = node.parentElement;
|
|
10762
|
-
if (!node) continue;
|
|
11330
|
+
while (node && node !== stop && !matched.has(node)) node = node.parentElement;
|
|
11331
|
+
if (!node || node === stop) continue;
|
|
10763
11332
|
run(node, matches.indexOf(node));
|
|
10764
11333
|
}
|
|
10765
11334
|
};
|
|
10766
|
-
|
|
10767
|
-
|
|
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
|
+
};
|
|
10768
11353
|
}
|
|
10769
11354
|
function createStepIndex(options) {
|
|
10770
|
-
const { el, params, ctx,
|
|
11355
|
+
const { el, params, ctx, resolveSteps, onRender } = options;
|
|
10771
11356
|
const name = options.name ?? "step-progress";
|
|
10772
11357
|
const marker = createStepMarker(resolveSteps, (message) => ctx.warn(`${name} ${message}`));
|
|
10773
11358
|
const self = createAttributeLedger(el);
|
|
@@ -10786,26 +11371,23 @@ var kuinetic = (() => {
|
|
|
10786
11371
|
};
|
|
10787
11372
|
const next = () => goTo(nextStep(step, total()));
|
|
10788
11373
|
const prev = () => goTo(prevStep(step, total()));
|
|
10789
|
-
const
|
|
10790
|
-
|
|
10791
|
-
|
|
10792
|
-
if (!selector) return false;
|
|
10793
|
-
if (queryScoped(el, ctx, selector, scope).length === 0) {
|
|
10794
|
-
ctx.warn(`${name} ${param} "${selector}" matched nothing`);
|
|
10795
|
-
}
|
|
10796
|
-
groups.push({ selector, run });
|
|
10797
|
-
return true;
|
|
11374
|
+
const byHand = (move) => {
|
|
11375
|
+
options.onInput?.();
|
|
11376
|
+
move();
|
|
10798
11377
|
};
|
|
10799
|
-
const
|
|
10800
|
-
|
|
10801
|
-
bindControl("
|
|
10802
|
-
bindControl("
|
|
10803
|
-
|
|
10804
|
-
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
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 });
|
|
10809
11391
|
render();
|
|
10810
11392
|
return {
|
|
10811
11393
|
goTo,
|
|
@@ -10814,7 +11396,7 @@ var kuinetic = (() => {
|
|
|
10814
11396
|
current: () => step,
|
|
10815
11397
|
total,
|
|
10816
11398
|
release: () => {
|
|
10817
|
-
if (fallback) el.removeEventListener("click",
|
|
11399
|
+
if (fallback) el.removeEventListener("click", onContainerClick);
|
|
10818
11400
|
releaseControls();
|
|
10819
11401
|
self.restore();
|
|
10820
11402
|
selfStyle.restore();
|
|
@@ -10823,24 +11405,300 @@ var kuinetic = (() => {
|
|
|
10823
11405
|
};
|
|
10824
11406
|
}
|
|
10825
11407
|
|
|
10826
|
-
// src/effects/
|
|
10827
|
-
var
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
|
|
10840
|
-
|
|
10841
|
-
|
|
10842
|
-
|
|
10843
|
-
|
|
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",
|
|
11696
|
+
channels: [CHANNEL.translate, CHANNEL.scale, CHANNEL.opacity, CHANNEL.stroke, CHANNEL.color],
|
|
11697
|
+
parameters: {},
|
|
11698
|
+
supportedTimelines: ["time"],
|
|
11699
|
+
supportedActivations: ["load"],
|
|
11700
|
+
defaultActivation: "load",
|
|
11701
|
+
perfClass: "compositor",
|
|
10844
11702
|
// Native pseudo-classes drive these and the motion lands on a sibling, not on the control that
|
|
10845
11703
|
// carries the attribute, so base.css's policy layer enforces this through its sibling
|
|
10846
11704
|
// `transition-duration` rules rather than the `animation-*` ones.
|
|
@@ -10943,7 +11801,7 @@ var kuinetic = (() => {
|
|
|
10943
11801
|
}
|
|
10944
11802
|
var STRENGTH_METER_PRIMITIVE = jsInputPrimitive(
|
|
10945
11803
|
"strength-meter",
|
|
10946
|
-
["meter"],
|
|
11804
|
+
["meter", attributeChannel("data-kui-strength-level")],
|
|
10947
11805
|
{},
|
|
10948
11806
|
deferPrepare(prepareStrengthMeter)
|
|
10949
11807
|
);
|
|
@@ -10954,14 +11812,55 @@ var kuinetic = (() => {
|
|
|
10954
11812
|
deferPrepare(prepareRangeFill)
|
|
10955
11813
|
);
|
|
10956
11814
|
function prepareStepProgress(el, params, ctx) {
|
|
10957
|
-
const
|
|
11815
|
+
const label = "step-progress";
|
|
11816
|
+
const selector = resolveTarget(params.text("target"), ctx, label);
|
|
10958
11817
|
const scope = scopeParam(params, "page");
|
|
10959
11818
|
const resolveSteps = () => selector ? queryScoped(el, ctx, selector, scope) : el.children;
|
|
10960
|
-
|
|
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
|
+
};
|
|
10961
11858
|
}
|
|
10962
11859
|
var STEP_PROGRESS_BASE = jsInputPrimitive(
|
|
10963
11860
|
"step-progress",
|
|
10964
|
-
|
|
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")],
|
|
10965
11864
|
{
|
|
10966
11865
|
// Empty default, not '4': `readParams` fills every declared default in unconditionally and
|
|
10967
11866
|
// does not validate it, so an empty one is how `prepareStepProgress` tells "unauthored" from
|
|
@@ -11008,6 +11907,16 @@ var kuinetic = (() => {
|
|
|
11008
11907
|
* at 390px and at 1440px, and the page keeps owning the box. Same reason `rest:` is a number.
|
|
11009
11908
|
*/
|
|
11010
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,
|
|
11011
11920
|
// Which tree `target:` is searched in. Unset means this primitive's own historical answer —
|
|
11012
11921
|
// see `prepareStepProgress`. One declaration, shared: `effects/step-marking.ts`.
|
|
11013
11922
|
scope: SCOPE_PARAM
|
|
@@ -11054,7 +11963,7 @@ var kuinetic = (() => {
|
|
|
11054
11963
|
}
|
|
11055
11964
|
var SUBMIT_FLOW_PRIMITIVE = jsInputPrimitive(
|
|
11056
11965
|
"submit-flow",
|
|
11057
|
-
["
|
|
11966
|
+
[attributeChannel("data-kui-stage")],
|
|
11058
11967
|
{
|
|
11059
11968
|
load: { type: "time", default: "1200ms", cssProperty: "--kui-load" },
|
|
11060
11969
|
hold: { type: "time", default: "1500ms", cssProperty: "--kui-hold" }
|
|
@@ -11104,9 +12013,12 @@ var kuinetic = (() => {
|
|
|
11104
12013
|
* two readings is the stylesheet. `step-progress` was the wrong word to type on a deck of
|
|
11105
12014
|
* slides, which is the only reason this alias exists; nothing behaves differently under it.
|
|
11106
12015
|
*
|
|
11107
|
-
* It is still an *index*, not a carousel component: no ARIA, no roving focus
|
|
11108
|
-
*
|
|
11109
|
-
*
|
|
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`).
|
|
11110
12022
|
*
|
|
11111
12023
|
* No `requiresOwnSubtree` here, unlike `step-progress` beside it. That flag means "this name's
|
|
11112
12024
|
* shipped CSS reaches past the element into its descendants, so the universal `target:` must
|
|
@@ -11185,6 +12097,17 @@ var kuinetic = (() => {
|
|
|
11185
12097
|
longPressTimer = null;
|
|
11186
12098
|
}
|
|
11187
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
|
+
}
|
|
11188
12111
|
origin = sampleOf(event);
|
|
11189
12112
|
samples = [origin];
|
|
11190
12113
|
active = false;
|
|
@@ -11268,7 +12191,9 @@ var kuinetic = (() => {
|
|
|
11268
12191
|
stopListeningOnDocument();
|
|
11269
12192
|
releaseCapture(event.pointerId);
|
|
11270
12193
|
if (!origin) return;
|
|
11271
|
-
|
|
12194
|
+
finish(sampleOf(event));
|
|
12195
|
+
}
|
|
12196
|
+
function finish(sample) {
|
|
11272
12197
|
samples.push(sample);
|
|
11273
12198
|
reportEnd(vectorNow(sample), sample);
|
|
11274
12199
|
origin = null;
|
|
@@ -11277,6 +12202,7 @@ var kuinetic = (() => {
|
|
|
11277
12202
|
samples = [];
|
|
11278
12203
|
}
|
|
11279
12204
|
function onLostCapture(event) {
|
|
12205
|
+
if (event.target !== el) return;
|
|
11280
12206
|
if (!captured || event.pointerId !== pointerId) return;
|
|
11281
12207
|
captured = false;
|
|
11282
12208
|
onUp(event);
|
|
@@ -11431,16 +12357,19 @@ var kuinetic = (() => {
|
|
|
11431
12357
|
runners.x.to(offset.x + vector.vx * carry);
|
|
11432
12358
|
runners.y.to(offset.y + vector.vy * carry);
|
|
11433
12359
|
}
|
|
11434
|
-
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`);
|
|
11435
12363
|
const stop = recognise(
|
|
11436
12364
|
el,
|
|
11437
12365
|
{
|
|
11438
12366
|
onSwipe(direction) {
|
|
11439
12367
|
el.setAttribute("data-kui-swipe", direction);
|
|
12368
|
+
announceSwipe(ctx.win, el, direction);
|
|
11440
12369
|
}
|
|
11441
12370
|
},
|
|
11442
12371
|
{
|
|
11443
|
-
axis
|
|
12372
|
+
axis,
|
|
11444
12373
|
swipeVelocity: params.num("velocity", 300),
|
|
11445
12374
|
// This primitive moves nothing, so it must not hold the pointer from `pointerdown` — that
|
|
11446
12375
|
// retargets the following `click` at this element and kills every interactive child (a
|
|
@@ -11506,7 +12435,7 @@ var kuinetic = (() => {
|
|
|
11506
12435
|
var GESTURE_PRIMITIVES = [
|
|
11507
12436
|
gesturePrimitive(
|
|
11508
12437
|
"draggable",
|
|
11509
|
-
["translate"],
|
|
12438
|
+
["translate", attributeChannel("data-kui-dragging")],
|
|
11510
12439
|
{
|
|
11511
12440
|
...springParams2,
|
|
11512
12441
|
axis: { type: "keyword", default: "both", cssProperty: "--kui-axis", keywords: ["x", "y", "both"] },
|
|
@@ -11537,7 +12466,7 @@ var kuinetic = (() => {
|
|
|
11537
12466
|
),
|
|
11538
12467
|
gesturePrimitive(
|
|
11539
12468
|
"swipeable",
|
|
11540
|
-
["
|
|
12469
|
+
[attributeChannel("data-kui-swipe")],
|
|
11541
12470
|
{
|
|
11542
12471
|
axis: { type: "keyword", default: "both", cssProperty: "--kui-axis", keywords: ["x", "y", "both"] },
|
|
11543
12472
|
velocity: { type: "number", default: "300", cssProperty: "--kui-velocity" }
|
|
@@ -11546,7 +12475,7 @@ var kuinetic = (() => {
|
|
|
11546
12475
|
),
|
|
11547
12476
|
gesturePrimitive(
|
|
11548
12477
|
"pressable",
|
|
11549
|
-
["
|
|
12478
|
+
[attributeChannel("data-kui-pressed")],
|
|
11550
12479
|
{ duration: { type: "time", default: "500ms", cssProperty: "--kui-duration" } },
|
|
11551
12480
|
// `duration` here is the hold threshold, not a span of motion — `long-press 800ms` means
|
|
11552
12481
|
// "count it once the finger has been down for 800ms". It is read (see `preparePressable`), so
|
|
@@ -11597,6 +12526,7 @@ var kuinetic = (() => {
|
|
|
11597
12526
|
},
|
|
11598
12527
|
{ name: "swipe", primitive: "swipeable", phase: "state" },
|
|
11599
12528
|
{ name: "swipe-x", primitive: "swipeable", params: { axis: "x" }, phase: "state" },
|
|
12529
|
+
{ name: "swipe-y", primitive: "swipeable", params: { axis: "y" }, phase: "state" },
|
|
11600
12530
|
{ name: "long-press", primitive: "pressable", phase: "state" },
|
|
11601
12531
|
{ name: "magnetic", primitive: "magnetic", phase: "state" },
|
|
11602
12532
|
{
|
|
@@ -11892,13 +12822,15 @@ var kuinetic = (() => {
|
|
|
11892
12822
|
),
|
|
11893
12823
|
layoutPrimitive(
|
|
11894
12824
|
"auto-height",
|
|
11895
|
-
|
|
12825
|
+
// The two properties it writes on the panel: `overflow: hidden`, and the `height` it animates.
|
|
12826
|
+
["overflow", "height"],
|
|
11896
12827
|
{ attribute: { type: "text", default: "data-open", cssProperty: "--kui-attribute" } },
|
|
11897
12828
|
deferPrepare(prepareAutoHeight)
|
|
11898
12829
|
),
|
|
11899
12830
|
layoutPrimitive(
|
|
11900
12831
|
"flip-indicator",
|
|
11901
|
-
|
|
12832
|
+
// The bar's landing spot (`translate`) and its `width`, matched to the selected tab.
|
|
12833
|
+
["translate", "width"],
|
|
11902
12834
|
{
|
|
11903
12835
|
follow: { type: "text", default: "", cssProperty: "--kui-follow" },
|
|
11904
12836
|
attribute: { type: "text", default: "aria-selected", cssProperty: "--kui-attribute" }
|
|
@@ -12147,26 +13079,25 @@ var kuinetic = (() => {
|
|
|
12147
13079
|
var NAV_JS_PRIMITIVES = [
|
|
12148
13080
|
navPrimitive(
|
|
12149
13081
|
"header-shrink",
|
|
12150
|
-
//
|
|
12151
|
-
//
|
|
12152
|
-
//
|
|
12153
|
-
//
|
|
12154
|
-
//
|
|
12155
|
-
//
|
|
12156
|
-
|
|
12157
|
-
["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")],
|
|
12158
13089
|
{ offset: { type: "number", default: "120", cssProperty: "--kui-offset" } },
|
|
12159
13090
|
deferPrepare(prepareHeaderShrink)
|
|
12160
13091
|
),
|
|
12161
13092
|
navPrimitive(
|
|
12162
13093
|
"header-hide-on-scroll",
|
|
12163
|
-
[CHANNEL.translate],
|
|
13094
|
+
[CHANNEL.translate, attributeChannel("data-kui-hidden")],
|
|
12164
13095
|
{ offset: { type: "number", default: "8", cssProperty: "--kui-offset" } },
|
|
12165
13096
|
deferPrepare(prepareHeaderHide)
|
|
12166
13097
|
),
|
|
12167
13098
|
navPrimitive(
|
|
12168
13099
|
"back-to-top-fade",
|
|
12169
|
-
[CHANNEL.opacity, CHANNEL.translate],
|
|
13100
|
+
[CHANNEL.opacity, CHANNEL.translate, attributeChannel("data-kui-visible")],
|
|
12170
13101
|
{ offset: { type: "number", default: "400", cssProperty: "--kui-offset" } },
|
|
12171
13102
|
deferPrepare(prepareBackToTop)
|
|
12172
13103
|
)
|
|
@@ -12729,7 +13660,10 @@ var kuinetic = (() => {
|
|
|
12729
13660
|
var SCROLL_PRIMITIVES = [
|
|
12730
13661
|
scrollPrimitive({
|
|
12731
13662
|
id: "pin",
|
|
12732
|
-
|
|
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")],
|
|
12733
13667
|
parameters: {
|
|
12734
13668
|
...distanceParam2,
|
|
12735
13669
|
...stickyParams
|
|
@@ -12739,7 +13673,13 @@ var kuinetic = (() => {
|
|
|
12739
13673
|
}),
|
|
12740
13674
|
scrollPrimitive({
|
|
12741
13675
|
id: "scroll-progress",
|
|
12742
|
-
|
|
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")],
|
|
12743
13683
|
parameters: {
|
|
12744
13684
|
...distanceParam2,
|
|
12745
13685
|
steps: { type: "number", default: "0", cssProperty: "--kui-steps" },
|
|
@@ -12775,7 +13715,8 @@ var kuinetic = (() => {
|
|
|
12775
13715
|
}),
|
|
12776
13716
|
scrollPrimitive({
|
|
12777
13717
|
id: "media-scrub",
|
|
12778
|
-
|
|
13718
|
+
// `position`: it holds its host the way a pin does (`position: sticky` and its `top`).
|
|
13719
|
+
channels: ["media", "progress", "position"],
|
|
12779
13720
|
parameters: {
|
|
12780
13721
|
...distanceParam2,
|
|
12781
13722
|
// A scrub is a hold, so it needs the same two knobs a pin does. Declaring them here is what
|
|
@@ -12794,7 +13735,7 @@ var kuinetic = (() => {
|
|
|
12794
13735
|
}),
|
|
12795
13736
|
scrollPrimitive({
|
|
12796
13737
|
id: "scroll-spy",
|
|
12797
|
-
channels: ["
|
|
13738
|
+
channels: [attributeChannel("data-kui-active")],
|
|
12798
13739
|
parameters: {
|
|
12799
13740
|
// `distance`: the per-section form only. `offset-top`: the container form only. Each is a
|
|
12800
13741
|
// no-op — warned, not silent — in the other; see `prepareScrollSpySingle` and
|
|
@@ -12820,8 +13761,8 @@ var kuinetic = (() => {
|
|
|
12820
13761
|
scrollPrimitive({
|
|
12821
13762
|
id: "smooth-scroll",
|
|
12822
13763
|
/*
|
|
12823
|
-
* Its own channel, not the `'layout'` it used to share with `pin`, `stacking-cards` and
|
|
12824
|
-
* `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
|
|
12825
13766
|
* property, and this one writes exactly `scroll-behavior` — a property that describes how a
|
|
12826
13767
|
* *user-or-script-initiated* scroll is performed, and that no other primitive touches.
|
|
12827
13768
|
*
|
|
@@ -12842,7 +13783,11 @@ var kuinetic = (() => {
|
|
|
12842
13783
|
}),
|
|
12843
13784
|
scrollPrimitive({
|
|
12844
13785
|
id: "scroll-snap",
|
|
12845
|
-
|
|
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],
|
|
12846
13791
|
parameters: {
|
|
12847
13792
|
axis: { type: "keyword", default: "y", cssProperty: "--kui-axis", keywords: ["x", "y"] },
|
|
12848
13793
|
strictness: {
|
|
@@ -13184,7 +14129,7 @@ var kuinetic = (() => {
|
|
|
13184
14129
|
return children.filter((child) => child.textContent.trim() === "");
|
|
13185
14130
|
}
|
|
13186
14131
|
function withIconParts(inner) {
|
|
13187
|
-
return (el, params, ctx) => {
|
|
14132
|
+
return inheritTimingContract(inner, (el, params, ctx) => {
|
|
13188
14133
|
if (!el.querySelector(".kui-bar")) {
|
|
13189
14134
|
const bars = findBars(el);
|
|
13190
14135
|
if (bars.length > 0) {
|
|
@@ -13203,7 +14148,7 @@ var kuinetic = (() => {
|
|
|
13203
14148
|
}
|
|
13204
14149
|
}
|
|
13205
14150
|
return inner(el, params, ctx);
|
|
13206
|
-
};
|
|
14151
|
+
});
|
|
13207
14152
|
}
|
|
13208
14153
|
|
|
13209
14154
|
// src/effects/svg/index.ts
|
|
@@ -13242,18 +14187,18 @@ var kuinetic = (() => {
|
|
|
13242
14187
|
};
|
|
13243
14188
|
let current = 0;
|
|
13244
14189
|
const enter = () => drive(1);
|
|
13245
|
-
const
|
|
14190
|
+
const leave2 = () => drive(0);
|
|
13246
14191
|
el.addEventListener("pointerenter", enter, { passive: true });
|
|
13247
14192
|
el.addEventListener("focusin", enter, { passive: true });
|
|
13248
|
-
el.addEventListener("pointerleave",
|
|
13249
|
-
el.addEventListener("focusout",
|
|
14193
|
+
el.addEventListener("pointerleave", leave2, { passive: true });
|
|
14194
|
+
el.addEventListener("focusout", leave2, { passive: true });
|
|
13250
14195
|
return () => {
|
|
13251
14196
|
cancelled = true;
|
|
13252
14197
|
cancelAnimationFrame(frame);
|
|
13253
14198
|
el.removeEventListener("pointerenter", enter);
|
|
13254
14199
|
el.removeEventListener("focusin", enter);
|
|
13255
|
-
el.removeEventListener("pointerleave",
|
|
13256
|
-
el.removeEventListener("focusout",
|
|
14200
|
+
el.removeEventListener("pointerleave", leave2);
|
|
14201
|
+
el.removeEventListener("focusout", leave2);
|
|
13257
14202
|
path2.setAttribute("d", startPath);
|
|
13258
14203
|
};
|
|
13259
14204
|
}
|
|
@@ -13274,7 +14219,16 @@ var kuinetic = (() => {
|
|
|
13274
14219
|
var ICON_TOGGLE_PRIMITIVE = {
|
|
13275
14220
|
id: "icon-toggle",
|
|
13276
14221
|
renderer: "javascript",
|
|
13277
|
-
|
|
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
|
+
],
|
|
13278
14232
|
parameters: {
|
|
13279
14233
|
duration: { type: "time", default: "260ms", cssProperty: "--kui-duration" },
|
|
13280
14234
|
// The state flip is the start moment: `aria-expanded` goes true and the bars begin to move.
|
|
@@ -13472,8 +14426,10 @@ var kuinetic = (() => {
|
|
|
13472
14426
|
// `'discrete'` alongside `rotate`: the unconditional `[data-kui-fx~='flip-card']` rule pins
|
|
13473
14427
|
// `display: grid` so the two faces stack in one cell instead of flowing as normal block
|
|
13474
14428
|
// siblings — unrelated to `catalog/discrete.ts`'s show/hide use of the same physical property,
|
|
13475
|
-
// but `display` is tracked as one channel regardless of the value written into it.
|
|
13476
|
-
|
|
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],
|
|
13477
14433
|
parameters: {
|
|
13478
14434
|
duration: { type: "time", default: "700ms", cssProperty: "--kui-duration" },
|
|
13479
14435
|
// The turn has a start moment — the click, or the pointer arriving — so a delay before it is
|
|
@@ -13611,8 +14567,8 @@ var kuinetic = (() => {
|
|
|
13611
14567
|
function snapTo(projected) {
|
|
13612
14568
|
return Math.round(projected) + 0;
|
|
13613
14569
|
}
|
|
13614
|
-
function wrapPlace(
|
|
13615
|
-
return total > 0 ? (
|
|
14570
|
+
function wrapPlace(place3, total) {
|
|
14571
|
+
return total > 0 ? (place3 % total + total) % total : 0;
|
|
13616
14572
|
}
|
|
13617
14573
|
var KEY_DELTAS = {
|
|
13618
14574
|
ArrowRight: 1,
|
|
@@ -13624,8 +14580,26 @@ var kuinetic = (() => {
|
|
|
13624
14580
|
Home: null,
|
|
13625
14581
|
End: null
|
|
13626
14582
|
};
|
|
13627
|
-
function
|
|
13628
|
-
|
|
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) {
|
|
13629
14603
|
let suppressClick = false;
|
|
13630
14604
|
const onClickCapture = (event) => {
|
|
13631
14605
|
if (!suppressClick) return;
|
|
@@ -13633,19 +14607,49 @@ var kuinetic = (() => {
|
|
|
13633
14607
|
event.stopPropagation();
|
|
13634
14608
|
event.preventDefault();
|
|
13635
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);
|
|
13636
14636
|
const stopRecognising = enabled ? recognise(
|
|
13637
14637
|
el,
|
|
13638
14638
|
{
|
|
13639
14639
|
onStart() {
|
|
14640
|
+
dragFrom = positionOf();
|
|
14641
|
+
sign = request.travelSign();
|
|
14642
|
+
selection.hold();
|
|
13640
14643
|
setDragging(true);
|
|
13641
14644
|
},
|
|
13642
14645
|
onMove(vector) {
|
|
13643
|
-
moveTo(
|
|
14646
|
+
moveTo(dragFrom - sign * vector.dx / travelPx);
|
|
13644
14647
|
},
|
|
13645
14648
|
onEnd(vector) {
|
|
14649
|
+
selection.release();
|
|
13646
14650
|
setDragging(false);
|
|
13647
|
-
|
|
13648
|
-
const settled = snapTo(projectRelease(positionOf(), vector.vx, travelPx));
|
|
14651
|
+
clicks.arm();
|
|
14652
|
+
const settled = snapTo(projectRelease(positionOf(), sign * vector.vx, travelPx));
|
|
13649
14653
|
moveTo(wrapPlace(settled, total()));
|
|
13650
14654
|
}
|
|
13651
14655
|
},
|
|
@@ -13654,7 +14658,15 @@ var kuinetic = (() => {
|
|
|
13654
14658
|
// threshold than `recognise`'s own default of 4px is what buys that — a ring occupies a
|
|
13655
14659
|
// large slab of a phone screen, and a scroll that begins with a two-pixel horizontal wobble
|
|
13656
14660
|
// should not capture the pointer away from the page.
|
|
13657
|
-
|
|
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" }
|
|
13658
14670
|
) : () => {
|
|
13659
14671
|
};
|
|
13660
14672
|
if (enabled) ctx.style.set("touch-action", "pan-y");
|
|
@@ -13673,216 +14685,213 @@ var kuinetic = (() => {
|
|
|
13673
14685
|
const grantedFocus = el.getAttribute("tabindex") === null;
|
|
13674
14686
|
if (grantedFocus) el.setAttribute("tabindex", "0");
|
|
13675
14687
|
el.addEventListener("keydown", onKeyDown);
|
|
13676
|
-
el.addEventListener("click", onClickCapture, true);
|
|
13677
14688
|
return () => {
|
|
13678
14689
|
stopRecognising();
|
|
14690
|
+
selection.release();
|
|
13679
14691
|
el.removeEventListener("keydown", onKeyDown);
|
|
13680
|
-
|
|
14692
|
+
clicks.release();
|
|
13681
14693
|
if (grantedFocus) el.removeAttribute("tabindex");
|
|
13682
14694
|
};
|
|
13683
14695
|
}
|
|
13684
14696
|
|
|
13685
|
-
// src/effects/carousel/
|
|
14697
|
+
// src/effects/carousel/deck.ts
|
|
13686
14698
|
var DRAGGING_ATTR = "data-kui-ring-dragging";
|
|
13687
|
-
var
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
["opacity", (value) => value !== "" && Number(value) < 1],
|
|
13693
|
-
["filter", (value) => value !== "" && value !== "none"],
|
|
13694
|
-
["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")
|
|
13695
14704
|
];
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
if (found) {
|
|
13702
|
-
ctx.warn(
|
|
13703
|
-
`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.`
|
|
13704
|
-
);
|
|
13705
|
-
return;
|
|
13706
|
-
}
|
|
13707
|
-
node = node.parentElement;
|
|
13708
|
-
}
|
|
13709
|
-
}
|
|
13710
|
-
function flatteningDeclaration(node, ctx) {
|
|
13711
|
-
const style = ctx.win.getComputedStyle?.(node);
|
|
13712
|
-
if (!style) return null;
|
|
13713
|
-
for (const [property2, flattens] of FLATTENING) {
|
|
13714
|
-
const value = style.getPropertyValue(property2);
|
|
13715
|
-
if (flattens(value)) return { property: property2, value };
|
|
13716
|
-
}
|
|
13717
|
-
return null;
|
|
13718
|
-
}
|
|
13719
|
-
function ledgersFor(store, node) {
|
|
13720
|
-
let attributes = store.attributes.get(node);
|
|
13721
|
-
if (!attributes) {
|
|
13722
|
-
attributes = createAttributeLedger(node);
|
|
13723
|
-
store.attributes.set(node, attributes);
|
|
13724
|
-
}
|
|
13725
|
-
let styles = store.styles.get(node);
|
|
13726
|
-
if (!styles) {
|
|
13727
|
-
styles = createStyleLedger(node);
|
|
13728
|
-
store.styles.set(node, styles);
|
|
13729
|
-
}
|
|
13730
|
-
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;
|
|
13731
14710
|
}
|
|
13732
|
-
function
|
|
13733
|
-
|
|
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;
|
|
13734
14715
|
}
|
|
13735
|
-
function
|
|
13736
|
-
|
|
13737
|
-
return wrapped2 > 180 ? wrapped2 - 360 : wrapped2;
|
|
14716
|
+
function offsetWrapped(previous, next, count) {
|
|
14717
|
+
return Math.abs(next - previous) > count / 2;
|
|
13738
14718
|
}
|
|
13739
14719
|
function snapPosition(position, total) {
|
|
13740
14720
|
if (total <= 0) return { step: 0, drift: 0 };
|
|
13741
14721
|
const nearest = Math.round(position);
|
|
13742
14722
|
return { step: (nearest % total + total) % total, drift: position - nearest };
|
|
13743
14723
|
}
|
|
13744
|
-
var
|
|
14724
|
+
var DECK_PARAMETERS = {
|
|
13745
14725
|
duration: { type: "time", default: "620ms", cssProperty: "--kui-duration" },
|
|
13746
14726
|
...TRIGGER_DELAY_PARAM,
|
|
13747
14727
|
ease: { type: "easing", default: "cubic-bezier(0.22, 1, 0.36, 1)", cssProperty: "--kui-ease" },
|
|
13748
|
-
|
|
13749
|
-
* A real CSS `<angle>`, not a normalised scalar.
|
|
13750
|
-
*
|
|
13751
|
-
* A designer specifies fifteen degrees; nobody specifies 0.375 of an unstated maximum. A
|
|
13752
|
-
* normalised knob also bakes taste into a coordinate — the maximum *is* an opinion about how much
|
|
13753
|
-
* tilt is too much, expressed as a unit — and it cannot be typed in the spelling the rest of the
|
|
13754
|
-
* attribute grammar uses (`0.05turn` is a legal angle and means something exact). Legibility is
|
|
13755
|
-
* still bounded, but the bound is applied to the angle, in `carousel.css`, where it is one
|
|
13756
|
-
* `clamp()` an author can see in devtools rather than a hidden rescale.
|
|
13757
|
-
*
|
|
13758
|
-
* Sign convention: **positive tilts the camera up and over the ring**, looking down at it. That
|
|
13759
|
-
* is the opposite sign from the `rotateX()` it compiles to — see `carousel.css` for the
|
|
13760
|
-
* derivation — and the inversion is deliberate: "18 degrees above" is the sentence a designer
|
|
13761
|
-
* says, and it should not be spelled `-18deg` because of which way a matrix happens to turn.
|
|
13762
|
-
*/
|
|
13763
|
-
tilt: { type: "angle", default: "0deg", cssProperty: "--kui-tilt" },
|
|
13764
|
-
/*
|
|
13765
|
-
* How far the ring spreads. `360deg` is a full ring; anything less is a slice.
|
|
13766
|
-
*
|
|
13767
|
-
* This is the parameter the concave name exists around. From the centre of a *full* ring most of
|
|
13768
|
-
* it is behind your head, so a 360-degree spread there is not a design choice but a way of hiding
|
|
13769
|
-
* two thirds of the content — the inside name defaults to a slice for that reason.
|
|
13770
|
-
*/
|
|
13771
|
-
arc: { type: "angle", default: "360deg", cssProperty: "--kui-arc" },
|
|
13772
|
-
/*
|
|
13773
|
-
* The ring's radius, or unset for one derived from the content.
|
|
13774
|
-
*
|
|
13775
|
-
* Empty default, not a number, and for the same reason `step-progress`'s `steps` is empty:
|
|
13776
|
-
* `readParams` fills every declared default in unconditionally, so an empty one is the only way
|
|
13777
|
-
* to tell "the author said nothing" from "the author said 320px". Unset, `carousel.css` computes
|
|
13778
|
-
* the circumradius of a regular polygon with this many sides and this much side length —
|
|
13779
|
-
* `(width + gap) / (2 * tan(step / 2))` — which is the arrangement where neighbours just touch.
|
|
13780
|
-
* Every consumer solving that by hand was the alternative, and they would all have solved it
|
|
13781
|
-
* differently.
|
|
13782
|
-
*
|
|
13783
|
-
* The width half of that has to come from JavaScript: CSS can compute with a measurement but
|
|
13784
|
-
* cannot take one. See `measureItemWidth`.
|
|
13785
|
-
*/
|
|
13786
|
-
radius: { type: "length", default: "", cssProperty: "--kui-radius" },
|
|
13787
|
-
/** Breathing room between neighbours, spent by the derived radius above. */
|
|
13788
|
-
gap: { type: "length", default: "24px", cssProperty: "--kui-gap" },
|
|
13789
|
-
/** Camera distance. Shorter is a wider lens: more foreshortening, more drama, more distortion. */
|
|
13790
|
-
perspective: { type: "length", default: "1600px", cssProperty: "--kui-perspective" },
|
|
13791
|
-
/*
|
|
13792
|
-
* Whether a slot keeps its own outward orientation, or turns to keep facing the viewer.
|
|
13793
|
-
*
|
|
13794
|
-
* `radial` is the ring's natural state and needs nothing: a slot placed by
|
|
13795
|
-
* `rotateY(θ) translateZ(R)` already faces outward along its own radius, which is why the live
|
|
13796
|
-
* one faces you. It is what you want for a cover-flow, and it is what makes the far side of the
|
|
13797
|
-
* ring read as the far side.
|
|
13798
|
-
*
|
|
13799
|
-
* `camera` counter-rotates each slot's *child* — never the slot itself; the placement transform
|
|
13800
|
-
* and the billboard have to stay on separate boxes or every later change to one silently
|
|
13801
|
-
* multiplies into the other — so text stays readable at every position, which is what a raised
|
|
13802
|
-
* camera needs. Costs one element of markup per slide; see `docs/catalog.md`.
|
|
13803
|
-
*/
|
|
13804
|
-
facing: {
|
|
13805
|
-
type: "keyword",
|
|
13806
|
-
default: "radial",
|
|
13807
|
-
cssProperty: "--kui-facing",
|
|
13808
|
-
keywords: ["radial", "camera"]
|
|
13809
|
-
},
|
|
13810
|
-
/** Whether a pointer can spin the ring by hand. */
|
|
14728
|
+
/** Whether a pointer can move the deck by hand. */
|
|
13811
14729
|
grab: { type: "keyword", default: "true", cssProperty: "--kui-grab", keywords: ["true", "false"] },
|
|
13812
14730
|
/*
|
|
13813
|
-
* How far the pointer travels, in pixels, to move the
|
|
14731
|
+
* How far the pointer travels, in pixels, to move the deck one place.
|
|
13814
14732
|
*
|
|
13815
14733
|
* A pixels-per-step mapping rather than pixels-per-degree, because a step is the unit everything
|
|
13816
14734
|
* else here is in — the index, the offset, the snap — and a degree is not: the same drag would
|
|
13817
14735
|
* move a six-item ring one place and a sixty-item ring ten, purely because the spacing changed.
|
|
13818
14736
|
*/
|
|
13819
14737
|
travel: { type: "number", default: "220", cssProperty: "--kui-travel", finite: true, minimum: 1 },
|
|
13820
|
-
/** Which elements
|
|
14738
|
+
/** Which elements are the slides. Unset means this element's own children. */
|
|
13821
14739
|
target: { type: "text", default: "", cssProperty: "--kui-target" },
|
|
13822
14740
|
/** Optional controls, resolved exactly as `step-progress` resolves its own. */
|
|
13823
14741
|
next: { type: "text", default: "", cssProperty: "--kui-next" },
|
|
13824
14742
|
prev: { type: "text", default: "", cssProperty: "--kui-prev" },
|
|
13825
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,
|
|
14748
|
+
/*
|
|
14749
|
+
* Continuous rotation: how long one full cycle of the deck takes. `0s` is off.
|
|
14750
|
+
*
|
|
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.
|
|
14757
|
+
*/
|
|
14758
|
+
spin: { type: "time", default: "0s", cssProperty: "--kui-spin" },
|
|
14759
|
+
/*
|
|
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`).
|
|
14762
|
+
*/
|
|
14763
|
+
autoplay: AUTOPLAY_PARAM,
|
|
14764
|
+
/*
|
|
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.
|
|
14769
|
+
*/
|
|
14770
|
+
lightbox: DECK_LIGHTBOX_PARAM,
|
|
13826
14771
|
scope: SCOPE_PARAM
|
|
13827
14772
|
};
|
|
13828
|
-
function
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
}
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
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);
|
|
13839
14791
|
const scope = scopeParam(params, "self");
|
|
13840
14792
|
const resolveSlots = () => selector ? queryScoped(el, ctx, selector, scope) : el.children;
|
|
13841
|
-
const marker = createStepMarker(resolveSlots, (message) => ctx.warn(
|
|
14793
|
+
const marker = createStepMarker(resolveSlots, (message) => ctx.warn(`${label} ${message}`));
|
|
13842
14794
|
const total = () => countSteps(params, resolveSlots);
|
|
13843
14795
|
const hostAttributes = createAttributeLedger(el);
|
|
13844
14796
|
const hostStyles = createStyleLedger(el);
|
|
13845
|
-
|
|
13846
|
-
const slots = { attributes: /* @__PURE__ */ new Map()
|
|
13847
|
-
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);
|
|
13848
14800
|
let position = 0;
|
|
13849
|
-
|
|
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
|
+
};
|
|
13850
14826
|
const render = () => {
|
|
13851
14827
|
const count = total();
|
|
13852
14828
|
const { step, drift: drift2 } = snapPosition(position, count);
|
|
13853
14829
|
hostAttributes.set("data-kui-step", String(step));
|
|
13854
14830
|
hostStyles.set("--kui-step", String(step));
|
|
13855
|
-
hostStyles.set("--kui-
|
|
14831
|
+
hostStyles.set("--kui-item-count", String(count));
|
|
14832
|
+
publishPosition(step, drift2);
|
|
13856
14833
|
marker.mark(step);
|
|
13857
|
-
const
|
|
13858
|
-
if (
|
|
13859
|
-
|
|
13860
|
-
|
|
13861
|
-
|
|
13862
|
-
|
|
13863
|
-
|
|
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;
|
|
13864
14846
|
}
|
|
14847
|
+
publishPosition(step, drift2);
|
|
14848
|
+
markFaces(rendered.nodes, rendered.count, drift2);
|
|
13865
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
|
+
});
|
|
13866
14870
|
const goTo = (next) => {
|
|
14871
|
+
motion.interrupt();
|
|
13867
14872
|
position = next;
|
|
13868
14873
|
render();
|
|
13869
14874
|
};
|
|
13870
|
-
const
|
|
13871
|
-
const
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13875
|
-
|
|
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);
|
|
13876
14892
|
}
|
|
13877
|
-
|
|
13878
|
-
return true;
|
|
14893
|
+
return travelSign(centres.get(0) ?? 0, centres.get(1) ?? null, centres.get(-1) ?? null);
|
|
13879
14894
|
};
|
|
13880
|
-
bindControl("next", () => goTo(nextStep(snapPosition(position, total()).step, total())));
|
|
13881
|
-
bindControl("prev", () => goTo(prevStep(snapPosition(position, total()).step, total())));
|
|
13882
|
-
bindControl("jump", (_node, at) => {
|
|
13883
|
-
if (at >= 0) goTo(at);
|
|
13884
|
-
});
|
|
13885
|
-
const releaseControls = delegateControls({ el, ctx, scope, groups });
|
|
13886
14895
|
const releaseDrag = createRingDrag({
|
|
13887
14896
|
el,
|
|
13888
14897
|
ctx,
|
|
@@ -13890,22 +14899,405 @@ var kuinetic = (() => {
|
|
|
13890
14899
|
travelPx: params.num("travel", 220),
|
|
13891
14900
|
total,
|
|
13892
14901
|
positionOf: () => position,
|
|
14902
|
+
travelSign: screenTravel,
|
|
13893
14903
|
moveTo: goTo,
|
|
13894
|
-
setDragging: (dragging) =>
|
|
14904
|
+
setDragging: (dragging) => {
|
|
14905
|
+
hostAttributes.set(DRAGGING_ATTR, String(dragging));
|
|
14906
|
+
motion.hold(dragging);
|
|
14907
|
+
}
|
|
13895
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
|
+
);
|
|
13896
14914
|
render();
|
|
13897
14915
|
return () => {
|
|
14916
|
+
releaseViewer();
|
|
14917
|
+
releaseMotion();
|
|
13898
14918
|
releaseDrag();
|
|
13899
14919
|
releaseControls();
|
|
13900
14920
|
marker.restore();
|
|
13901
14921
|
for (const ledger of slots.attributes.values()) ledger.restore();
|
|
13902
|
-
for (const ledger of slots.styles.values()) ledger.restore();
|
|
13903
14922
|
slots.attributes.clear();
|
|
13904
|
-
|
|
14923
|
+
attachment?.release();
|
|
13905
14924
|
hostAttributes.restore();
|
|
13906
14925
|
hostStyles.restore();
|
|
13907
14926
|
};
|
|
13908
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
|
+
}
|
|
13909
15301
|
function degreesOf(value, fallbackDeg) {
|
|
13910
15302
|
const match = /^(-?[\d.]+)(deg|rad|turn|grad)?$/i.exec(value.trim());
|
|
13911
15303
|
if (!match) return fallbackDeg;
|
|
@@ -13925,7 +15317,15 @@ var kuinetic = (() => {
|
|
|
13925
15317
|
var SPATIAL_RING_PRIMITIVE = {
|
|
13926
15318
|
id: "spatial-ring",
|
|
13927
15319
|
renderer: "javascript",
|
|
13928
|
-
|
|
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
|
+
],
|
|
13929
15329
|
parameters: RING_PARAMETERS,
|
|
13930
15330
|
supportedTimelines: ["time"],
|
|
13931
15331
|
// `load`, not `enter`: a carousel that only wires its arrows once scrolled into view is broken,
|
|
@@ -13936,7 +15336,7 @@ var kuinetic = (() => {
|
|
|
13936
15336
|
reducedMotion: "shorten",
|
|
13937
15337
|
prepare: deferPrepare(prepareSpatialRing)
|
|
13938
15338
|
};
|
|
13939
|
-
var CAROUSEL_PRIMITIVES = [SPATIAL_RING_PRIMITIVE];
|
|
15339
|
+
var CAROUSEL_PRIMITIVES = [SPATIAL_RING_PRIMITIVE, SPATIAL_STACK_PRIMITIVE];
|
|
13940
15340
|
var CAROUSEL_PRESETS = [
|
|
13941
15341
|
{
|
|
13942
15342
|
name: "carousel-3d",
|
|
@@ -13973,10 +15373,31 @@ var kuinetic = (() => {
|
|
|
13973
15373
|
params: { arc: "120deg", facing: "camera", perspective: "900px" },
|
|
13974
15374
|
requiresOwnSubtree: true,
|
|
13975
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"
|
|
13976
15397
|
}
|
|
13977
15398
|
];
|
|
13978
15399
|
function registerCarousel(registry) {
|
|
13979
|
-
return registry.registerPrimitives(CAROUSEL_PRIMITIVES).registerPresets(CAROUSEL_PRESETS);
|
|
15400
|
+
return registry.registerPrimitives(CAROUSEL_PRIMITIVES).registerPresets(CAROUSEL_PRESETS).registerPresets(STACK_PRESETS);
|
|
13980
15401
|
}
|
|
13981
15402
|
|
|
13982
15403
|
// src/effects/tween/properties.ts
|
|
@@ -14532,7 +15953,10 @@ var kuinetic = (() => {
|
|
|
14532
15953
|
keywords: [...DEVICE_FRAME_KINDS]
|
|
14533
15954
|
},
|
|
14534
15955
|
color: { type: "color", default: "#111", cssProperty: "--kui-device-color" },
|
|
14535
|
-
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" }
|
|
14536
15960
|
};
|
|
14537
15961
|
function prepareDeviceFrame(el, params) {
|
|
14538
15962
|
const state = createAttributeLedger(el);
|
|
@@ -14545,8 +15969,9 @@ var kuinetic = (() => {
|
|
|
14545
15969
|
// Exclusive ownership of the frame chrome. Its static box-model declarations are outside
|
|
14546
15970
|
// the channel-property audit, as they are for the existing catalog. The chrome also reaches
|
|
14547
15971
|
// into the direct media child (`object-fit: cover` etc.); that is a `requiresOwnSubtree`
|
|
14548
|
-
// question, as with the other showcase widgets.
|
|
14549
|
-
|
|
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")],
|
|
14550
15975
|
parameters: deviceFrameParams,
|
|
14551
15976
|
// `'layout'`: the bezel's `border`+`padding` are box-model properties added once, at
|
|
14552
15977
|
// activation, which is a real (if one-time) layout — not the "transform/opacity only" budget
|
|
@@ -14581,10 +16006,12 @@ var kuinetic = (() => {
|
|
|
14581
16006
|
state.lock = ledger;
|
|
14582
16007
|
}
|
|
14583
16008
|
function clearContent(state) {
|
|
14584
|
-
state.current
|
|
16009
|
+
const closing = state.current;
|
|
16010
|
+
closing?.onClose?.();
|
|
14585
16011
|
state.current = void 0;
|
|
14586
16012
|
state.currentOwner = void 0;
|
|
14587
16013
|
state.slot?.replaceChildren();
|
|
16014
|
+
if (closing?.opener) announceViewer(state.doc, closing.opener, false);
|
|
14588
16015
|
}
|
|
14589
16016
|
function onClose(state) {
|
|
14590
16017
|
state.dialog?.classList.remove("is-open");
|
|
@@ -14603,11 +16030,9 @@ var kuinetic = (() => {
|
|
|
14603
16030
|
}, state.current.reducedMotion ? 0 : state.current.duration);
|
|
14604
16031
|
}
|
|
14605
16032
|
function clickedOutside(state, target) {
|
|
14606
|
-
const
|
|
14607
|
-
if (
|
|
14608
|
-
|
|
14609
|
-
}
|
|
14610
|
-
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");
|
|
14611
16036
|
}
|
|
14612
16037
|
function forceReflow(element) {
|
|
14613
16038
|
return element.offsetWidth;
|
|
@@ -14657,6 +16082,7 @@ var kuinetic = (() => {
|
|
|
14657
16082
|
dialog.scrollTop = 0;
|
|
14658
16083
|
forceReflow(dialog);
|
|
14659
16084
|
dialog.classList.add("is-open");
|
|
16085
|
+
if (content.opener) announceViewer(state.doc, content.opener, true);
|
|
14660
16086
|
}
|
|
14661
16087
|
function closeNow(state) {
|
|
14662
16088
|
if (state.timer !== void 0) clearTimeout(state.timer);
|
|
@@ -14736,25 +16162,31 @@ var kuinetic = (() => {
|
|
|
14736
16162
|
}
|
|
14737
16163
|
|
|
14738
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)";
|
|
14739
16168
|
var paramsSchema = {
|
|
14740
|
-
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"] },
|
|
14741
16170
|
target: { type: "text", default: "", cssProperty: "--kui-target" },
|
|
14742
16171
|
scope: SCOPE_PARAM,
|
|
14743
|
-
scale: { type: "number", default:
|
|
14744
|
-
duration: { type: "time", default:
|
|
14745
|
-
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" },
|
|
14746
16175
|
loop: { type: "keyword", default: "true", cssProperty: "--kui-lightbox-loop", keywords: ["true", "false"] },
|
|
14747
16176
|
aspect: { type: "keyword", default: "", cssProperty: "--kui-lightbox-aspect", keywords: ["wide", "tall", "square"] },
|
|
14748
16177
|
caption: { type: "keyword", default: "figcaption", cssProperty: "--kui-lightbox-caption", keywords: ["figcaption", "alt", "title", "none"] }
|
|
14749
16178
|
};
|
|
14750
16179
|
function defaultTargets(el, media) {
|
|
14751
|
-
if (el.matches("a[href]") || media
|
|
16180
|
+
if (el.matches("a[href]") || media !== "video" && el.matches("img")) return [el];
|
|
14752
16181
|
return [...el.querySelectorAll(media === "video" ? ":scope a[href]" : ":scope a[href], :scope img")];
|
|
14753
16182
|
}
|
|
16183
|
+
function effectName(media) {
|
|
16184
|
+
return media === "video" ? "video-lightbox" : "lightbox";
|
|
16185
|
+
}
|
|
14754
16186
|
function targets(el, params, ctx, media) {
|
|
14755
16187
|
const authored = params.text("target");
|
|
14756
16188
|
if (!authored) return defaultTargets(el, media);
|
|
14757
|
-
const selector = resolveTarget(authored, ctx, media
|
|
16189
|
+
const selector = resolveTarget(authored, ctx, effectName(media));
|
|
14758
16190
|
return selector ? queryScoped(el, ctx, selector, scopeParam(params, "self")) : [];
|
|
14759
16191
|
}
|
|
14760
16192
|
function imageTrigger(target) {
|
|
@@ -14765,52 +16197,137 @@ var kuinetic = (() => {
|
|
|
14765
16197
|
}
|
|
14766
16198
|
function captionFor(trigger, image, source) {
|
|
14767
16199
|
if (source === "none") return "";
|
|
14768
|
-
|
|
14769
|
-
if (source === "
|
|
14770
|
-
|
|
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();
|
|
14771
16207
|
}
|
|
14772
16208
|
function imageItem(trigger, captionSource) {
|
|
14773
16209
|
const image = imageIn(trigger);
|
|
14774
16210
|
if (!image) return null;
|
|
14775
16211
|
const link = trigger instanceof HTMLAnchorElement ? trigger : null;
|
|
14776
16212
|
return {
|
|
16213
|
+
kind: "image",
|
|
14777
16214
|
trigger,
|
|
14778
16215
|
image,
|
|
14779
16216
|
src: link?.href ?? (image.currentSrc || image.src),
|
|
14780
16217
|
caption: captionFor(trigger, image, captionSource)
|
|
14781
16218
|
};
|
|
14782
16219
|
}
|
|
14783
|
-
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) {
|
|
14784
16237
|
const seen = /* @__PURE__ */ new Set();
|
|
14785
|
-
const
|
|
14786
|
-
for (const target of
|
|
14787
|
-
const trigger = imageTrigger(target);
|
|
16238
|
+
const triggers = [];
|
|
16239
|
+
for (const target of found) {
|
|
16240
|
+
const trigger = media === "video" ? target : imageTrigger(target);
|
|
14788
16241
|
if (seen.has(trigger)) continue;
|
|
14789
|
-
|
|
14790
|
-
if (!item) continue;
|
|
16242
|
+
if (!usable(trigger, media)) continue;
|
|
14791
16243
|
seen.add(trigger);
|
|
14792
|
-
|
|
16244
|
+
triggers.push(trigger);
|
|
14793
16245
|
}
|
|
14794
|
-
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;
|
|
14795
16253
|
}
|
|
14796
16254
|
function modalOptions(params, ctx) {
|
|
14797
16255
|
return {
|
|
14798
|
-
duration: params.timing.durationMs ?? params.ms("duration",
|
|
14799
|
-
scale: params.text("scale",
|
|
14800
|
-
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),
|
|
14801
16259
|
reducedMotion: ctx.reducedMotion
|
|
14802
16260
|
};
|
|
14803
16261
|
}
|
|
14804
|
-
function showImage(item, view
|
|
16262
|
+
function showImage(item, view) {
|
|
14805
16263
|
view.src = item.src;
|
|
14806
16264
|
view.alt = item.image.alt;
|
|
14807
|
-
caption.textContent = item.caption;
|
|
14808
|
-
caption.hidden = item.caption === "";
|
|
14809
16265
|
const ratio = item.image.naturalWidth ? item.image.naturalHeight / item.image.naturalWidth : 1;
|
|
14810
16266
|
view.className = ratio > 1.5 ? "is-tall" : "is-fit";
|
|
14811
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
|
+
}
|
|
14812
16321
|
function renderGallery(state, focus) {
|
|
14813
|
-
|
|
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
|
+
}
|
|
14814
16331
|
state.counter.textContent = `${state.index + 1} of ${state.items.length}`;
|
|
14815
16332
|
state.previous.disabled = !state.loop && state.index === 0;
|
|
14816
16333
|
state.next.disabled = !state.loop && state.index === state.items.length - 1;
|
|
@@ -14828,6 +16345,7 @@ var kuinetic = (() => {
|
|
|
14828
16345
|
}
|
|
14829
16346
|
function galleryKey(state, event) {
|
|
14830
16347
|
if (state.items.length < 2) return;
|
|
16348
|
+
if (event.target.closest("video")) return;
|
|
14831
16349
|
if (event.key === "ArrowLeft") moveGallery(state, -1, true);
|
|
14832
16350
|
else if (event.key === "ArrowRight") moveGallery(state, 1, true);
|
|
14833
16351
|
else if (event.key === "Home") {
|
|
@@ -14839,8 +16357,40 @@ var kuinetic = (() => {
|
|
|
14839
16357
|
} else return;
|
|
14840
16358
|
event.preventDefault();
|
|
14841
16359
|
}
|
|
14842
|
-
function
|
|
14843
|
-
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;
|
|
14844
16394
|
const box = doc.createElement("div");
|
|
14845
16395
|
box.className = "kui-lightbox-gallery";
|
|
14846
16396
|
const figure = doc.createElement("figure");
|
|
@@ -14848,7 +16398,6 @@ var kuinetic = (() => {
|
|
|
14848
16398
|
const view = doc.createElement("img");
|
|
14849
16399
|
view.decoding = "async";
|
|
14850
16400
|
const caption = doc.createElement("figcaption");
|
|
14851
|
-
figure.append(view, caption);
|
|
14852
16401
|
box.append(figure);
|
|
14853
16402
|
const counter = doc.createElement("span");
|
|
14854
16403
|
counter.className = "kui-lightbox-counter";
|
|
@@ -14858,149 +16407,244 @@ var kuinetic = (() => {
|
|
|
14858
16407
|
previous.type = next.type = "button";
|
|
14859
16408
|
previous.textContent = "\u2039";
|
|
14860
16409
|
next.textContent = "\u203A";
|
|
14861
|
-
|
|
14862
|
-
|
|
16410
|
+
const { noun, label } = vocabulary(items);
|
|
16411
|
+
previous.setAttribute("aria-label", `Previous ${noun}`);
|
|
16412
|
+
next.setAttribute("aria-label", `Next ${noun}`);
|
|
14863
16413
|
previous.className = "kui-lightbox-prev";
|
|
14864
16414
|
next.className = "kui-lightbox-next";
|
|
14865
|
-
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
|
+
};
|
|
14866
16428
|
if (items.length > 1) {
|
|
14867
16429
|
box.append(previous, next, counter);
|
|
14868
16430
|
previous.addEventListener("click", () => clickGallery(state, -1, previous));
|
|
14869
16431
|
next.addEventListener("click", () => clickGallery(state, 1, next));
|
|
14870
16432
|
}
|
|
14871
16433
|
renderGallery(state, false);
|
|
16434
|
+
const unswipe = items.length > 1 ? swipeGallery(state, box) : void 0;
|
|
16435
|
+
const { duration, scale, ease, reducedMotion } = options;
|
|
14872
16436
|
return {
|
|
14873
|
-
|
|
16437
|
+
duration,
|
|
16438
|
+
scale,
|
|
16439
|
+
ease,
|
|
16440
|
+
reducedMotion,
|
|
14874
16441
|
node: box,
|
|
14875
|
-
label
|
|
14876
|
-
|
|
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
|
+
},
|
|
14877
16449
|
onKey: (event) => galleryKey(state, event)
|
|
14878
16450
|
};
|
|
14879
16451
|
}
|
|
14880
|
-
function stopMedia(frame) {
|
|
14881
|
-
const video = frame.querySelector("video");
|
|
14882
|
-
if (video) {
|
|
14883
|
-
if (video.readyState > 0) video.pause();
|
|
14884
|
-
video.removeAttribute("src");
|
|
14885
|
-
}
|
|
14886
|
-
frame.replaceChildren();
|
|
14887
|
-
}
|
|
14888
|
-
function videoContent(link, source, options, aspect) {
|
|
14889
|
-
const doc = link.ownerDocument;
|
|
14890
|
-
const frame = doc.createElement("div");
|
|
14891
|
-
frame.className = `kui-lightbox-frame kui-lightbox-frame--${aspect}`;
|
|
14892
|
-
const poster = link.querySelector("img");
|
|
14893
|
-
const title = link.title || poster?.getAttribute("alt") || link.getAttribute("aria-label")?.trim() || link.textContent?.trim() || "Video";
|
|
14894
|
-
if (source.kind === "file") {
|
|
14895
|
-
const video = doc.createElement("video");
|
|
14896
|
-
video.src = source.embedUrl;
|
|
14897
|
-
video.controls = true;
|
|
14898
|
-
video.autoplay = true;
|
|
14899
|
-
video.playsInline = true;
|
|
14900
|
-
video.setAttribute("aria-label", title);
|
|
14901
|
-
frame.append(video);
|
|
14902
|
-
} else {
|
|
14903
|
-
const iframe = doc.createElement("iframe");
|
|
14904
|
-
iframe.src = source.embedUrl;
|
|
14905
|
-
iframe.title = title;
|
|
14906
|
-
iframe.allow = "autoplay; encrypted-media; picture-in-picture; fullscreen";
|
|
14907
|
-
iframe.allowFullscreen = true;
|
|
14908
|
-
frame.append(iframe);
|
|
14909
|
-
}
|
|
14910
|
-
return { ...options, node: frame, label: "Video viewer", onClose: () => stopMedia(frame) };
|
|
14911
|
-
}
|
|
14912
16452
|
function primaryClick(event) {
|
|
14913
16453
|
return event.button === 0 && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey;
|
|
14914
16454
|
}
|
|
14915
|
-
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) {
|
|
14916
16474
|
const restore = [];
|
|
14917
|
-
|
|
14918
|
-
const bare =
|
|
16475
|
+
for (const trigger of triggers) {
|
|
16476
|
+
const bare = trigger instanceof HTMLImageElement;
|
|
14919
16477
|
if (bare) {
|
|
14920
|
-
const attrs = createAttributeLedger(
|
|
16478
|
+
const attrs = createAttributeLedger(trigger);
|
|
14921
16479
|
attrs.set("role", "button");
|
|
14922
16480
|
attrs.set("tabindex", "0");
|
|
14923
|
-
attrs.set("aria-label",
|
|
16481
|
+
attrs.set("aria-label", trigger.alt ? `Open larger image: ${trigger.alt}` : "Open larger image");
|
|
14924
16482
|
restore.push(() => attrs.restore());
|
|
14925
|
-
|
|
16483
|
+
trigger.addEventListener("keydown", (event) => {
|
|
14926
16484
|
if (event.key !== "Enter" && event.key !== " ") return;
|
|
14927
16485
|
event.preventDefault();
|
|
14928
|
-
|
|
16486
|
+
openAt(triggers, trigger, wiring);
|
|
14929
16487
|
}, { signal: wiring.signal });
|
|
14930
16488
|
}
|
|
14931
|
-
|
|
16489
|
+
trigger.addEventListener("click", (event) => {
|
|
14932
16490
|
if (!primaryClick(event)) return;
|
|
14933
|
-
if (
|
|
14934
|
-
wiring.shell.open(galleryContent(items, index, loop2, wiring.options));
|
|
16491
|
+
if (openAt(triggers, trigger, wiring)) event.preventDefault();
|
|
14935
16492
|
}, { signal: wiring.signal });
|
|
14936
|
-
}
|
|
16493
|
+
}
|
|
14937
16494
|
return restore;
|
|
14938
16495
|
}
|
|
14939
|
-
function
|
|
14940
|
-
const
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
|
|
14945
|
-
|
|
14946
|
-
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
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
|
+
};
|
|
14956
16565
|
}
|
|
14957
16566
|
function prepareLightbox(el, params, ctx) {
|
|
14958
16567
|
const media = params.text("media", "image");
|
|
14959
|
-
const
|
|
14960
|
-
if (
|
|
16568
|
+
const triggers = triggersFor(targets(el, params, ctx, media), media);
|
|
16569
|
+
if (triggers.length === 0) return () => {
|
|
14961
16570
|
};
|
|
14962
16571
|
const controller = new AbortController();
|
|
14963
16572
|
const shell = acquireModalShell(ctx.doc);
|
|
14964
|
-
const
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
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
|
+
});
|
|
14972
16583
|
return continuousSetup(() => {
|
|
14973
16584
|
controller.abort();
|
|
14974
16585
|
for (const undo of restore) undo();
|
|
14975
16586
|
shell.release();
|
|
14976
16587
|
});
|
|
14977
16588
|
}
|
|
16589
|
+
var TRIGGERS_CHANNEL = "lightbox:triggers";
|
|
14978
16590
|
var LIGHTBOX_PRIMITIVE = widgetPrimitive(
|
|
14979
16591
|
"lightbox",
|
|
14980
|
-
{ channels: [
|
|
16592
|
+
{ channels: [TRIGGERS_CHANNEL], parameters: paramsSchema, perfClass: "paint" },
|
|
14981
16593
|
withTimingContract("lightbox", { honours: ["duration", "ease"], because: "the dialog opens immediately, so it has no delay phase" }, deferPrepare(prepareLightbox))
|
|
14982
16594
|
);
|
|
14983
16595
|
var LIGHTBOX_PRESETS = [
|
|
14984
16596
|
{ name: "lightbox", primitive: "lightbox", params: { media: "image" } },
|
|
14985
|
-
|
|
16597
|
+
// A video row has never shown captions; `caption:figcaption` opts one in.
|
|
16598
|
+
{ name: "video-lightbox", primitive: "lightbox", params: { media: "video", caption: "none" } }
|
|
14986
16599
|
];
|
|
14987
16600
|
|
|
14988
16601
|
// src/showcase/compare.ts
|
|
14989
16602
|
var COMPARE_AXES = ["x", "y"];
|
|
14990
16603
|
var COMPARE_AXIS_ATTR = "data-kui-compare-axis";
|
|
16604
|
+
var COMPARE_DIVIDERS_ATTR = "data-kui-compare-dividers";
|
|
14991
16605
|
var compareParams = {
|
|
14992
16606
|
position: {
|
|
14993
16607
|
type: "percentage",
|
|
14994
16608
|
default: "50%",
|
|
14995
16609
|
cssProperty: "--kui-compare"
|
|
14996
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" },
|
|
14997
16618
|
axis: {
|
|
14998
16619
|
type: "keyword",
|
|
14999
16620
|
default: "x",
|
|
15000
16621
|
cssProperty: "--kui-compare-axis",
|
|
15001
16622
|
keywords: [...COMPARE_AXES]
|
|
15002
|
-
}
|
|
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"] }
|
|
15003
16645
|
};
|
|
16646
|
+
var MIN_SWEEP_MS = 250;
|
|
16647
|
+
var MAX_FRAME_MS2 = 100;
|
|
15004
16648
|
var VERTICAL_KEY_DELTA = { ArrowUp: -1, ArrowDown: 1 };
|
|
15005
16649
|
function getMediaName(item, fallback) {
|
|
15006
16650
|
const alt = item.localName === "picture" ? item.querySelector("img")?.getAttribute("alt") : item.getAttribute("alt");
|
|
@@ -15016,76 +16660,402 @@ var kuinetic = (() => {
|
|
|
15016
16660
|
}
|
|
15017
16661
|
return "Compare";
|
|
15018
16662
|
}
|
|
15019
|
-
function
|
|
16663
|
+
function createRange(doc, label, value, axis) {
|
|
15020
16664
|
const range = doc.createElement("input");
|
|
15021
16665
|
range.type = "range";
|
|
15022
16666
|
range.className = "kui-compare-range";
|
|
15023
16667
|
range.min = "0";
|
|
15024
16668
|
range.max = "100";
|
|
15025
16669
|
range.step = "1";
|
|
15026
|
-
range.value = String(
|
|
15027
|
-
range.setAttribute("aria-label", label);
|
|
15028
|
-
range.setAttribute("aria-valuetext",
|
|
16670
|
+
range.value = String(value);
|
|
16671
|
+
range.setAttribute("aria-label", label.name);
|
|
16672
|
+
range.setAttribute("aria-valuetext", label.valueText);
|
|
15029
16673
|
if (axis === "y") range.setAttribute("aria-orientation", "vertical");
|
|
16674
|
+
return range;
|
|
16675
|
+
}
|
|
16676
|
+
function createHandle(doc) {
|
|
15030
16677
|
const handle = doc.createElement("span");
|
|
15031
16678
|
handle.className = "kui-compare-handle";
|
|
15032
16679
|
handle.setAttribute("aria-hidden", "true");
|
|
15033
|
-
return
|
|
16680
|
+
return handle;
|
|
15034
16681
|
}
|
|
15035
|
-
function
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
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);
|
|
15039
16713
|
}
|
|
15040
|
-
const
|
|
15041
|
-
|
|
15042
|
-
|
|
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 }) {
|
|
15043
16806
|
const initialPercent = Math.round(params.num("position", 0.5) * 100);
|
|
15044
16807
|
const style = createStyleLedger(el);
|
|
15045
16808
|
style.set("--kui-compare", params.text("position", "50%"));
|
|
15046
|
-
const
|
|
15047
|
-
|
|
15048
|
-
|
|
16809
|
+
const doc = el.ownerDocument;
|
|
16810
|
+
const range = createRange(
|
|
16811
|
+
doc,
|
|
16812
|
+
{ name: resolveCompareLabel(el, media), valueText: `${initialPercent}% after` },
|
|
15049
16813
|
initialPercent,
|
|
15050
16814
|
axis
|
|
15051
16815
|
);
|
|
16816
|
+
const handle = createHandle(doc);
|
|
15052
16817
|
const onInput = () => {
|
|
15053
16818
|
const val = range.value;
|
|
15054
16819
|
style.set("--kui-compare", `${val}%`);
|
|
15055
16820
|
range.setAttribute("aria-valuetext", `${val}% after`);
|
|
15056
16821
|
};
|
|
15057
|
-
const
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
if (delta < 0) range.stepDown();
|
|
15062
|
-
else range.stepUp();
|
|
15063
|
-
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`);
|
|
15064
16826
|
};
|
|
16827
|
+
const onKeyDown = verticalKeys(range, axis, onInput);
|
|
15065
16828
|
range.addEventListener("input", onInput);
|
|
15066
16829
|
range.addEventListener("keydown", onKeyDown);
|
|
15067
16830
|
el.appendChild(range);
|
|
15068
16831
|
el.appendChild(handle);
|
|
15069
|
-
|
|
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();
|
|
15070
16843
|
range.removeEventListener("input", onInput);
|
|
15071
16844
|
range.removeEventListener("keydown", onKeyDown);
|
|
15072
16845
|
range.remove();
|
|
15073
16846
|
handle.remove();
|
|
15074
|
-
attrs.restore();
|
|
15075
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();
|
|
15076
17042
|
});
|
|
15077
17043
|
}
|
|
15078
17044
|
var COMPARE_PRIMITIVE = widgetPrimitive(
|
|
15079
17045
|
"compare",
|
|
15080
17046
|
{
|
|
15081
|
-
|
|
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)],
|
|
15082
17051
|
parameters: compareParams,
|
|
15083
17052
|
perfClass: "paint"
|
|
15084
17053
|
},
|
|
15085
17054
|
withTimingContract(
|
|
15086
17055
|
"compare",
|
|
15087
17056
|
{
|
|
15088
|
-
|
|
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"
|
|
15089
17059
|
},
|
|
15090
17060
|
deferPrepare(prepareCompare)
|
|
15091
17061
|
)
|
|
@@ -15259,7 +17229,8 @@ var kuinetic = (() => {
|
|
|
15259
17229
|
var HOTSPOTS_PRIMITIVE = widgetPrimitive(
|
|
15260
17230
|
"hotspots",
|
|
15261
17231
|
{
|
|
15262
|
-
|
|
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")],
|
|
15263
17234
|
parameters: hotspotsParams,
|
|
15264
17235
|
perfClass: "layout"
|
|
15265
17236
|
},
|
|
@@ -15463,7 +17434,7 @@ var kuinetic = (() => {
|
|
|
15463
17434
|
hovered = true;
|
|
15464
17435
|
update();
|
|
15465
17436
|
};
|
|
15466
|
-
const
|
|
17437
|
+
const leave2 = () => {
|
|
15467
17438
|
hovered = false;
|
|
15468
17439
|
update();
|
|
15469
17440
|
};
|
|
@@ -15480,7 +17451,7 @@ var kuinetic = (() => {
|
|
|
15480
17451
|
update();
|
|
15481
17452
|
};
|
|
15482
17453
|
el.addEventListener("pointerenter", enter);
|
|
15483
|
-
el.addEventListener("pointerleave",
|
|
17454
|
+
el.addEventListener("pointerleave", leave2);
|
|
15484
17455
|
el.addEventListener("focusin", focus);
|
|
15485
17456
|
el.addEventListener("focusout", blur);
|
|
15486
17457
|
ctx.doc.addEventListener("visibilitychange", update);
|
|
@@ -15489,7 +17460,7 @@ var kuinetic = (() => {
|
|
|
15489
17460
|
return () => {
|
|
15490
17461
|
if (timer !== null) clearTimeout(timer);
|
|
15491
17462
|
el.removeEventListener("pointerenter", enter);
|
|
15492
|
-
el.removeEventListener("pointerleave",
|
|
17463
|
+
el.removeEventListener("pointerleave", leave2);
|
|
15493
17464
|
el.removeEventListener("focusin", focus);
|
|
15494
17465
|
el.removeEventListener("focusout", blur);
|
|
15495
17466
|
ctx.doc.removeEventListener("visibilitychange", update);
|
|
@@ -15555,7 +17526,6 @@ var kuinetic = (() => {
|
|
|
15555
17526
|
el,
|
|
15556
17527
|
params,
|
|
15557
17528
|
ctx,
|
|
15558
|
-
scope,
|
|
15559
17529
|
resolveSteps,
|
|
15560
17530
|
name: "slideshow",
|
|
15561
17531
|
clickFallback: false,
|
|
@@ -15583,7 +17553,19 @@ var kuinetic = (() => {
|
|
|
15583
17553
|
}
|
|
15584
17554
|
var SLIDESHOW_PRIMITIVE = widgetPrimitive(
|
|
15585
17555
|
"slideshow",
|
|
15586
|
-
|
|
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
|
+
},
|
|
15587
17569
|
withTimingContract(
|
|
15588
17570
|
"slideshow",
|
|
15589
17571
|
{ honours: ["duration", "ease"], because: "the slideshow has no delayed start" },
|
|
@@ -15745,7 +17727,14 @@ var kuinetic = (() => {
|
|
|
15745
17727
|
}
|
|
15746
17728
|
var SCROLL_STORY_PRIMITIVE = widgetPrimitive(
|
|
15747
17729
|
"scroll-story",
|
|
15748
|
-
|
|
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
|
+
},
|
|
15749
17738
|
withTimingContract(
|
|
15750
17739
|
"scroll-story",
|
|
15751
17740
|
{ because: "scroll position" },
|
|
@@ -15848,7 +17837,8 @@ var kuinetic = (() => {
|
|
|
15848
17837
|
}
|
|
15849
17838
|
var SLOW_MO_PRIMITIVE = widgetPrimitive(
|
|
15850
17839
|
"slow-mo",
|
|
15851
|
-
|
|
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" },
|
|
15852
17842
|
withTimingContract(
|
|
15853
17843
|
"slow-mo",
|
|
15854
17844
|
{ because: "it changes playback rate rather than defining an animation timeline" },
|
|
@@ -15877,6 +17867,7 @@ var kuinetic = (() => {
|
|
|
15877
17867
|
...SLOW_MO_PRESETS
|
|
15878
17868
|
];
|
|
15879
17869
|
function registerShowcase(target) {
|
|
17870
|
+
provideDeckViewer(attachDeckLightbox);
|
|
15880
17871
|
return registerInto(target, SHOWCASE_PRIMITIVES, SHOWCASE_PRESETS, "Showcase");
|
|
15881
17872
|
}
|
|
15882
17873
|
|
|
@@ -15905,6 +17896,16 @@ var kuinetic = (() => {
|
|
|
15905
17896
|
return new Animator({ registry: createRegistry(), ...options });
|
|
15906
17897
|
}
|
|
15907
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
|
+
}
|
|
15908
17909
|
return __toCommonJS(src_exports);
|
|
15909
17910
|
})();
|
|
15910
17911
|
|