@kawaijs/renderer-dom 0.1.11 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -12
- package/dist/asset-fallbacks.d.ts +13 -0
- package/dist/asset-fallbacks.d.ts.map +1 -0
- package/dist/asset-fallbacks.js +41 -0
- package/dist/asset-fallbacks.js.map +1 -0
- package/dist/components/choice-menu.d.ts +12 -0
- package/dist/components/choice-menu.d.ts.map +1 -0
- package/dist/components/choice-menu.js +44 -0
- package/dist/components/choice-menu.js.map +1 -0
- package/dist/components/dialogue-box.d.ts +29 -0
- package/dist/components/dialogue-box.d.ts.map +1 -0
- package/dist/components/dialogue-box.js +155 -0
- package/dist/components/dialogue-box.js.map +1 -0
- package/dist/components/hotspot-layer.d.ts +13 -0
- package/dist/components/hotspot-layer.d.ts.map +1 -0
- package/dist/components/hotspot-layer.js +47 -0
- package/dist/components/hotspot-layer.js.map +1 -0
- package/dist/components/main-menu.d.ts +36 -0
- package/dist/components/main-menu.d.ts.map +1 -0
- package/dist/components/main-menu.js +257 -0
- package/dist/components/main-menu.js.map +1 -0
- package/dist/components/quick-menu.d.ts +22 -0
- package/dist/components/quick-menu.d.ts.map +1 -0
- package/dist/components/quick-menu.js +59 -0
- package/dist/components/quick-menu.js.map +1 -0
- package/dist/components/stage-layer.d.ts +39 -0
- package/dist/components/stage-layer.d.ts.map +1 -0
- package/dist/components/stage-layer.js +253 -0
- package/dist/components/stage-layer.js.map +1 -0
- package/dist/components/vfx-layer.d.ts +28 -0
- package/dist/components/vfx-layer.d.ts.map +1 -0
- package/dist/components/vfx-layer.js +310 -0
- package/dist/components/vfx-layer.js.map +1 -0
- package/dist/embed.d.ts +5 -0
- package/dist/embed.d.ts.map +1 -0
- package/dist/embed.js +17 -0
- package/dist/embed.js.map +1 -0
- package/dist/icons.d.ts +31 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +31 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +31 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +69 -3
- package/dist/index.js.map +1 -1
- package/dist/layout/viewport-adapter.d.ts +52 -0
- package/dist/layout/viewport-adapter.d.ts.map +1 -0
- package/dist/layout/viewport-adapter.js +123 -0
- package/dist/layout/viewport-adapter.js.map +1 -0
- package/dist/modals/about-modal.d.ts +3 -0
- package/dist/modals/about-modal.d.ts.map +1 -0
- package/dist/modals/about-modal.js +59 -0
- package/dist/modals/about-modal.js.map +1 -0
- package/dist/modals/achievements-modal.d.ts +4 -0
- package/dist/modals/achievements-modal.d.ts.map +1 -0
- package/dist/modals/achievements-modal.js +103 -0
- package/dist/modals/achievements-modal.js.map +1 -0
- package/dist/modals/confirm-modal.d.ts +9 -0
- package/dist/modals/confirm-modal.d.ts.map +1 -0
- package/dist/modals/confirm-modal.js +80 -0
- package/dist/modals/confirm-modal.js.map +1 -0
- package/dist/modals/gallery-modal.d.ts +4 -0
- package/dist/modals/gallery-modal.d.ts.map +1 -0
- package/dist/modals/gallery-modal.js +128 -0
- package/dist/modals/gallery-modal.js.map +1 -0
- package/dist/modals/history-modal.d.ts +3 -0
- package/dist/modals/history-modal.d.ts.map +1 -0
- package/dist/modals/history-modal.js +66 -0
- package/dist/modals/history-modal.js.map +1 -0
- package/dist/modals/input-modal.d.ts +10 -0
- package/dist/modals/input-modal.d.ts.map +1 -0
- package/dist/modals/input-modal.js +62 -0
- package/dist/modals/input-modal.js.map +1 -0
- package/dist/modals/save-load-modal.d.ts +3 -0
- package/dist/modals/save-load-modal.d.ts.map +1 -0
- package/dist/modals/save-load-modal.js +262 -0
- package/dist/modals/save-load-modal.js.map +1 -0
- package/dist/modals/settings-modal.d.ts +14 -0
- package/dist/modals/settings-modal.d.ts.map +1 -0
- package/dist/modals/settings-modal.js +128 -0
- package/dist/modals/settings-modal.js.map +1 -0
- package/dist/renderer.d.ts +63 -56
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +643 -734
- package/dist/renderer.js.map +1 -1
- package/dist/share-meta.d.ts +27 -0
- package/dist/share-meta.d.ts.map +1 -0
- package/dist/share-meta.js +113 -0
- package/dist/share-meta.js.map +1 -0
- package/dist/touch.d.ts +16 -0
- package/dist/touch.d.ts.map +1 -0
- package/dist/touch.js +60 -0
- package/dist/touch.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +60 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/focus-trap.d.ts +6 -0
- package/dist/utils/focus-trap.d.ts.map +1 -0
- package/dist/utils/focus-trap.js +52 -0
- package/dist/utils/focus-trap.js.map +1 -0
- package/dist/utils/rich-text.d.ts +7 -0
- package/dist/utils/rich-text.d.ts.map +1 -0
- package/dist/utils/rich-text.js +31 -0
- package/dist/utils/rich-text.js.map +1 -0
- package/package.json +17 -3
- package/src/theme.css +1995 -685
package/README.md
CHANGED
|
@@ -7,33 +7,55 @@ Part of the [Kawaijs](https://github.com/biagio-scaglia/KawaiJS) visual novel en
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @kawaijs/renderer-dom
|
|
10
|
+
npm install @kawaijs/renderer-dom @kawaijs/runtime @kawaijs/parser
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Quick Example
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
16
|
import { compileScript } from '@kawaijs/parser';
|
|
17
|
-
import {
|
|
18
|
-
import '@kawaijs/renderer-dom
|
|
17
|
+
import { StoryVM } from '@kawaijs/runtime';
|
|
18
|
+
import { DOMRenderer } from '@kawaijs/renderer-dom';
|
|
19
|
+
import '@kawaijs/renderer-dom/dist/theme.css';
|
|
19
20
|
|
|
20
21
|
const story = compileScript(`
|
|
21
|
-
character yumia "Yumia"
|
|
22
|
+
character yumia "Yumia" #f43f5e
|
|
22
23
|
|
|
23
24
|
label start:
|
|
24
|
-
scene bg classroom
|
|
25
|
-
|
|
25
|
+
scene bg classroom with fade
|
|
26
|
+
vfx sakura
|
|
27
|
+
show yumia happy at center with bounce
|
|
26
28
|
yumia "Hello from Kawaijs DOM renderer!"
|
|
27
29
|
`);
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
const vm = new StoryVM(story);
|
|
32
|
+
|
|
33
|
+
const renderer = new DOMRenderer(vm, {
|
|
34
|
+
container: document.getElementById('app')!,
|
|
35
|
+
mainMenu: {
|
|
36
|
+
enabled: true,
|
|
37
|
+
title: "My Visual Novel",
|
|
38
|
+
subtitle: "A Web Tale"
|
|
39
|
+
}
|
|
40
|
+
});
|
|
30
41
|
```
|
|
31
42
|
|
|
32
43
|
## Features
|
|
33
|
-
|
|
34
|
-
- **
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
44
|
+
|
|
45
|
+
- **Ren'Py-Style Start / Main Menu**: Start Game, Continue, Load, CG Gallery, Preferences, About, and Quit. Fully customizable through `MainMenuOptions` or standard CSS.
|
|
46
|
+
- **Atmospheric VFX Layer**: Canvas particles for `sakura` (cherry blossoms), `rain`, `snow`, plus `fog` / `tint` overlays (`tint` layers over weather without stopping petals).
|
|
47
|
+
- **Scene & Sprite Transitions**: Background `with fade`; sprite `with bounce` / `dissolve` / `nod` / `shake` / `slideleft` / `slideright`.
|
|
48
|
+
- **Camera Directives & Shakes**: `shake`, `vpunch`, `hpunch`, and screen `flash`.
|
|
49
|
+
- **Unlockable CG Gallery & Lightbox**: Fullscreen illustration modal with progress tracking and lightbox viewer.
|
|
50
|
+
- **Save & Load Modals**: Multi-slot save system with live state previews, slot deletion, and timestamps.
|
|
51
|
+
- **Preferences Modal**: Interactive sliders for Typewriter speed, Auto-forward delay, Music and SFX volume.
|
|
52
|
+
- **Dialogue History / Backlog**: Formatted log of all past speaker interactions (synced on rollback/load).
|
|
53
|
+
- **Responsive 16:9 Letterboxing**: Automatically scales to any screen aspect ratio.
|
|
54
|
+
- **Rich Text & Interpolation**: Built-in support for dynamic variables `[var]`, `{b}`, `{i}`, `{color=...}`, `{size=...}` tags with XSS sanitization.
|
|
55
|
+
- **Keyboard Navigation**: Space/Enter advance, Backspace rollback, A auto, Ctrl skip, S/L save/load, H history, P preferences, Esc title.
|
|
56
|
+
- **Theme / Style hooks**: `theme "noir|sakura|ocean|dusk"` and `style dialogue|choices|stage …` map to CSS tokens / classes.
|
|
57
|
+
- **Share & embed**: dynamic Open Graph meta per scene; `?embed=1` for iframe-friendly chrome; `?at=` deep-links.
|
|
37
58
|
|
|
38
59
|
## License
|
|
39
|
-
|
|
60
|
+
|
|
61
|
+
MIT License © 2026 [Biagio Scaglia](https://github.com/biagio-scaglia)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AssetType } from './types.js';
|
|
2
|
+
/** Raster + vector extensions tried when a character path resolves to `.svg` by default. */
|
|
3
|
+
export declare const IMAGE_EXTENSIONS: readonly [".png", ".jpg", ".jpeg", ".webp", ".svg"];
|
|
4
|
+
/**
|
|
5
|
+
* Build candidate URLs for a character asset key (`yumia` or `yumia/happy`).
|
|
6
|
+
* Prefer raster (png/jpg/webp) before the default `.svg` so real art wins.
|
|
7
|
+
*/
|
|
8
|
+
export declare function characterAssetCandidates(pathKey: string, resolve: (path: string, type: AssetType) => string): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Attach sequential src fallbacks on an image (png/jpg/webp before giving up).
|
|
11
|
+
*/
|
|
12
|
+
export declare function bindImageSrcFallbacks(img: HTMLImageElement, candidates: string[], onAllFailed?: () => void): void;
|
|
13
|
+
//# sourceMappingURL=asset-fallbacks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-fallbacks.d.ts","sourceRoot":"","sources":["../src/asset-fallbacks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,qDAAsD,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,KAAK,MAAM,GACjD,MAAM,EAAE,CAoBV;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,WAAW,CAAC,EAAE,MAAM,IAAI,GACvB,IAAI,CAeN"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Raster + vector extensions tried when a character path resolves to `.svg` by default. */
|
|
2
|
+
export const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.svg'];
|
|
3
|
+
/**
|
|
4
|
+
* Build candidate URLs for a character asset key (`yumia` or `yumia/happy`).
|
|
5
|
+
* Prefer raster (png/jpg/webp) before the default `.svg` so real art wins.
|
|
6
|
+
*/
|
|
7
|
+
export function characterAssetCandidates(pathKey, resolve) {
|
|
8
|
+
const primary = resolve(pathKey, 'character');
|
|
9
|
+
const extMatch = primary.match(/^(.*)\.(svg|png|jpe?g|webp|gif)$/i);
|
|
10
|
+
if (!extMatch) {
|
|
11
|
+
return IMAGE_EXTENSIONS.map((ext) => `${primary}${ext}`);
|
|
12
|
+
}
|
|
13
|
+
const base = extMatch[1];
|
|
14
|
+
const primaryExt = `.${extMatch[2].toLowerCase()}`;
|
|
15
|
+
const normalizedPrimary = primaryExt === '.jpeg' ? '.jpg' : primaryExt === '.jpg' ? '.jpg' : primaryExt;
|
|
16
|
+
// Explicit non-svg path → that file first, then siblings
|
|
17
|
+
if (normalizedPrimary !== '.svg') {
|
|
18
|
+
const rest = IMAGE_EXTENSIONS.filter((ext) => ext !== normalizedPrimary && !(normalizedPrimary === '.jpg' && ext === '.jpeg'));
|
|
19
|
+
return [primary, ...rest.map((ext) => `${base}${ext}`)];
|
|
20
|
+
}
|
|
21
|
+
// Default resolver appended .svg → try real bitmaps first
|
|
22
|
+
return IMAGE_EXTENSIONS.map((ext) => `${base}${ext}`);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Attach sequential src fallbacks on an image (png/jpg/webp before giving up).
|
|
26
|
+
*/
|
|
27
|
+
export function bindImageSrcFallbacks(img, candidates, onAllFailed) {
|
|
28
|
+
let index = 0;
|
|
29
|
+
const tryNext = () => {
|
|
30
|
+
if (index >= candidates.length) {
|
|
31
|
+
onAllFailed?.();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
img.src = candidates[index++];
|
|
35
|
+
};
|
|
36
|
+
img.onerror = () => {
|
|
37
|
+
tryNext();
|
|
38
|
+
};
|
|
39
|
+
tryNext();
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=asset-fallbacks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asset-fallbacks.js","sourceRoot":"","sources":["../src/asset-fallbacks.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEpF;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAe,EACf,OAAkD;IAElD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IACpD,MAAM,iBAAiB,GACrB,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;IAEhF,yDAAyD;IACzD,IAAI,iBAAiB,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,iBAAiB,IAAI,CAAC,CAAC,iBAAiB,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC;QAC/H,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,0DAA0D;IAC1D,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAqB,EACrB,UAAoB,EACpB,WAAwB;IAExB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YAC/B,WAAW,EAAE,EAAE,CAAC;YAChB,OAAO;QACT,CAAC;QACD,GAAG,CAAC,GAAG,GAAG,UAAU,CAAC,KAAK,EAAE,CAAE,CAAC;IACjC,CAAC,CAAC;IAEF,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE;QACjB,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChoiceOption } from '@kawaijs/ast';
|
|
2
|
+
export interface ChoiceMenuCallbacks {
|
|
3
|
+
onSelect: (index: number) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare class ChoiceMenuComponent {
|
|
6
|
+
readonly el: HTMLDivElement;
|
|
7
|
+
private readonly callbacks;
|
|
8
|
+
constructor(callbacks: ChoiceMenuCallbacks);
|
|
9
|
+
render(choices: readonly ChoiceOption[] | null | undefined): void;
|
|
10
|
+
hide(): void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=choice-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choice-menu.d.ts","sourceRoot":"","sources":["../../src/components/choice-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,qBAAa,mBAAmB;IAC9B,SAAgB,EAAE,EAAE,cAAc,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;gBAEpC,SAAS,EAAE,mBAAmB;IASnC,MAAM,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAkCjE,IAAI,IAAI,IAAI;CAIpB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export class ChoiceMenuComponent {
|
|
2
|
+
el;
|
|
3
|
+
callbacks;
|
|
4
|
+
constructor(callbacks) {
|
|
5
|
+
this.callbacks = callbacks;
|
|
6
|
+
this.el = document.createElement('div');
|
|
7
|
+
this.el.className = 'kawa-choice-container kawa-choices';
|
|
8
|
+
this.el.setAttribute('role', 'group');
|
|
9
|
+
this.el.setAttribute('aria-label', 'Choices');
|
|
10
|
+
this.el.style.display = 'none';
|
|
11
|
+
}
|
|
12
|
+
render(choices) {
|
|
13
|
+
this.el.innerHTML = '';
|
|
14
|
+
if (!choices || choices.length === 0) {
|
|
15
|
+
this.el.style.display = 'none';
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
this.el.style.display = 'flex';
|
|
19
|
+
choices.forEach((choice, index) => {
|
|
20
|
+
const btn = document.createElement('button');
|
|
21
|
+
btn.type = 'button';
|
|
22
|
+
btn.className = 'kawa-choice-btn';
|
|
23
|
+
btn.textContent = choice.text;
|
|
24
|
+
btn.setAttribute('tabindex', '0');
|
|
25
|
+
btn.setAttribute('aria-label', `Option ${index + 1}: ${choice.text}`);
|
|
26
|
+
btn.addEventListener('click', (e) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
e.stopPropagation();
|
|
29
|
+
this.callbacks.onSelect(index);
|
|
30
|
+
});
|
|
31
|
+
this.el.appendChild(btn);
|
|
32
|
+
});
|
|
33
|
+
// Auto-focus first choice for keyboard navigation
|
|
34
|
+
const firstBtn = this.el.querySelector('button');
|
|
35
|
+
if (firstBtn) {
|
|
36
|
+
firstBtn.focus();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
hide() {
|
|
40
|
+
this.el.style.display = 'none';
|
|
41
|
+
this.el.innerHTML = '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=choice-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"choice-menu.js","sourceRoot":"","sources":["../../src/components/choice-menu.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,mBAAmB;IACd,EAAE,CAAiB;IAClB,SAAS,CAAsB;IAEhD,YAAY,SAA8B;QACxC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,oCAAoC,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IACjC,CAAC;IAEM,MAAM,CAAC,OAAmD;QAC/D,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC;QAEvB,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAE/B,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC7C,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;YACpB,GAAG,CAAC,SAAS,GAAG,iBAAiB,CAAC;YAClC,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;YAC9B,GAAG,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAClC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,KAAK,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAEtE,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBAClC,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,CAAC,CAAC,eAAe,EAAE,CAAC;gBACpB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,kDAAkD;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAEM,IAAI;QACT,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface DialogueBoxCallbacks {
|
|
2
|
+
onAdvance: () => void;
|
|
3
|
+
}
|
|
4
|
+
export declare class DialogueBoxComponent {
|
|
5
|
+
readonly el: HTMLDivElement;
|
|
6
|
+
private readonly speakerTagEl;
|
|
7
|
+
private readonly dialogueTextEl;
|
|
8
|
+
private readonly announceEl;
|
|
9
|
+
private readonly indicatorEl;
|
|
10
|
+
private typewriterSpeed;
|
|
11
|
+
private typewriterInterval;
|
|
12
|
+
private isTypewriting;
|
|
13
|
+
private fullCurrentText;
|
|
14
|
+
private onTypewriterComplete?;
|
|
15
|
+
constructor(typewriterSpeed?: number);
|
|
16
|
+
setTypewriterSpeed(speed: number): void;
|
|
17
|
+
getIsTypewriting(): boolean;
|
|
18
|
+
render(dialogue: {
|
|
19
|
+
speaker?: string;
|
|
20
|
+
speakerDisplayName?: string;
|
|
21
|
+
speakerColor?: string;
|
|
22
|
+
text: string;
|
|
23
|
+
} | null | undefined, onComplete?: () => void): void;
|
|
24
|
+
finishTypewriter(): void;
|
|
25
|
+
private announceFinishedLine;
|
|
26
|
+
private startTypewriter;
|
|
27
|
+
destroy(): void;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=dialogue-box.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogue-box.d.ts","sourceRoot":"","sources":["../../src/components/dialogue-box.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,qBAAa,oBAAoB;IAC/B,SAAgB,EAAE,EAAE,cAAc,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiB;IAE7C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAa;gBAE9B,eAAe,SAAK;IA2CzB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIvC,gBAAgB,IAAI,OAAO;IAI3B,MAAM,CACX,QAAQ,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,EACnH,UAAU,CAAC,EAAE,MAAM,IAAI,GACtB,IAAI;IA8CA,gBAAgB,IAAI,IAAI;IAiB/B,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,eAAe;IA8BhB,OAAO,IAAI,IAAI;CAMvB"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { SVG_ICONS } from '../icons.js';
|
|
2
|
+
import { formatRichText } from '../utils/rich-text.js';
|
|
3
|
+
export class DialogueBoxComponent {
|
|
4
|
+
el;
|
|
5
|
+
speakerTagEl;
|
|
6
|
+
dialogueTextEl;
|
|
7
|
+
announceEl;
|
|
8
|
+
indicatorEl;
|
|
9
|
+
typewriterSpeed;
|
|
10
|
+
typewriterInterval = null;
|
|
11
|
+
isTypewriting = false;
|
|
12
|
+
fullCurrentText = '';
|
|
13
|
+
onTypewriterComplete;
|
|
14
|
+
constructor(typewriterSpeed = 20) {
|
|
15
|
+
this.typewriterSpeed = typewriterSpeed;
|
|
16
|
+
this.el = document.createElement('div');
|
|
17
|
+
this.el.className = 'kawa-dialogue-box kawa-dialogue';
|
|
18
|
+
this.el.setAttribute('role', 'region');
|
|
19
|
+
this.el.setAttribute('aria-label', 'Dialogue');
|
|
20
|
+
this.el.setAttribute('tabindex', '0');
|
|
21
|
+
this.speakerTagEl = document.createElement('div');
|
|
22
|
+
this.speakerTagEl.className = 'kawa-speaker-tag kawa-speaker';
|
|
23
|
+
this.dialogueTextEl = document.createElement('div');
|
|
24
|
+
this.dialogueTextEl.className = 'kawa-dialogue-text kawa-text';
|
|
25
|
+
// Visible typewriter must NOT be live — per-character updates spam screen readers.
|
|
26
|
+
this.dialogueTextEl.setAttribute('aria-hidden', 'true');
|
|
27
|
+
this.announceEl = document.createElement('div');
|
|
28
|
+
this.announceEl.className = 'kawa-dialogue-announce';
|
|
29
|
+
this.announceEl.setAttribute('aria-live', 'polite');
|
|
30
|
+
this.announceEl.setAttribute('aria-atomic', 'true');
|
|
31
|
+
// Visually hidden but available to AT
|
|
32
|
+
this.announceEl.style.position = 'absolute';
|
|
33
|
+
this.announceEl.style.width = '1px';
|
|
34
|
+
this.announceEl.style.height = '1px';
|
|
35
|
+
this.announceEl.style.padding = '0';
|
|
36
|
+
this.announceEl.style.margin = '-1px';
|
|
37
|
+
this.announceEl.style.overflow = 'hidden';
|
|
38
|
+
this.announceEl.style.clip = 'rect(0, 0, 0, 0)';
|
|
39
|
+
this.announceEl.style.whiteSpace = 'nowrap';
|
|
40
|
+
this.announceEl.style.border = '0';
|
|
41
|
+
this.indicatorEl = document.createElement('div');
|
|
42
|
+
this.indicatorEl.className = 'kawa-continue-indicator';
|
|
43
|
+
this.indicatorEl.setAttribute('aria-hidden', 'true');
|
|
44
|
+
this.indicatorEl.innerHTML = SVG_ICONS.arrowDown;
|
|
45
|
+
this.el.appendChild(this.speakerTagEl);
|
|
46
|
+
this.el.appendChild(this.dialogueTextEl);
|
|
47
|
+
this.el.appendChild(this.announceEl);
|
|
48
|
+
this.el.appendChild(this.indicatorEl);
|
|
49
|
+
}
|
|
50
|
+
setTypewriterSpeed(speed) {
|
|
51
|
+
this.typewriterSpeed = speed;
|
|
52
|
+
}
|
|
53
|
+
getIsTypewriting() {
|
|
54
|
+
return this.isTypewriting;
|
|
55
|
+
}
|
|
56
|
+
render(dialogue, onComplete) {
|
|
57
|
+
if (!dialogue) {
|
|
58
|
+
this.el.style.display = 'none';
|
|
59
|
+
this.announceEl.textContent = '';
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
this.el.style.display = 'block';
|
|
63
|
+
// Speaker Tag
|
|
64
|
+
if (dialogue.speakerDisplayName || dialogue.speaker) {
|
|
65
|
+
this.speakerTagEl.textContent = dialogue.speakerDisplayName ?? dialogue.speaker ?? '';
|
|
66
|
+
this.speakerTagEl.style.display = 'inline-block';
|
|
67
|
+
if (dialogue.speakerColor) {
|
|
68
|
+
this.speakerTagEl.style.backgroundColor = dialogue.speakerColor;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.speakerTagEl.style.backgroundColor = '';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
this.speakerTagEl.style.display = 'none';
|
|
76
|
+
}
|
|
77
|
+
// Dialogue Text
|
|
78
|
+
this.onTypewriterComplete = onComplete;
|
|
79
|
+
this.fullCurrentText = dialogue.text;
|
|
80
|
+
if (this.typewriterInterval) {
|
|
81
|
+
clearInterval(this.typewriterInterval);
|
|
82
|
+
this.typewriterInterval = null;
|
|
83
|
+
}
|
|
84
|
+
// Clear previous announcement while typing so AT only hears the finished line.
|
|
85
|
+
this.announceEl.textContent = '';
|
|
86
|
+
this.el.setAttribute('aria-busy', 'true');
|
|
87
|
+
if (this.typewriterSpeed <= 0) {
|
|
88
|
+
this.dialogueTextEl.innerHTML = formatRichText(this.fullCurrentText);
|
|
89
|
+
this.isTypewriting = false;
|
|
90
|
+
this.indicatorEl.style.opacity = '1';
|
|
91
|
+
this.announceFinishedLine(dialogue);
|
|
92
|
+
this.onTypewriterComplete?.();
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.startTypewriter(this.fullCurrentText, dialogue);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
finishTypewriter() {
|
|
99
|
+
if (!this.isTypewriting)
|
|
100
|
+
return;
|
|
101
|
+
if (this.typewriterInterval) {
|
|
102
|
+
clearInterval(this.typewriterInterval);
|
|
103
|
+
this.typewriterInterval = null;
|
|
104
|
+
}
|
|
105
|
+
this.isTypewriting = false;
|
|
106
|
+
this.dialogueTextEl.innerHTML = formatRichText(this.fullCurrentText);
|
|
107
|
+
this.indicatorEl.style.opacity = '1';
|
|
108
|
+
const speaker = this.speakerTagEl.style.display === 'none' ? undefined : this.speakerTagEl.textContent || undefined;
|
|
109
|
+
this.announceFinishedLine({
|
|
110
|
+
speakerDisplayName: speaker,
|
|
111
|
+
text: this.fullCurrentText
|
|
112
|
+
});
|
|
113
|
+
this.onTypewriterComplete?.();
|
|
114
|
+
}
|
|
115
|
+
announceFinishedLine(dialogue) {
|
|
116
|
+
this.el.setAttribute('aria-busy', 'false');
|
|
117
|
+
const who = dialogue.speakerDisplayName || dialogue.speaker;
|
|
118
|
+
const plain = (() => {
|
|
119
|
+
const temp = document.createElement('div');
|
|
120
|
+
temp.innerHTML = formatRichText(dialogue.text);
|
|
121
|
+
return temp.textContent || dialogue.text;
|
|
122
|
+
})();
|
|
123
|
+
this.announceEl.textContent = who ? `${who}: ${plain}` : plain;
|
|
124
|
+
}
|
|
125
|
+
startTypewriter(text, dialogue) {
|
|
126
|
+
this.isTypewriting = true;
|
|
127
|
+
this.indicatorEl.style.opacity = '0';
|
|
128
|
+
this.dialogueTextEl.innerHTML = '';
|
|
129
|
+
const formattedFull = formatRichText(text);
|
|
130
|
+
// Extract text chunks or plain characters
|
|
131
|
+
const temp = document.createElement('div');
|
|
132
|
+
temp.innerHTML = formattedFull;
|
|
133
|
+
const plainText = temp.textContent || '';
|
|
134
|
+
const graphemes = Array.from(plainText);
|
|
135
|
+
let charIdx = 0;
|
|
136
|
+
this.typewriterInterval = window.setInterval(() => {
|
|
137
|
+
charIdx++;
|
|
138
|
+
if (charIdx >= graphemes.length) {
|
|
139
|
+
this.finishTypewriter();
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this.dialogueTextEl.textContent = graphemes.slice(0, charIdx).join('');
|
|
143
|
+
}
|
|
144
|
+
}, this.typewriterSpeed);
|
|
145
|
+
// Keep dialogue ref for finish path via fullCurrentText / speakerTag
|
|
146
|
+
void dialogue;
|
|
147
|
+
}
|
|
148
|
+
destroy() {
|
|
149
|
+
if (this.typewriterInterval) {
|
|
150
|
+
clearInterval(this.typewriterInterval);
|
|
151
|
+
this.typewriterInterval = null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=dialogue-box.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialogue-box.js","sourceRoot":"","sources":["../../src/components/dialogue-box.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HotspotOption } from '@kawaijs/ast';
|
|
2
|
+
/**
|
|
3
|
+
* Absolute-positioned clickable regions over the stage / CG.
|
|
4
|
+
* Coordinates are normalized 0–1 relative to the stage.
|
|
5
|
+
*/
|
|
6
|
+
export declare class HotspotLayerComponent {
|
|
7
|
+
private readonly onSelect;
|
|
8
|
+
readonly el: HTMLDivElement;
|
|
9
|
+
constructor(onSelect: (id: string) => void);
|
|
10
|
+
render(hotspots: readonly HotspotOption[] | null | undefined): void;
|
|
11
|
+
destroy(): void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=hotspot-layer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotspot-layer.d.ts","sourceRoot":"","sources":["../../src/components/hotspot-layer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;GAGG;AACH,qBAAa,qBAAqB;IAGpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,SAAgB,EAAE,EAAE,cAAc,CAAC;gBAEN,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI;IAOpD,MAAM,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IA+BnE,OAAO,IAAI,IAAI;CAIvB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Absolute-positioned clickable regions over the stage / CG.
|
|
3
|
+
* Coordinates are normalized 0–1 relative to the stage.
|
|
4
|
+
*/
|
|
5
|
+
export class HotspotLayerComponent {
|
|
6
|
+
onSelect;
|
|
7
|
+
el;
|
|
8
|
+
constructor(onSelect) {
|
|
9
|
+
this.onSelect = onSelect;
|
|
10
|
+
this.el = document.createElement('div');
|
|
11
|
+
this.el.className = 'kawa-hotspot-layer';
|
|
12
|
+
this.el.setAttribute('aria-label', 'Interactive regions');
|
|
13
|
+
this.el.style.display = 'none';
|
|
14
|
+
}
|
|
15
|
+
render(hotspots) {
|
|
16
|
+
this.el.replaceChildren();
|
|
17
|
+
if (!hotspots || hotspots.length === 0) {
|
|
18
|
+
this.el.style.display = 'none';
|
|
19
|
+
this.el.setAttribute('aria-hidden', 'true');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.el.style.display = 'block';
|
|
23
|
+
this.el.setAttribute('aria-hidden', 'false');
|
|
24
|
+
for (const spot of hotspots) {
|
|
25
|
+
const btn = document.createElement('button');
|
|
26
|
+
btn.type = 'button';
|
|
27
|
+
btn.className = 'kawa-hotspot';
|
|
28
|
+
btn.dataset.hotspotId = spot.id;
|
|
29
|
+
btn.setAttribute('aria-label', spot.id);
|
|
30
|
+
btn.title = spot.id;
|
|
31
|
+
btn.style.left = `${spot.x * 100}%`;
|
|
32
|
+
btn.style.top = `${spot.y * 100}%`;
|
|
33
|
+
btn.style.width = `${spot.w * 100}%`;
|
|
34
|
+
btn.style.height = `${spot.h * 100}%`;
|
|
35
|
+
btn.addEventListener('click', (e) => {
|
|
36
|
+
e.stopPropagation();
|
|
37
|
+
this.onSelect(spot.id);
|
|
38
|
+
});
|
|
39
|
+
this.el.appendChild(btn);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
destroy() {
|
|
43
|
+
this.el.replaceChildren();
|
|
44
|
+
this.el.remove();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=hotspot-layer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotspot-layer.js","sourceRoot":"","sources":["../../src/components/hotspot-layer.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IAGH;IAFb,EAAE,CAAiB;IAEnC,YAA6B,QAA8B;QAA9B,aAAQ,GAAR,QAAQ,CAAsB;QACzD,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,oBAAoB,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;QAC1D,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IACjC,CAAC;IAEM,MAAM,CAAC,QAAqD;QACjE,IAAI,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC;QAE1B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;YAC/B,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAE7C,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC7C,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;YACpB,GAAG,CAAC,SAAS,GAAG,cAAc,CAAC;YAC/B,GAAG,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACxC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;YACpB,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACpC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACnC,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACrC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACtC,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBAClC,CAAC,CAAC,eAAe,EAAE,CAAC;gBACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { StoryVM } from '@kawaijs/runtime';
|
|
2
|
+
import type { MainMenuItem, MainMenuOptions, AssetType } from '../types.js';
|
|
3
|
+
export interface MainMenuComponentCallbacks {
|
|
4
|
+
onStartNewGame: () => void;
|
|
5
|
+
onContinueGame: () => Promise<boolean>;
|
|
6
|
+
assetResolver: (path: string, type: AssetType) => string;
|
|
7
|
+
getSettingsConfig: () => {
|
|
8
|
+
typewriterSpeed: number;
|
|
9
|
+
autoDelayMs: number;
|
|
10
|
+
musicVolume?: number;
|
|
11
|
+
soundVolume?: number;
|
|
12
|
+
onSettingsChange: (s: {
|
|
13
|
+
typewriterSpeed: number;
|
|
14
|
+
autoDelayMs: number;
|
|
15
|
+
musicVolume?: number;
|
|
16
|
+
soundVolume?: number;
|
|
17
|
+
}) => void;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare class MainMenuComponent {
|
|
21
|
+
private readonly vm;
|
|
22
|
+
private readonly rootEl;
|
|
23
|
+
private readonly options?;
|
|
24
|
+
private readonly callbacks;
|
|
25
|
+
private menuEl?;
|
|
26
|
+
private isVisible;
|
|
27
|
+
constructor(rootEl: HTMLElement, vm: StoryVM, options: MainMenuOptions | undefined, callbacks: MainMenuComponentCallbacks);
|
|
28
|
+
mount(container: HTMLElement): void;
|
|
29
|
+
show(): void;
|
|
30
|
+
hide(): void;
|
|
31
|
+
isActive(): boolean;
|
|
32
|
+
refresh(): void;
|
|
33
|
+
getDefaultMenuItems(): MainMenuItem[];
|
|
34
|
+
private buildDOM;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=main-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-menu.d.ts","sourceRoot":"","sources":["../../src/components/main-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAS5E,MAAM,WAAW,0BAA0B;IACzC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IACzD,iBAAiB,EAAE,MAAM;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,CAAC,CAAC,EAAE;YACpB,eAAe,EAAE,MAAM,CAAC;YACxB,WAAW,EAAE,MAAM,CAAC;YACpB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,WAAW,CAAC,EAAE,MAAM,CAAC;SACtB,KAAK,IAAI,CAAC;KACZ,CAAC;CACH;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAU;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IAEvD,OAAO,CAAC,MAAM,CAAC,CAAiB;IAChC,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,SAAS,EAAE,SAAS,EAAE,0BAA0B;IAOlH,KAAK,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IASnC,IAAI,IAAI,IAAI;IAcZ,IAAI,IAAI,IAAI;IAQZ,QAAQ,IAAI,OAAO;IAInB,OAAO,IAAI,IAAI;IAoBf,mBAAmB,IAAI,YAAY,EAAE;IA8D5C,OAAO,CAAC,QAAQ;CAyIjB"}
|