squiffy-runtime 6.0.0-alpha.2 → 6.0.0-alpha.20

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.
Files changed (43) hide show
  1. package/LICENSE +22 -0
  2. package/dist/animation.d.ts +11 -0
  3. package/dist/events.d.ts +20 -0
  4. package/dist/import.d.ts +4 -0
  5. package/dist/linkHandler.d.ts +8 -0
  6. package/dist/pluginManager.d.ts +23 -0
  7. package/dist/squiffy.runtime.d.ts +3 -34
  8. package/dist/squiffy.runtime.global.js +126 -0
  9. package/dist/squiffy.runtime.global.js.map +1 -0
  10. package/dist/squiffy.runtime.js +8779 -547
  11. package/dist/squiffy.runtime.js.map +1 -0
  12. package/dist/squiffy.runtime.test.d.ts +1 -0
  13. package/dist/state.d.ts +19 -0
  14. package/dist/textProcessor.d.ts +11 -0
  15. package/dist/types.d.ts +57 -0
  16. package/dist/types.plugins.d.ts +27 -0
  17. package/dist/updater.d.ts +2 -0
  18. package/dist/utils.d.ts +1 -0
  19. package/package.json +26 -5
  20. package/src/__snapshots__/squiffy.runtime.test.ts.snap +138 -0
  21. package/src/animation.ts +68 -0
  22. package/src/events.ts +41 -0
  23. package/src/import.ts +5 -0
  24. package/src/linkHandler.ts +18 -0
  25. package/src/pluginManager.ts +74 -0
  26. package/src/plugins/animate.ts +97 -0
  27. package/src/plugins/index.ts +13 -0
  28. package/src/plugins/live.ts +83 -0
  29. package/src/plugins/random.ts +22 -0
  30. package/src/plugins/replaceLabel.ts +22 -0
  31. package/src/plugins/rotateSequence.ts +61 -0
  32. package/src/squiffy.runtime.test.ts +677 -0
  33. package/src/squiffy.runtime.ts +528 -499
  34. package/src/state.ts +106 -0
  35. package/src/textProcessor.ts +76 -0
  36. package/src/types.plugins.ts +41 -0
  37. package/src/types.ts +81 -0
  38. package/src/updater.ts +77 -0
  39. package/src/utils.ts +17 -0
  40. package/tsconfig.json +4 -1
  41. package/vite.config.ts +36 -0
  42. package/vitest.config.ts +9 -0
  43. package/vitest.setup.ts +16 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { Emitter, SquiffyEventMap } from './events.js';
2
+ export declare class State {
3
+ persist: boolean;
4
+ storyId: string;
5
+ onSet: (attribute: string, value: any) => void;
6
+ onSetInternal?: (attribute: string, oldValue: any, newValue: any) => void;
7
+ store: Record<string, any>;
8
+ emitter: Emitter<SquiffyEventMap>;
9
+ constructor(persist: boolean, storyId: string, onSet: (attribute: string, value: any) => void, emitter: Emitter<SquiffyEventMap>, onSetInternal?: (attribute: string, oldValue: any, newValue: any) => void);
10
+ private usePersistentStorage;
11
+ set(attribute: string, value: any): void;
12
+ setInternal(attribute: string, value: any, raiseEvents: boolean): void;
13
+ get(attribute: string): any;
14
+ getStore(): Record<string, any>;
15
+ load(): void;
16
+ reset(): void;
17
+ setSeen(sectionName: string): void;
18
+ getSeen(sectionName: string): boolean;
19
+ }
@@ -0,0 +1,11 @@
1
+ import { default as Handlebars } from 'handlebars';
2
+ import { Section, Story } from './types.js';
3
+ import { State } from './state.js';
4
+ export declare class TextProcessor {
5
+ story: Story;
6
+ state: State;
7
+ getCurrentSection: () => Section;
8
+ handlebars: typeof Handlebars;
9
+ constructor(story: Story, state: State, currentSection: () => Section);
10
+ process(text: string, inline: boolean): string;
11
+ }
@@ -0,0 +1,57 @@
1
+ import { SquiffyEventHandler, SquiffyEventMap } from './events.js';
2
+ export interface SquiffyInitOptions {
3
+ element: HTMLElement;
4
+ story: Story;
5
+ scroll?: string;
6
+ persist?: boolean;
7
+ onSet?: (attribute: string, value: any) => void;
8
+ }
9
+ export interface SquiffySettings {
10
+ scroll: string;
11
+ persist: boolean;
12
+ onSet: (attribute: string, value: any) => void;
13
+ }
14
+ export interface SquiffyApi {
15
+ begin: () => Promise<void>;
16
+ restart: () => void;
17
+ get: (attribute: string) => any;
18
+ set: (attribute: string, value: any) => void;
19
+ clickLink: (link: HTMLElement) => Promise<boolean>;
20
+ update: (story: Story) => void;
21
+ goBack: () => void;
22
+ on<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): () => void;
23
+ off<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): void;
24
+ once<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): () => void;
25
+ }
26
+ interface SquiffyJsFunctionApi {
27
+ get: (attribute: string) => any;
28
+ set: (attribute: string, value: any) => void;
29
+ ui: {
30
+ transition: (f: any) => void;
31
+ };
32
+ story: {
33
+ go: (section: string) => void;
34
+ };
35
+ }
36
+ export interface Story {
37
+ js: ((squiffy: SquiffyJsFunctionApi, get: (attribute: string) => any, set: (attribute: string, value: any) => void) => void)[];
38
+ start: string;
39
+ id?: string | null;
40
+ uiJsIndex?: number;
41
+ sections: Record<string, Section>;
42
+ }
43
+ export interface Section {
44
+ text?: string;
45
+ clear?: boolean;
46
+ attributes?: string[];
47
+ jsIndex?: number;
48
+ passages?: Record<string, Passage>;
49
+ passageCount?: number;
50
+ }
51
+ export interface Passage {
52
+ text?: string;
53
+ clear?: boolean;
54
+ attributes?: string[];
55
+ jsIndex?: number;
56
+ }
57
+ export {};
@@ -0,0 +1,27 @@
1
+ import { HelperDelegate } from 'handlebars';
2
+ import { SquiffyEventHandler, SquiffyEventMap } from './events.js';
3
+ import { Animation } from './animation.js';
4
+ export interface SquiffyPlugin {
5
+ name: string;
6
+ init(host: PluginHost): void | Promise<void>;
7
+ onWrite?(el: HTMLElement): void;
8
+ onLoad?(): void;
9
+ }
10
+ export interface HandleLinkResult {
11
+ disableLink?: boolean;
12
+ }
13
+ export interface PluginHost {
14
+ outputElement: HTMLElement;
15
+ registerHelper(name: string, helper: HelperDelegate): void;
16
+ registerLinkHandler(type: string, handler: (el: HTMLElement) => HandleLinkResult): void;
17
+ get(attribute: string): any;
18
+ set(attribute: string, value: any): void;
19
+ getSectionText(name: string): string | null;
20
+ getPassageText(name: string): string | null;
21
+ processText: (text: string, inline: boolean) => string;
22
+ addTransition: (fn: () => Promise<void>) => void;
23
+ animation: Animation;
24
+ on<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): () => void;
25
+ off<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): void;
26
+ once<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): () => void;
27
+ }
@@ -0,0 +1,2 @@
1
+ import { Story } from './types.js';
2
+ export declare function updateStory(oldStory: Story, newStory: Story, outputElement: HTMLElement, ui: any, disableLink: (link: Element) => void): void;
@@ -0,0 +1 @@
1
+ export declare function fadeReplace(element: HTMLElement, text: string): Promise<void>;
package/package.json CHANGED
@@ -1,16 +1,37 @@
1
1
  {
2
2
  "name": "squiffy-runtime",
3
- "version": "6.0.0-alpha.2",
3
+ "version": "6.0.0-alpha.20",
4
+ "type": "module",
4
5
  "main": "dist/squiffy.runtime.js",
5
6
  "types": "dist/squiffy.runtime.d.ts",
6
7
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1",
8
- "build": "tsc"
8
+ "dev": "vitest",
9
+ "test": "vitest --run",
10
+ "build": "vite build",
11
+ "prepublishOnly": "npm run build && npm run test"
9
12
  },
10
13
  "author": "Alex Warren",
14
+ "contributors": [
15
+ "CrisisSDK",
16
+ "mrangel",
17
+ "Luis Felipe Morales"
18
+ ],
11
19
  "license": "MIT",
12
20
  "description": "",
13
21
  "devDependencies": {
14
- "typescript": "^5.6.2"
15
- }
22
+ "@types/jsdom": "^21.1.7",
23
+ "css.escape": "^1.5.1",
24
+ "global-jsdom": "^25.0.0",
25
+ "jsdom": "^25.0.0",
26
+ "squiffy-compiler": "^6.0.0-alpha.20",
27
+ "typescript": "^5.6.2",
28
+ "vite": "^7.1.3",
29
+ "vite-plugin-dts": "^4.5.4"
30
+ },
31
+ "dependencies": {
32
+ "animejs": "^4.1.3",
33
+ "handlebars": "^4.7.8",
34
+ "marked": "^16.2.0"
35
+ },
36
+ "gitHead": "eee4b8836ed791b68d8d56b0d5071129e6214d2b"
16
37
  }
@@ -0,0 +1,138 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`"Hello world" script should run 1`] = `
4
+ "
5
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Hello world</p></div></div></div></div>"
6
+ `;
7
+
8
+ exports[`Click a passage link 1`] = `
9
+ "
10
+ <div><div class="squiffy-output-section" data-section="Introduction" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
11
+ <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
12
+ <p>This should be <a class="squiffy-link link-passage" data-passage="passage" role="link" tabindex="0">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
13
+ <p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
14
+ <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
15
+ <p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
16
+ <p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
17
+ <p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div></div></div>"
18
+ `;
19
+
20
+ exports[`Click a passage link 2`] = `
21
+ "
22
+ <div><div class="squiffy-output-section" data-section="Introduction" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
23
+ <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
24
+ <p>This should be <a class="squiffy-link link-passage disabled" data-passage="passage" role="link" tabindex="-1">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
25
+ <p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
26
+ <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
27
+ <p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
28
+ <p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
29
+ <p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div><div class="squiffy-output-passage" data-passage="passage" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;Introduction&quot;]}"><div class="squiffy-output-block"><div data-source="[[Introduction]][passage]"><p>Here's some text for the passage.</p></div></div></div></div></div>"
30
+ `;
31
+
32
+ exports[`Click a passage link and go back 1`] = `
33
+ "
34
+ <div><div class="squiffy-output-section" data-section="Introduction" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
35
+ <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
36
+ <p>This should be <a class="squiffy-link link-passage" data-passage="passage" role="link">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
37
+ <p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
38
+ <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
39
+ <p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
40
+ <p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
41
+ <p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div></div></div>"
42
+ `;
43
+
44
+ exports[`Click a section link 1`] = `
45
+ "
46
+ <div><div class="squiffy-output-section" data-section="Introduction" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
47
+ <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
48
+ <p>This should be <a class="squiffy-link link-passage" data-passage="passage" role="link" tabindex="0">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
49
+ <p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
50
+ <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
51
+ <p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
52
+ <p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
53
+ <p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div></div></div>"
54
+ `;
55
+
56
+ exports[`Click a section link 2`] = `
57
+ "
58
+ <div><div class="squiffy-output-section" data-section="Introduction" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
59
+ <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
60
+ <p>This should be <a class="squiffy-link link-passage" data-passage="passage" role="link" tabindex="0">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
61
+ <p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
62
+ <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
63
+ <p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
64
+ <p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
65
+ <p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div></div><div class="squiffy-output-section" data-section="section 3" data-undo="{&quot;_section&quot;:&quot;Introduction&quot;,&quot;_seen_sections&quot;:[&quot;Introduction&quot;],&quot;_turncount&quot;:0}"><div class="squiffy-output-block"><div data-source="[[section 3]]"><p>Another section is here, with passages <a class="squiffy-link link-passage" data-passage="a" role="link" tabindex="0">a</a> and <a class="squiffy-link link-passage" data-passage="b" role="link" tabindex="0">b</a>.</p></div></div></div></div>"
66
+ `;
67
+
68
+ exports[`Click a section link and go back 1`] = `
69
+ "
70
+ <div><div class="squiffy-output-section" data-section="Introduction" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
71
+ <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
72
+ <p>This should be <a class="squiffy-link link-passage" data-passage="passage" role="link" tabindex="0">a link to a passage</a>. Here's <a class="squiffy-link link-passage" data-passage="passage2" role="link" tabindex="0">another one</a>.</p>
73
+ <p>You don't need to specify a name - for example, this <a class="squiffy-link link-passage" data-passage="link" role="link" tabindex="0">link</a> and this <a class="squiffy-link link-section" data-section="section" role="link" tabindex="0">section</a>.</p>
74
+ <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
75
+ <p>This line has links to <a class="squiffy-link link-section" data-section="section 3" role="link" tabindex="0">section 3</a> and <a class="squiffy-link link-section" data-section="section four" role="link" tabindex="0">section 4</a>.</p>
76
+ <p>This line has links to <a class="squiffy-link link-passage" data-passage="passage 3" role="link" tabindex="0">passage 3</a> and <a class="squiffy-link link-passage" data-passage="passage four" role="link" tabindex="0">passage 4</a>.</p>
77
+ <p>Oh look - <a class="squiffy-link link-passage" data-passage="it's a passage with an apostrophe" role="link" tabindex="0">it's a passage with an apostrophe</a>.</p></div></div></div></div>"
78
+ `;
79
+
80
+ exports[`Delete passage 1`] = `
81
+ "
82
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div><div class="squiffy-output-passage" data-passage="a" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;]}"><div class="squiffy-output-block"><div data-source="[[_default]][a]"><p>New passage</p></div></div></div></div></div>"
83
+ `;
84
+
85
+ exports[`Delete passage 2`] = `
86
+ "
87
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div></div></div>"
88
+ `;
89
+
90
+ exports[`Delete section 1`] = `
91
+ "
92
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-section" data-section="a" role="link" tabindex="0">a</a></p></div></div></div><div class="squiffy-output-section" data-section="a" data-undo="{&quot;_section&quot;:&quot;_default&quot;,&quot;_seen_sections&quot;:[&quot;_default&quot;],&quot;_turncount&quot;:0}"><div class="squiffy-output-block"><div data-source="[[a]]"><p>New section</p></div></div></div></div>"
93
+ `;
94
+
95
+ exports[`Delete section 2`] = `
96
+ "
97
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-section" data-section="a" role="link" tabindex="0">a</a></p></div></div></div></div>"
98
+ `;
99
+
100
+ exports[`Going back handling @clear and attribute changes 1`] = `
101
+ "
102
+ <div><div class="squiffy-clear-stack" style="display: none;"><div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Choose: <a class="squiffy-link link-passage" data-passage="a" role="link">a</a>, <a class="squiffy-link link-passage disabled" data-passage="b" role="link" tabindex="-1">b</a></p></div></div><div class="squiffy-output-passage" data-passage="b" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;],&quot;test&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]][b]"><p>You chose b. Now <a class="squiffy-link link-section" data-section="continue" role="link" tabindex="0">continue</a></p></div></div></div></div><div class="squiffy-output-section" data-section="continue" data-undo="{&quot;_section&quot;:&quot;_default&quot;,&quot;_seen_sections&quot;:[&quot;_default&quot;,&quot;b&quot;],&quot;test&quot;:456,&quot;_turncount&quot;:1}"><div class="squiffy-output-block"><div data-source="[[continue]]"><p>Now choose: <a class="squiffy-link link-passage disabled" data-passage="c" role="link" tabindex="-1">c</a>, <a class="squiffy-link link-passage" data-passage="d" role="link" tabindex="0">d</a></p></div></div></div></div></div><div class="squiffy-output-section" data-section="continue" data-clear="true"><div class="squiffy-output-passage" data-passage="c" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;,&quot;b&quot;,&quot;continue&quot;],&quot;test&quot;:789}"><div class="squiffy-output-block"><div data-source="[[continue]][c]"><p>You chose c. Now <a class="squiffy-link link-section" data-section="finish" role="link" tabindex="0">finish</a></p></div></div></div></div></div>"
103
+ `;
104
+
105
+ exports[`Going back handling @clear and attribute changes 2`] = `
106
+ "
107
+ <div><div class="squiffy-clear-stack" style="display: none;"></div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Choose: <a class="squiffy-link link-passage" data-passage="a" role="link">a</a>, <a class="squiffy-link link-passage disabled" data-passage="b" role="link" tabindex="-1">b</a></p></div></div><div class="squiffy-output-passage" data-passage="b" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;],&quot;test&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]][b]"><p>You chose b. Now <a class="squiffy-link link-section" data-section="continue" role="link" tabindex="0">continue</a></p></div></div></div></div><div class="squiffy-output-section" data-section="continue" data-undo="{&quot;_section&quot;:&quot;_default&quot;,&quot;_seen_sections&quot;:[&quot;_default&quot;,&quot;b&quot;],&quot;test&quot;:456,&quot;_turncount&quot;:1}"><div class="squiffy-output-block"><div data-source="[[continue]]"><p>Now choose: <a class="squiffy-link link-passage" data-passage="c" role="link">c</a>, <a class="squiffy-link link-passage" data-passage="d" role="link" tabindex="0">d</a></p></div></div></div></div>"
108
+ `;
109
+
110
+ exports[`Update default section output 1`] = `
111
+ "
112
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Hello world</p></div></div></div></div>"
113
+ `;
114
+
115
+ exports[`Update default section output 2`] = `
116
+ "
117
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Updated content</p></div></div></div></div>"
118
+ `;
119
+
120
+ exports[`Update passage output - passage name "a" 1`] = `
121
+ "
122
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div><div class="squiffy-output-passage" data-passage="a" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;]}"><div class="squiffy-output-block"><div data-source="[[_default]][a]"><p>Passage a content</p></div></div></div></div></div>"
123
+ `;
124
+
125
+ exports[`Update passage output - passage name "a" 2`] = `
126
+ "
127
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a" role="link" tabindex="-1">a</a></p></div></div><div class="squiffy-output-passage" data-passage="a" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;]}"><div class="squiffy-output-block"><div data-source="[[_default]][a]"><p>Updated passage content</p></div></div></div></div></div>"
128
+ `;
129
+
130
+ exports[`Update passage output - passage name "a'1" 1`] = `
131
+ "
132
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a'1" role="link" tabindex="-1">a'1</a></p></div></div><div class="squiffy-output-passage" data-passage="a'1" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;]}"><div class="squiffy-output-block"><div data-source="[[_default]][a'1]"><p>Passage a content</p></div></div></div></div></div>"
133
+ `;
134
+
135
+ exports[`Update passage output - passage name "a'1" 2`] = `
136
+ "
137
+ <div><div class="squiffy-output-section" data-section="_default" data-undo="{&quot;_section&quot;:null,&quot;_seen_sections&quot;:null,&quot;_turncount&quot;:null}"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Click this: <a class="squiffy-link link-passage disabled" data-passage="a'1" role="link" tabindex="-1">a'1</a></p></div></div><div class="squiffy-output-passage" data-passage="a'1" data-undo="{&quot;_turncount&quot;:0,&quot;_seen_sections&quot;:[&quot;_default&quot;]}"><div class="squiffy-output-block"><div data-source="[[_default]][a'1]"><p>Updated passage content</p></div></div></div></div></div>"
138
+ `;
@@ -0,0 +1,68 @@
1
+ import { animate, stagger, text } from "animejs";
2
+
3
+ export class Animation {
4
+ animations: {[name: string]: (el: HTMLElement, params: Record<string, any>, onComplete: () => void, loop: boolean) => void} = {};
5
+ linkAnimations = new Map<HTMLElement, () => void>();
6
+
7
+ registerAnimation(name: string, handler: (el: HTMLElement, params: Record<string, any>, onComplete: () => void, loop: boolean) => void) {
8
+ this.animations[name] = handler;
9
+ }
10
+
11
+ runAnimation(name: string, el: HTMLElement, params: Record<string, any>, onComplete: () => void, loop: boolean) {
12
+ const animation = this.animations[name];
13
+ if (animation) {
14
+ animation(el, params, onComplete, loop);
15
+ } else {
16
+ console.warn(`No animation registered with name: ${name}`);
17
+ onComplete();
18
+ }
19
+ }
20
+
21
+ addLinkAnimation(link: HTMLElement, fn: () => void) {
22
+ this.linkAnimations.set(link, fn);
23
+ }
24
+
25
+ runLinkAnimation(link: HTMLElement) {
26
+ const fn = this.linkAnimations.get(link);
27
+ if (fn) {
28
+ fn();
29
+ }
30
+ }
31
+
32
+ constructor() {
33
+ this.registerAnimation("typewriter", function(el, params, onComplete, loop) {
34
+ const { chars } = text.split(el, { words: false, chars: true });
35
+
36
+ const fadeDuration = params.fadeDuration || 100; // ms fade-in per character
37
+ const interval = params.interval || 100; // ms between each character appearing
38
+
39
+ animate(chars, {
40
+ opacity: [0, 1],
41
+ easing: "linear",
42
+ duration: fadeDuration,
43
+ delay: stagger(interval, { start: params.start || 0 }),
44
+ loop: loop,
45
+ onComplete: onComplete
46
+ });
47
+ });
48
+
49
+ this.registerAnimation("toast", function(el, params, onComplete, loop) {
50
+ const { words } = text.split(el, { words: true, chars: false });
51
+
52
+ const fadeDuration = params.fadeDuration || 100; // ms fade-in per word
53
+ const interval = params.interval || 200; // ms between each word appearing
54
+
55
+ animate(words, {
56
+ opacity: [0, 1],
57
+ y: [
58
+ { to: ["100%", "0%"] },
59
+ ],
60
+ easing: "linear",
61
+ duration: fadeDuration,
62
+ delay: stagger(interval, { start: params.start || 0 }),
63
+ loop: loop,
64
+ onComplete: onComplete
65
+ });
66
+ });
67
+ }
68
+ }
package/src/events.ts ADDED
@@ -0,0 +1,41 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-function-type */
2
+
3
+ export type SquiffyEventMap = {
4
+ linkClick: { linkType: string }; // a story link was clicked
5
+ set: { attribute: string, value: any }; // an attribute was set
6
+ canGoBackChanged: { canGoBack: boolean }; // whether the "back" button should be enabled
7
+ };
8
+
9
+ export type SquiffyEventHandler<E extends keyof SquiffyEventMap> =
10
+ (payload: SquiffyEventMap[E]) => void;
11
+
12
+ export class Emitter<Events extends Record<string, any>> {
13
+ private listeners = new Map<keyof Events, Set<Function>>();
14
+
15
+ on<E extends keyof Events>(event: E, handler: (p: Events[E]) => void) {
16
+ if (!this.listeners.has(event)) this.listeners.set(event, new Set());
17
+ this.listeners.get(event)!.add(handler);
18
+ return () => this.off(event, handler);
19
+ }
20
+
21
+ off<E extends keyof Events>(event: E, handler: (p: Events[E]) => void) {
22
+ this.listeners.get(event)?.delete(handler);
23
+ }
24
+
25
+ once<E extends keyof Events>(event: E, handler: (p: Events[E]) => void) {
26
+ const off = this.on(event, (payload) => {
27
+ off();
28
+ handler(payload);
29
+ });
30
+ return off;
31
+ }
32
+
33
+ emit<E extends keyof Events>(event: E, payload: Events[E]) {
34
+ this.listeners.get(event)?.forEach(h => {
35
+ try { (h as any)(payload); } catch (err) {
36
+ // Swallow so a bad handler doesn't break the game; optionally log.
37
+ console.error(`[Squiffy] handler for "${String(event)}" failed`, err);
38
+ }
39
+ });
40
+ }
41
+ }
package/src/import.ts ADDED
@@ -0,0 +1,5 @@
1
+ import * as animejs from "animejs";
2
+
3
+ export const imports = {
4
+ animejs
5
+ };
@@ -0,0 +1,18 @@
1
+ import { HandleLinkResult } from "./types.plugins.js";
2
+
3
+ export class LinkHandler {
4
+ linkHandlers: {[type: string]: (el: HTMLElement) => HandleLinkResult} = {};
5
+
6
+ registerLinkHandler(type: string, handler: (el: HTMLElement) => HandleLinkResult) {
7
+ this.linkHandlers[type] = handler;
8
+ }
9
+
10
+ handleLink(link: HTMLElement): [found: boolean, type: string, result: HandleLinkResult] {
11
+ const type = link.getAttribute("data-handler") || "";
12
+ const handler = this.linkHandlers[type];
13
+ if (handler) {
14
+ return [true, type, handler(link)];
15
+ }
16
+ return [false, type, null];
17
+ }
18
+ }
@@ -0,0 +1,74 @@
1
+ import { TextProcessor } from "./textProcessor.js";
2
+ import { SquiffyPlugin } from "./types.plugins.js";
3
+ import { State } from "./state.js";
4
+ import { LinkHandler } from "./linkHandler.js";
5
+ import { Emitter, SquiffyEventMap } from "./events.js";
6
+ import { Animation } from "./animation.js";
7
+
8
+ export class PluginManager {
9
+ outputElement: HTMLElement;
10
+ textProcessor: TextProcessor;
11
+ state: State;
12
+ linkHandler: LinkHandler;
13
+ getSectionText: (name: string) => string | null;
14
+ getPassageText: (name: string) => string | null;
15
+ processText: (text: string, inline: boolean) => string;
16
+ addTransition: (fn: () => Promise<void>) => void;
17
+ emitter: Emitter<SquiffyEventMap>;
18
+ plugins: SquiffyPlugin[] = [];
19
+ animation: Animation;
20
+
21
+ constructor(outputElement: HTMLElement,
22
+ textProcessor: TextProcessor,
23
+ state: State,
24
+ linkHandler: LinkHandler,
25
+ getSectionText: (name: string) => string | null,
26
+ getPassageText: (name: string) => string | null,
27
+ processText: (text: string, inline: boolean) => string,
28
+ addTransition: (fn: () => Promise<void>) => void,
29
+ animation: Animation,
30
+ emitter: Emitter<SquiffyEventMap>) {
31
+ this.outputElement = outputElement;
32
+ this.textProcessor = textProcessor;
33
+ this.state = state;
34
+ this.linkHandler = linkHandler;
35
+ this.getSectionText = getSectionText;
36
+ this.getPassageText = getPassageText;
37
+ this.processText = processText;
38
+ this.addTransition = addTransition;
39
+ this.animation = animation;
40
+ this.emitter = emitter;
41
+ }
42
+
43
+ add(plugin: SquiffyPlugin) {
44
+ const instance = plugin.init({
45
+ outputElement: this.outputElement,
46
+ registerHelper: (name, helper) => {
47
+ this.textProcessor.handlebars.registerHelper(name, helper);
48
+ },
49
+ registerLinkHandler: (type, handler) => {
50
+ this.linkHandler.registerLinkHandler(type, handler);
51
+ },
52
+ get: (attribute) => this.state.get(attribute),
53
+ set: (attribute, value) => this.state.set(attribute, value),
54
+ getSectionText: this.getSectionText,
55
+ getPassageText: this.getPassageText,
56
+ processText: this.processText,
57
+ addTransition: this.addTransition,
58
+ animation: this.animation,
59
+ on: (e, h) => this.emitter.on(e, h),
60
+ off: (e, h) => this.emitter.off(e, h),
61
+ once: (e, h) => this.emitter.once(e, h),
62
+ });
63
+ this.plugins.push(plugin);
64
+ return instance;
65
+ }
66
+
67
+ onWrite(el: HTMLElement) {
68
+ this.plugins.forEach(p => p.onWrite?.(el));
69
+ }
70
+
71
+ onLoad() {
72
+ this.plugins.forEach(p => p.onLoad?.());
73
+ }
74
+ }