squiffy-runtime 6.0.0-alpha.14 → 6.0.0-alpha.17
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/LICENSE +22 -0
- package/dist/animation.d.ts +11 -0
- package/dist/events.d.ts +8 -3
- package/dist/import.d.ts +4 -0
- package/dist/linkHandler.d.ts +8 -0
- package/dist/pluginManager.d.ts +23 -0
- package/dist/squiffy.runtime.d.ts +2 -2
- package/dist/squiffy.runtime.global.js +126 -0
- package/dist/squiffy.runtime.global.js.map +1 -0
- package/dist/squiffy.runtime.js +8785 -487
- package/dist/squiffy.runtime.js.map +1 -0
- package/dist/state.d.ts +19 -0
- package/dist/textProcessor.d.ts +8 -13
- package/dist/types.d.ts +5 -2
- package/dist/types.plugins.d.ts +27 -0
- package/dist/updater.d.ts +2 -0
- package/dist/utils.d.ts +1 -2
- package/package.json +13 -5
- package/src/__snapshots__/squiffy.runtime.test.ts.snap +53 -19
- package/src/animation.ts +68 -0
- package/src/events.ts +9 -10
- package/src/import.ts +5 -0
- package/src/linkHandler.ts +18 -0
- package/src/pluginManager.ts +74 -0
- package/src/plugins/animate.ts +97 -0
- package/src/plugins/index.ts +13 -0
- package/src/plugins/live.ts +83 -0
- package/src/plugins/random.ts +22 -0
- package/src/plugins/replaceLabel.ts +22 -0
- package/src/plugins/rotateSequence.ts +61 -0
- package/src/squiffy.runtime.test.ts +306 -134
- package/src/squiffy.runtime.ts +460 -332
- package/src/state.ts +106 -0
- package/src/textProcessor.ts +61 -166
- package/src/types.plugins.ts +41 -0
- package/src/types.ts +5 -2
- package/src/updater.ts +77 -0
- package/src/utils.ts +15 -12
- package/vite.config.ts +36 -0
- package/vitest.config.ts +9 -0
- package/vitest.setup.ts +16 -0
- package/dist/events.js +0 -35
- package/dist/squiffy.runtime.test.js +0 -394
- package/dist/textProcessor.js +0 -168
- package/dist/types.js +0 -1
- package/dist/utils.js +0 -14
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { PluginHost, SquiffyPlugin } from "../types.plugins.js";
|
|
2
|
+
import Handlebars from "handlebars";
|
|
3
|
+
|
|
4
|
+
export function AnimatePlugin(): SquiffyPlugin {
|
|
5
|
+
let squiffy: PluginHost;
|
|
6
|
+
|
|
7
|
+
const parseDataset = (dataset: DOMStringMap): Record<string, any> => {
|
|
8
|
+
const result: Record<string, any> = {};
|
|
9
|
+
|
|
10
|
+
for (const [key, value] of Object.entries(dataset)) {
|
|
11
|
+
if (value == null) {
|
|
12
|
+
result[key] = value;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
result[key] = JSON.parse(value);
|
|
18
|
+
} catch {
|
|
19
|
+
result[key] = value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const setupAnimations = (element: HTMLElement) => {
|
|
27
|
+
const selector = ".squiffy-animate";
|
|
28
|
+
for (const el of element.querySelectorAll<HTMLElement>(selector)) {
|
|
29
|
+
const params = parseDataset(el.dataset);
|
|
30
|
+
|
|
31
|
+
if (!params.content) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
el.innerHTML = params.content;
|
|
35
|
+
|
|
36
|
+
if (!params.name) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const runAnimation = () => {
|
|
41
|
+
if (params.loop) {
|
|
42
|
+
squiffy.animation.runAnimation(params.name, el, params, () => {}, true);
|
|
43
|
+
} else {
|
|
44
|
+
squiffy.addTransition(() => {
|
|
45
|
+
return new Promise<void>((resolve) => {
|
|
46
|
+
const currentContent = el.innerHTML;
|
|
47
|
+
squiffy.animation.runAnimation(params.name, el, params, () => {
|
|
48
|
+
el.classList.remove("squiffy-animate");
|
|
49
|
+
el.innerHTML = currentContent;
|
|
50
|
+
resolve();
|
|
51
|
+
}, false);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (params.trigger === "link") {
|
|
58
|
+
const links = el.querySelectorAll("a");
|
|
59
|
+
for (const link of links) {
|
|
60
|
+
squiffy.animation.addLinkAnimation(link, runAnimation);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
runAnimation();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
name: "animate",
|
|
70
|
+
init(sq: PluginHost) {
|
|
71
|
+
squiffy = sq;
|
|
72
|
+
|
|
73
|
+
squiffy.registerHelper("animate", (name: string, options) => {
|
|
74
|
+
const content = options.fn(this);
|
|
75
|
+
const dataset: Record<string, any> = {
|
|
76
|
+
name,
|
|
77
|
+
content,
|
|
78
|
+
...options.hash,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const attrs = Object.entries(dataset)
|
|
82
|
+
.map(([key, value]) =>
|
|
83
|
+
` data-${Handlebars.escapeExpression(key)}="${Handlebars.escapeExpression(JSON.stringify(value))}"`
|
|
84
|
+
)
|
|
85
|
+
.join("");
|
|
86
|
+
|
|
87
|
+
return new Handlebars.SafeString(`<span class="squiffy-animate"${attrs}></span>`);
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
onWrite(element: HTMLElement) {
|
|
91
|
+
setupAnimations(element);
|
|
92
|
+
},
|
|
93
|
+
onLoad() {
|
|
94
|
+
setupAnimations(squiffy.outputElement);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReplaceLabel } from "./replaceLabel.js";
|
|
2
|
+
import { RotateSequencePlugin } from "./rotateSequence.js";
|
|
3
|
+
import { RandomPlugin } from "./random.js";
|
|
4
|
+
import { LivePlugin } from "./live.js";
|
|
5
|
+
import { AnimatePlugin } from "./animate.js";
|
|
6
|
+
|
|
7
|
+
export const Plugins = {
|
|
8
|
+
ReplaceLabel,
|
|
9
|
+
RotateSequencePlugin,
|
|
10
|
+
RandomPlugin,
|
|
11
|
+
LivePlugin,
|
|
12
|
+
AnimatePlugin,
|
|
13
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { PluginHost, SquiffyPlugin } from "../types.plugins.js";
|
|
2
|
+
import Handlebars from "handlebars";
|
|
3
|
+
import { fadeReplace } from "../utils.js";
|
|
4
|
+
import { SquiffyEventMap } from "../events.js";
|
|
5
|
+
|
|
6
|
+
export function LivePlugin(): SquiffyPlugin {
|
|
7
|
+
let squiffy: PluginHost;
|
|
8
|
+
return {
|
|
9
|
+
name: "live",
|
|
10
|
+
init(sq: PluginHost) {
|
|
11
|
+
squiffy = sq;
|
|
12
|
+
|
|
13
|
+
// No need to render the attribute value (or section or passage contents) here - this
|
|
14
|
+
// is handled by onWrite, so that we pick up any attribute values set in the same passage.
|
|
15
|
+
// TODO: We could mark values as "pending", then in onWrite we could iteratively resolve
|
|
16
|
+
// any that are still pending. That would let us pick up {{live}} helpers inside embed
|
|
17
|
+
// live sections/passages.
|
|
18
|
+
squiffy.registerHelper("live", (attribute: string, options) => {
|
|
19
|
+
const section = options.hash.section as string || "";
|
|
20
|
+
if (section) {
|
|
21
|
+
return new Handlebars.SafeString(`<span class="squiffy-live" data-attribute="${attribute}" data-section="${section}"></span>`);
|
|
22
|
+
}
|
|
23
|
+
const passage = options.hash.passage as string || "";
|
|
24
|
+
if (passage) {
|
|
25
|
+
return new Handlebars.SafeString(`<span class="squiffy-live" data-attribute="${attribute}" data-passage="${passage}"></span>`);
|
|
26
|
+
}
|
|
27
|
+
return new Handlebars.SafeString(`<span class="squiffy-live" data-attribute="${attribute}"></span>`);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const onSet = async (e: SquiffyEventMap["set"]) => {
|
|
31
|
+
const promises: Promise<void>[] = [];
|
|
32
|
+
const selector = `.squiffy-live[data-attribute="${CSS.escape(e.attribute)}"]`;
|
|
33
|
+
for (const el of squiffy.outputElement.querySelectorAll<HTMLElement>(selector)) {
|
|
34
|
+
const oldContent = el.innerHTML;
|
|
35
|
+
let newContent = "";
|
|
36
|
+
if (el.dataset.section) {
|
|
37
|
+
const sectionText = squiffy.getSectionText(el.dataset.section);
|
|
38
|
+
if (sectionText) {
|
|
39
|
+
newContent = squiffy.processText(sectionText, true);
|
|
40
|
+
}
|
|
41
|
+
} else if (el.dataset.passage) {
|
|
42
|
+
const passageText = squiffy.getPassageText(el.dataset.passage);
|
|
43
|
+
if (passageText) {
|
|
44
|
+
newContent = squiffy.processText(passageText, true);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
newContent = e.value;
|
|
48
|
+
}
|
|
49
|
+
if (oldContent !== newContent) {
|
|
50
|
+
promises.push(fadeReplace(el, newContent));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
await Promise.all(promises);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
let setQueue: Promise<void> = Promise.resolve();
|
|
57
|
+
squiffy.on("set", (e) => {
|
|
58
|
+
setQueue = setQueue.then(() => onSet(e));
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
onWrite(element: HTMLElement) {
|
|
62
|
+
if (!squiffy) return;
|
|
63
|
+
|
|
64
|
+
const selector = ".squiffy-live";
|
|
65
|
+
element.querySelectorAll<HTMLElement>(selector).forEach((el) => {
|
|
66
|
+
if (el.dataset.section) {
|
|
67
|
+
const sectionText = squiffy.getSectionText(el.dataset.section);
|
|
68
|
+
if (sectionText) {
|
|
69
|
+
el.innerHTML = squiffy.processText(sectionText, true);
|
|
70
|
+
}
|
|
71
|
+
} else if (el.dataset.passage) {
|
|
72
|
+
const passageText = squiffy.getPassageText(el.dataset.passage);
|
|
73
|
+
if (passageText) {
|
|
74
|
+
el.innerHTML = squiffy.processText(passageText, true);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
const attribute = el.dataset.attribute;
|
|
78
|
+
el.textContent = attribute ? squiffy.get(attribute) : "";
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PluginHost, SquiffyPlugin } from "../types.plugins.js";
|
|
2
|
+
import Handlebars from "handlebars";
|
|
3
|
+
import type { SafeString } from "handlebars";
|
|
4
|
+
|
|
5
|
+
export function RandomPlugin() : SquiffyPlugin {
|
|
6
|
+
return {
|
|
7
|
+
name: "random",
|
|
8
|
+
init(squiffy: PluginHost) {
|
|
9
|
+
squiffy.registerHelper("random", (items: (string | SafeString)[], options) => {
|
|
10
|
+
const stringItems = items.map(i => i.toString());
|
|
11
|
+
const randomIndex = Math.floor(Math.random() * stringItems.length);
|
|
12
|
+
const item = stringItems[randomIndex];
|
|
13
|
+
|
|
14
|
+
const attribute = options.hash.set as string || "";
|
|
15
|
+
if (attribute) {
|
|
16
|
+
squiffy.set(attribute, item);
|
|
17
|
+
}
|
|
18
|
+
return new Handlebars.SafeString(item);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PluginHost, SquiffyPlugin } from "../types.plugins.js";
|
|
2
|
+
import Handlebars from "handlebars";
|
|
3
|
+
import { fadeReplace } from "../utils.js";
|
|
4
|
+
|
|
5
|
+
export function ReplaceLabel() : SquiffyPlugin {
|
|
6
|
+
return {
|
|
7
|
+
name: "replaceLabel",
|
|
8
|
+
init(squiffy: PluginHost) {
|
|
9
|
+
squiffy.registerHelper("label", (name: string, options) => {
|
|
10
|
+
return new Handlebars.SafeString(`<span class="squiffy-label-${name}">${options.fn(this)}</span>`);
|
|
11
|
+
});
|
|
12
|
+
squiffy.registerHelper("replace", (name: string, options) => {
|
|
13
|
+
const result = options.fn(this);
|
|
14
|
+
const element = squiffy.outputElement.querySelector<HTMLElement>(`.squiffy-label-${name}`);
|
|
15
|
+
if (element) {
|
|
16
|
+
squiffy.addTransition(() => fadeReplace(element, result));
|
|
17
|
+
}
|
|
18
|
+
return "";
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { HandleLinkResult, PluginHost, SquiffyPlugin } from "../types.plugins.js";
|
|
2
|
+
import Handlebars from "handlebars";
|
|
3
|
+
import type { SafeString } from "handlebars";
|
|
4
|
+
|
|
5
|
+
export function RotateSequencePlugin() : SquiffyPlugin {
|
|
6
|
+
const rotateSequence = (squiffy: PluginHost, type: string, items: (string | SafeString)[], options: any) => {
|
|
7
|
+
const stringItems = items.map(i => i.toString());
|
|
8
|
+
const rotation = rotate(stringItems, null);
|
|
9
|
+
const attribute = options.hash.set as string || "";
|
|
10
|
+
if (attribute) {
|
|
11
|
+
squiffy.set(attribute, rotation[0]);
|
|
12
|
+
}
|
|
13
|
+
const optionsString = JSON.stringify(rotation.slice(1)) || "";
|
|
14
|
+
const text = options.hash.show == "next" ? rotation[1] : rotation[0];
|
|
15
|
+
return new Handlebars.SafeString(`<a class="squiffy-link" data-handler="${type}" data-value="${rotation[0]}" data-show="${options.hash.show || ""}" data-options='${optionsString}' data-attribute="${attribute}" role="link">${text}</a>`);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const handleLink = (squiffy: PluginHost, link: HTMLElement, isRotate: boolean) => {
|
|
19
|
+
const result: HandleLinkResult = {};
|
|
20
|
+
const options = JSON.parse(link.getAttribute("data-options")) as string[] || [];
|
|
21
|
+
|
|
22
|
+
const rotateResult = rotate(options, isRotate ? link.getAttribute("data-value") : "");
|
|
23
|
+
|
|
24
|
+
link.innerHTML = link.getAttribute("data-show") == "next" ? rotateResult[1] : rotateResult[0];
|
|
25
|
+
link.setAttribute("data-value", rotateResult[0]);
|
|
26
|
+
link.setAttribute("data-options", JSON.stringify(rotateResult.slice(1)) || "");
|
|
27
|
+
if (!rotateResult[1]) {
|
|
28
|
+
result.disableLink = true;
|
|
29
|
+
}
|
|
30
|
+
const attribute = link.getAttribute("data-attribute");
|
|
31
|
+
if (attribute) {
|
|
32
|
+
squiffy.set(attribute, link.getAttribute("data-value"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const rotate = (options: string[], current: string | null): string[] => {
|
|
39
|
+
const next = options[0];
|
|
40
|
+
const remaining = options.slice(1);
|
|
41
|
+
if (current) remaining.push(current);
|
|
42
|
+
return [next, ...remaining];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
name: "rotateSequence",
|
|
47
|
+
init(squiffy: PluginHost) {
|
|
48
|
+
squiffy.registerHelper("rotate", (items: (string | SafeString)[], options) =>
|
|
49
|
+
rotateSequence(squiffy, "rotate", items, options));
|
|
50
|
+
squiffy.registerHelper("sequence", (items: (string | SafeString)[], options) =>
|
|
51
|
+
rotateSequence(squiffy, "sequence", items, options));
|
|
52
|
+
|
|
53
|
+
squiffy.registerLinkHandler("rotate", (link: HTMLElement) => {
|
|
54
|
+
return handleLink(squiffy, link, true);
|
|
55
|
+
});
|
|
56
|
+
squiffy.registerLinkHandler("sequence", (link: HTMLElement) => {
|
|
57
|
+
return handleLink(squiffy, link, false);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|