squiffy-runtime 6.0.0-alpha.15 → 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.
Files changed (45) hide show
  1. package/dist/animation.d.ts +11 -0
  2. package/dist/events.d.ts +8 -3
  3. package/dist/import.d.ts +4 -0
  4. package/dist/linkHandler.d.ts +8 -0
  5. package/dist/pluginManager.d.ts +23 -0
  6. package/dist/squiffy.runtime.d.ts +2 -2
  7. package/dist/squiffy.runtime.global.js +126 -0
  8. package/dist/squiffy.runtime.global.js.map +1 -0
  9. package/dist/squiffy.runtime.js +8785 -487
  10. package/dist/squiffy.runtime.js.map +1 -0
  11. package/dist/state.d.ts +19 -0
  12. package/dist/textProcessor.d.ts +8 -13
  13. package/dist/types.d.ts +5 -2
  14. package/dist/types.plugins.d.ts +27 -0
  15. package/dist/updater.d.ts +2 -0
  16. package/dist/utils.d.ts +1 -2
  17. package/package.json +12 -5
  18. package/src/__snapshots__/squiffy.runtime.test.ts.snap +53 -19
  19. package/src/animation.ts +68 -0
  20. package/src/events.ts +9 -10
  21. package/src/import.ts +5 -0
  22. package/src/linkHandler.ts +18 -0
  23. package/src/pluginManager.ts +74 -0
  24. package/src/plugins/animate.ts +97 -0
  25. package/src/plugins/index.ts +13 -0
  26. package/src/plugins/live.ts +83 -0
  27. package/src/plugins/random.ts +22 -0
  28. package/src/plugins/replaceLabel.ts +22 -0
  29. package/src/plugins/rotateSequence.ts +61 -0
  30. package/src/squiffy.runtime.test.ts +306 -134
  31. package/src/squiffy.runtime.ts +460 -332
  32. package/src/state.ts +106 -0
  33. package/src/textProcessor.ts +61 -164
  34. package/src/types.plugins.ts +41 -0
  35. package/src/types.ts +5 -2
  36. package/src/updater.ts +77 -0
  37. package/src/utils.ts +15 -12
  38. package/vite.config.ts +36 -0
  39. package/vitest.config.ts +9 -0
  40. package/vitest.setup.ts +16 -0
  41. package/dist/events.js +0 -35
  42. package/dist/squiffy.runtime.test.js +0 -394
  43. package/dist/textProcessor.js +0 -166
  44. package/dist/types.js +0 -1
  45. package/dist/utils.js +0 -14
@@ -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
+ }
@@ -1,16 +1,11 @@
1
- import { Section } from "./types.js";
1
+ import { default as Handlebars } from 'handlebars';
2
+ import { Section, Story } from './types.js';
3
+ import { State } from './state.js';
2
4
  export declare class TextProcessor {
3
- get: (attribute: string) => any;
4
- set: (attribute: string, value: any) => void;
5
- story: any;
5
+ story: Story;
6
+ state: State;
6
7
  getCurrentSection: () => Section;
7
- seen: (section: string) => boolean;
8
- processAttributes: (attributes: string[]) => void;
9
- constructor(get: (attribute: string) => any, set: (attribute: string, value: any) => void, story: any, currentSection: () => Section, seen: (section: string) => boolean, processAttributes: (attributes: string[]) => void);
10
- process(text: string, data: any): string;
11
- processTextCommand(text: string, data: any): any;
12
- processTextCommand_If(section: string, data: any): string;
13
- processTextCommand_Else(section: string, data: any): string;
14
- processTextCommand_Label(section: string, data: any): string;
15
- processTextCommand_Rotate(type: string, section: string): string;
8
+ handlebars: typeof Handlebars;
9
+ constructor(story: Story, state: State, currentSection: () => Section);
10
+ process(text: string, inline: boolean): string;
16
11
  }
package/dist/types.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SquiffyEventHandler, SquiffyEventMap } from "./events.js";
1
+ import { SquiffyEventHandler, SquiffyEventMap } from './events.js';
2
2
  export interface SquiffyInitOptions {
3
3
  element: HTMLElement;
4
4
  story: Story;
@@ -12,11 +12,13 @@ export interface SquiffySettings {
12
12
  onSet: (attribute: string, value: any) => void;
13
13
  }
14
14
  export interface SquiffyApi {
15
+ begin: () => Promise<void>;
15
16
  restart: () => void;
16
17
  get: (attribute: string) => any;
17
18
  set: (attribute: string, value: any) => void;
18
- clickLink: (link: HTMLElement) => boolean;
19
+ clickLink: (link: HTMLElement) => Promise<boolean>;
19
20
  update: (story: Story) => void;
21
+ goBack: () => void;
20
22
  on<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): () => void;
21
23
  off<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): void;
22
24
  once<E extends keyof SquiffyEventMap>(event: E, handler: SquiffyEventHandler<E>): () => void;
@@ -35,6 +37,7 @@ export interface Story {
35
37
  js: ((squiffy: SquiffyJsFunctionApi, get: (attribute: string) => any, set: (attribute: string, value: any) => void) => void)[];
36
38
  start: string;
37
39
  id?: string | null;
40
+ uiJsIndex?: number;
38
41
  sections: Record<string, Section>;
39
42
  }
40
43
  export interface Section {
@@ -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;
package/dist/utils.d.ts CHANGED
@@ -1,2 +1 @@
1
- export declare function startsWith(string: string, prefix: string): boolean;
2
- export declare function rotate(options: string, current: string | null): string[];
1
+ export declare function fadeReplace(element: HTMLElement, text: string): Promise<void>;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "squiffy-runtime",
3
- "version": "6.0.0-alpha.15",
3
+ "version": "6.0.0-alpha.17",
4
4
  "type": "module",
5
5
  "main": "dist/squiffy.runtime.js",
6
6
  "types": "dist/squiffy.runtime.d.ts",
7
7
  "scripts": {
8
8
  "dev": "vitest",
9
9
  "test": "vitest --run",
10
- "build": "tsc",
10
+ "build": "vite build",
11
11
  "prepublishOnly": "npm run build && npm run test"
12
12
  },
13
13
  "author": "Alex Warren",
@@ -20,11 +20,18 @@
20
20
  "description": "",
21
21
  "devDependencies": {
22
22
  "@types/jsdom": "^21.1.7",
23
+ "css.escape": "^1.5.1",
23
24
  "global-jsdom": "^25.0.0",
24
25
  "jsdom": "^25.0.0",
25
- "squiffy-compiler": "^6.0.0-alpha.15",
26
+ "squiffy-compiler": "^6.0.0-alpha.17",
26
27
  "typescript": "^5.6.2",
27
- "vitest": "^2.1.1"
28
+ "vite": "^7.1.3",
29
+ "vite-plugin-dts": "^4.5.4"
28
30
  },
29
- "gitHead": "a326d226beb6121dcdf528e97d287deba553fe39"
31
+ "dependencies": {
32
+ "animejs": "^4.1.3",
33
+ "handlebars": "^4.7.8",
34
+ "marked": "^16.2.0"
35
+ },
36
+ "gitHead": "59e410271573c741dbc7c6031b7aab5619d9d1c7"
30
37
  }
@@ -2,103 +2,137 @@
2
2
 
3
3
  exports[`"Hello world" script should run 1`] = `
4
4
  "
5
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Hello world</p></div></div></div>"
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
6
  `;
7
7
 
8
8
  exports[`Click a passage link 1`] = `
9
9
  "
10
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
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
11
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
12
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
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
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
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
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>"
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
18
  `;
19
19
 
20
20
  exports[`Click a passage link 2`] = `
21
21
  "
22
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
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
23
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
24
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
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
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
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
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-block"><div data-source="[[Introduction]][passage]"><p>Here's some text for the passage.</p></div></div></div>"
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>"
30
42
  `;
31
43
 
32
44
  exports[`Click a section link 1`] = `
33
45
  "
34
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
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>
35
47
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
36
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>
37
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>
38
50
  <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
39
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>
40
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>
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>"
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>"
42
54
  `;
43
55
 
44
56
  exports[`Click a section link 2`] = `
45
57
  "
46
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="Introduction"><div class="squiffy-output-block"><div data-source="[[Introduction]]"><h1>Squiffy Test</h1>
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>
47
59
  <p>This is <a href="http://textadventures.co.uk">a website link</a>.</p>
48
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>
49
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>
50
62
  <p>And this goes to the <a class="squiffy-link link-section" data-section="section2" role="link" tabindex="0">next section</a>.</p>
51
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>
52
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>
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 class="squiffy-output-section" id="squiffy-section-2" data-section="section 3"><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>"
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>"
54
78
  `;
55
79
 
56
80
  exports[`Delete passage 1`] = `
57
81
  "
58
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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-block"><div data-source="[[_default]][a]"><p>New passage</p></div></div></div>"
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>"
59
83
  `;
60
84
 
61
85
  exports[`Delete passage 2`] = `
62
86
  "
63
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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>"
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>"
64
88
  `;
65
89
 
66
90
  exports[`Delete section 1`] = `
67
91
  "
68
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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" id="squiffy-section-2" data-section="a"><div class="squiffy-output-block"><div data-source="[[a]]"><p>New section</p></div></div></div>"
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>"
69
93
  `;
70
94
 
71
95
  exports[`Delete section 2`] = `
72
96
  "
73
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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>"
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>"
74
108
  `;
75
109
 
76
110
  exports[`Update default section output 1`] = `
77
111
  "
78
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Hello world</p></div></div></div>"
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>"
79
113
  `;
80
114
 
81
115
  exports[`Update default section output 2`] = `
82
116
  "
83
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><div class="squiffy-output-block"><div data-source="[[_default]]"><p>Updated content</p></div></div></div>"
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>"
84
118
  `;
85
119
 
86
120
  exports[`Update passage output - passage name "a" 1`] = `
87
121
  "
88
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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-block"><div data-source="[[_default]][a]"><p>Passage a content</p></div></div></div>"
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>"
89
123
  `;
90
124
 
91
125
  exports[`Update passage output - passage name "a" 2`] = `
92
126
  "
93
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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-block"><div data-source="[[_default]][a]"><p>Updated passage content</p></div></div></div>"
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>"
94
128
  `;
95
129
 
96
130
  exports[`Update passage output - passage name "a'1" 1`] = `
97
131
  "
98
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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-block"><div data-source="[[_default]][a'1]"><p>Passage a content</p></div></div></div>"
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>"
99
133
  `;
100
134
 
101
135
  exports[`Update passage output - passage name "a'1" 2`] = `
102
136
  "
103
- <div class="squiffy-output-section" id="squiffy-section-1" data-section="_default"><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-block"><div data-source="[[_default]][a'1]"><p>Updated passage content</p></div></div></div>"
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>"
104
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 CHANGED
@@ -1,7 +1,9 @@
1
- type LinkType = 'section' | 'passage' | 'rotate' | 'sequence';
1
+ /* eslint-disable @typescript-eslint/no-unsafe-function-type */
2
2
 
3
3
  export type SquiffyEventMap = {
4
- linkClick: { linkType: LinkType }; // a story link was clicked
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
5
7
  };
6
8
 
7
9
  export type SquiffyEventHandler<E extends keyof SquiffyEventMap> =
@@ -29,14 +31,11 @@ export class Emitter<Events extends Record<string, any>> {
29
31
  }
30
32
 
31
33
  emit<E extends keyof Events>(event: E, payload: Events[E]) {
32
- // Fire handlers asynchronously so the runtime isn't blocked by user code.
33
- queueMicrotask(() => {
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
- });
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
+ }
40
39
  });
41
40
  }
42
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
+ }