@texuli/deck 0.0.0-stage → 0.5.0

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 (139) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +183 -2
  3. package/dist/authoring/builder.d.ts +351 -0
  4. package/dist/authoring/builder.js +64 -0
  5. package/dist/authoring/index.d.ts +6 -0
  6. package/dist/authoring/index.js +6 -0
  7. package/dist/authoring/lint.d.ts +36 -0
  8. package/dist/authoring/lint.js +325 -0
  9. package/dist/authoring/outlines.d.ts +34 -0
  10. package/dist/authoring/outlines.js +255 -0
  11. package/dist/authoring/patch.d.ts +23 -0
  12. package/dist/authoring/patch.js +166 -0
  13. package/dist/authoring/select-layouts.d.ts +19 -0
  14. package/dist/authoring/select-layouts.js +90 -0
  15. package/dist/engine/auto-slide.d.ts +32 -0
  16. package/dist/engine/auto-slide.js +67 -0
  17. package/dist/engine/canvas.d.ts +56 -0
  18. package/dist/engine/canvas.js +48 -0
  19. package/dist/engine/controller.d.ts +126 -0
  20. package/dist/engine/controller.js +248 -0
  21. package/dist/engine/controls.d.ts +128 -0
  22. package/dist/engine/controls.js +460 -0
  23. package/dist/engine/hash.d.ts +42 -0
  24. package/dist/engine/hash.js +86 -0
  25. package/dist/engine/index.d.ts +88 -0
  26. package/dist/engine/index.js +88 -0
  27. package/dist/engine/messages.d.ts +138 -0
  28. package/dist/engine/messages.js +140 -0
  29. package/dist/engine/motion.d.ts +64 -0
  30. package/dist/engine/motion.js +112 -0
  31. package/dist/engine/navigation.d.ts +87 -0
  32. package/dist/engine/navigation.js +156 -0
  33. package/dist/engine/presenter.d.ts +58 -0
  34. package/dist/engine/presenter.js +157 -0
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.js +9 -0
  37. package/dist/markdown/attributes.d.ts +24 -0
  38. package/dist/markdown/attributes.js +94 -0
  39. package/dist/markdown/attrs-closed.d.ts +52 -0
  40. package/dist/markdown/attrs-closed.js +520 -0
  41. package/dist/markdown/blocks.d.ts +112 -0
  42. package/dist/markdown/blocks.js +766 -0
  43. package/dist/markdown/frontmatter.d.ts +30 -0
  44. package/dist/markdown/frontmatter.js +165 -0
  45. package/dist/markdown/index.d.ts +29 -0
  46. package/dist/markdown/index.js +29 -0
  47. package/dist/markdown/issues.d.ts +68 -0
  48. package/dist/markdown/issues.js +83 -0
  49. package/dist/markdown/json.d.ts +21 -0
  50. package/dist/markdown/json.js +236 -0
  51. package/dist/markdown/limits.d.ts +32 -0
  52. package/dist/markdown/limits.js +32 -0
  53. package/dist/markdown/parse.d.ts +31 -0
  54. package/dist/markdown/parse.js +283 -0
  55. package/dist/markdown/scan.d.ts +26 -0
  56. package/dist/markdown/scan.js +138 -0
  57. package/dist/markdown/slides.d.ts +32 -0
  58. package/dist/markdown/slides.js +854 -0
  59. package/dist/markdown/text.d.ts +76 -0
  60. package/dist/markdown/text.js +447 -0
  61. package/dist/markdown/twin.d.ts +41 -0
  62. package/dist/markdown/twin.js +374 -0
  63. package/dist/markdown/write.d.ts +17 -0
  64. package/dist/markdown/write.js +688 -0
  65. package/dist/math/index.d.ts +14 -0
  66. package/dist/math/index.js +14 -0
  67. package/dist/math/parse.d.ts +9 -0
  68. package/dist/math/parse.js +648 -0
  69. package/dist/math/symbols.d.ts +44 -0
  70. package/dist/math/symbols.js +333 -0
  71. package/dist/math/types.d.ts +59 -0
  72. package/dist/math/types.js +69 -0
  73. package/dist/model/backgrounds/contrast.d.ts +77 -0
  74. package/dist/model/backgrounds/contrast.js +186 -0
  75. package/dist/model/backgrounds/index.d.ts +7 -0
  76. package/dist/model/backgrounds/index.js +7 -0
  77. package/dist/model/backgrounds/resolve.d.ts +86 -0
  78. package/dist/model/backgrounds/resolve.js +215 -0
  79. package/dist/model/backgrounds/schema.d.ts +178 -0
  80. package/dist/model/backgrounds/schema.js +203 -0
  81. package/dist/model/codes.d.ts +73 -0
  82. package/dist/model/codes.js +67 -0
  83. package/dist/model/deck.d.ts +392 -0
  84. package/dist/model/deck.js +338 -0
  85. package/dist/model/index.d.ts +19 -0
  86. package/dist/model/index.js +19 -0
  87. package/dist/model/layouts/catalog.d.ts +433 -0
  88. package/dist/model/layouts/catalog.js +495 -0
  89. package/dist/model/layouts/index.d.ts +5 -0
  90. package/dist/model/layouts/index.js +5 -0
  91. package/dist/model/layouts/props.d.ts +302 -0
  92. package/dist/model/layouts/props.js +272 -0
  93. package/dist/model/loader.d.ts +37 -0
  94. package/dist/model/loader.js +317 -0
  95. package/dist/model/migrate.d.ts +34 -0
  96. package/dist/model/migrate.js +453 -0
  97. package/dist/model/motion/index.d.ts +6 -0
  98. package/dist/model/motion/index.js +6 -0
  99. package/dist/model/motion/transition.d.ts +96 -0
  100. package/dist/model/motion/transition.js +127 -0
  101. package/dist/model/parse.d.ts +57 -0
  102. package/dist/model/parse.js +307 -0
  103. package/dist/model/plain.d.ts +40 -0
  104. package/dist/model/plain.js +275 -0
  105. package/dist/model/shared.d.ts +63 -0
  106. package/dist/model/shared.js +86 -0
  107. package/dist/model/steps/index.d.ts +7 -0
  108. package/dist/model/steps/index.js +7 -0
  109. package/dist/model/steps/phase.d.ts +34 -0
  110. package/dist/model/steps/phase.js +50 -0
  111. package/dist/model/steps/slide-steps.d.ts +129 -0
  112. package/dist/model/steps/slide-steps.js +256 -0
  113. package/dist/model/style/index.d.ts +8 -0
  114. package/dist/model/style/index.js +8 -0
  115. package/dist/model/style/resolve.d.ts +87 -0
  116. package/dist/model/style/resolve.js +174 -0
  117. package/dist/model/style/schema.d.ts +85 -0
  118. package/dist/model/style/schema.js +53 -0
  119. package/dist/model/style/theme.d.ts +69 -0
  120. package/dist/model/style/theme.js +84 -0
  121. package/dist/schema/builders.d.ts +146 -0
  122. package/dist/schema/builders.js +285 -0
  123. package/dist/schema/diagnostics.d.ts +78 -0
  124. package/dist/schema/diagnostics.js +81 -0
  125. package/dist/schema/index.d.ts +15 -0
  126. package/dist/schema/index.js +14 -0
  127. package/dist/schema/internal.d.ts +40 -0
  128. package/dist/schema/internal.js +209 -0
  129. package/dist/schema/json-schema.d.ts +12 -0
  130. package/dist/schema/json-schema.js +213 -0
  131. package/dist/schema/types.d.ts +166 -0
  132. package/dist/schema/types.js +1 -0
  133. package/dist/schema/validate.d.ts +51 -0
  134. package/dist/schema/validate.js +765 -0
  135. package/dist/templates/index.d.ts +2 -0
  136. package/dist/templates/index.js +2 -0
  137. package/dist/templates/templates.d.ts +11 -0
  138. package/dist/templates/templates.js +47 -0
  139. package/package.json +60 -4
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Imperative framework-free controller with typed events and ordered plugin hooks.
3
+ *
4
+ * `createDeckController(deck, options)` owns navigation, overview/blackout/pause
5
+ * flags, optional auto-slide, and routes `handleInput` through `decideDeckInput`.
6
+ * Stage chrome (help, laser, fullscreen, presenter window) stays with the host:
7
+ * the controller emits the action and leaves rendering to the stage.
8
+ */
9
+ import { createAutoSlideScheduler } from "./auto-slide.js";
10
+ import { decideDeckInput, } from "./controls.js";
11
+ import { createDeckNavigator, } from "./navigation.js";
12
+ function sameLocation(a, b) {
13
+ return (a.slideId === b.slideId &&
14
+ a.slideIndex === b.slideIndex &&
15
+ a.step === b.step &&
16
+ a.overview === b.overview &&
17
+ a.blackout === b.blackout &&
18
+ a.paused === b.paused);
19
+ }
20
+ export function createDeckController(deck, options = {}) {
21
+ const navigator = createDeckNavigator(deck, options);
22
+ let overview = false;
23
+ let blackout = false;
24
+ let paused = false;
25
+ let destroyed = false;
26
+ const listeners = new Map();
27
+ const stateListeners = new Set();
28
+ const cleanups = [];
29
+ const plugins = options.plugins ?? [];
30
+ const snapshot = () => ({
31
+ ...navigator.getLocation(),
32
+ overview,
33
+ blackout,
34
+ paused,
35
+ });
36
+ const emit = (event, detail) => {
37
+ for (const listener of listeners.get(event) ?? []) {
38
+ listener(detail);
39
+ }
40
+ };
41
+ const announceChange = (previous, source) => {
42
+ const state = snapshot();
43
+ if (sameLocation(state, previous))
44
+ return state;
45
+ const change = { state, previous, source };
46
+ emit('statechange', change);
47
+ for (const listener of stateListeners)
48
+ listener(state, previous);
49
+ if (state.slideId !== previous.slideId || state.slideIndex !== previous.slideIndex) {
50
+ emit('slidechange', change);
51
+ }
52
+ else if (state.step !== previous.step) {
53
+ emit('stepchange', change);
54
+ }
55
+ for (const plugin of plugins)
56
+ plugin.afterNavigate?.(state, previous);
57
+ return state;
58
+ };
59
+ const controller = {};
60
+ let navigate;
61
+ const autoMs = options.autoSlideMs ?? deck.navigation?.autoSlideMs ?? 0;
62
+ const scheduler = autoMs > 0
63
+ ? createAutoSlideScheduler({
64
+ intervalMs: autoMs,
65
+ clock: options.clock,
66
+ startPaused: options.autoSlidePaused === true || paused,
67
+ onAdvance: () => {
68
+ if (!paused && !destroyed)
69
+ navigate(() => navigator.next(), 'auto');
70
+ },
71
+ })
72
+ : undefined;
73
+ navigate = (move, source = 'api', target) => {
74
+ if (destroyed)
75
+ return snapshot();
76
+ const previous = snapshot();
77
+ let action = move;
78
+ for (const plugin of plugins) {
79
+ const decision = plugin.beforeNavigate?.(target ?? previous, previous);
80
+ if (decision === false)
81
+ return previous;
82
+ if (decision && typeof decision === 'object') {
83
+ const redirected = decision;
84
+ action = () => navigator.goTo(redirected);
85
+ }
86
+ }
87
+ action();
88
+ return announceChange(previous, source);
89
+ };
90
+ const fireAction = (name, source) => {
91
+ for (const plugin of plugins)
92
+ plugin.onAction?.(name, source);
93
+ emit('action', { action: name, source });
94
+ };
95
+ const applyAction = (name, source) => {
96
+ fireAction(name, source);
97
+ switch (name) {
98
+ case 'next':
99
+ navigate(() => navigator.next(), source);
100
+ break;
101
+ case 'prev':
102
+ navigate(() => navigator.prev(), source);
103
+ break;
104
+ case 'first':
105
+ navigate(() => navigator.first(), source);
106
+ break;
107
+ case 'last':
108
+ navigate(() => navigator.last(), source);
109
+ break;
110
+ case 'overview':
111
+ controller.setOverview(!overview);
112
+ break;
113
+ case 'blackout':
114
+ controller.setBlackout(!blackout);
115
+ break;
116
+ default:
117
+ // Stage-owned chrome: help, laser, fullscreen, presenter, print, …
118
+ break;
119
+ }
120
+ };
121
+ Object.assign(controller, {
122
+ goTo(target, opts = {}) {
123
+ return navigate(() => navigator.goTo(target), opts.source ?? 'api', target);
124
+ },
125
+ next(opts = {}) {
126
+ return navigate(() => navigator.next(), opts.source ?? 'api');
127
+ },
128
+ prev(opts = {}) {
129
+ return navigate(() => navigator.prev(), opts.source ?? 'api');
130
+ },
131
+ first(opts = {}) {
132
+ return navigate(() => navigator.first(), opts.source ?? 'api');
133
+ },
134
+ last(opts = {}) {
135
+ return navigate(() => navigator.last(), opts.source ?? 'api');
136
+ },
137
+ getState: snapshot,
138
+ subscribe(eventOrListener, maybeListener) {
139
+ if (typeof eventOrListener === 'function') {
140
+ stateListeners.add(eventOrListener);
141
+ return () => {
142
+ stateListeners.delete(eventOrListener);
143
+ };
144
+ }
145
+ const event = eventOrListener;
146
+ const listener = maybeListener;
147
+ const group = listeners.get(event) ?? new Set();
148
+ group.add(listener);
149
+ listeners.set(event, group);
150
+ return () => {
151
+ group.delete(listener);
152
+ if (group.size === 0)
153
+ listeners.delete(event);
154
+ };
155
+ },
156
+ setOverview(open) {
157
+ if (overview === open || destroyed)
158
+ return;
159
+ overview = open;
160
+ emit('overview', { open, state: snapshot() });
161
+ },
162
+ setBlackout(active) {
163
+ if (blackout === active || destroyed)
164
+ return;
165
+ blackout = active;
166
+ emit('blackout', { active, state: snapshot() });
167
+ },
168
+ pause() {
169
+ if (paused || destroyed)
170
+ return;
171
+ paused = true;
172
+ scheduler?.pause();
173
+ emit('pause', { paused: true, state: snapshot() });
174
+ },
175
+ resume() {
176
+ if (!paused || destroyed)
177
+ return;
178
+ paused = false;
179
+ scheduler?.resume();
180
+ emit('pause', { paused: false, state: snapshot() });
181
+ },
182
+ handleInput(input, inputState = {}, source = input.kind === 'swipe'
183
+ ? 'swipe'
184
+ : input.kind === 'key'
185
+ ? 'keyboard'
186
+ : 'pointer') {
187
+ if (destroyed)
188
+ return null;
189
+ const picked = decideDeckInput(input, {
190
+ ...inputState,
191
+ overview: overview ? 'open' : inputState.overview,
192
+ blackout: blackout || inputState.blackout,
193
+ dir: inputState.dir ?? deck.meta?.dir,
194
+ });
195
+ if (!picked)
196
+ return null;
197
+ scheduler?.input();
198
+ applyAction(picked, source);
199
+ return picked;
200
+ },
201
+ beforeRender(extra = {}) {
202
+ if (destroyed)
203
+ return;
204
+ const state = snapshot();
205
+ const slide = deck.slides[state.slideIndex];
206
+ const context = {
207
+ state,
208
+ ...(slide ? { slide } : {}),
209
+ ...extra,
210
+ };
211
+ for (const plugin of plugins)
212
+ plugin.beforeRender?.(context);
213
+ },
214
+ afterRender(extra = {}) {
215
+ if (destroyed)
216
+ return;
217
+ const state = snapshot();
218
+ const slide = deck.slides[state.slideIndex];
219
+ const context = {
220
+ state,
221
+ ...(slide ? { slide } : {}),
222
+ ...extra,
223
+ };
224
+ for (const plugin of plugins)
225
+ plugin.afterRender?.(context);
226
+ },
227
+ destroy() {
228
+ if (destroyed)
229
+ return;
230
+ destroyed = true;
231
+ scheduler?.stop();
232
+ for (const cleanup of cleanups.splice(0).reverse())
233
+ cleanup();
234
+ for (const plugin of plugins)
235
+ plugin.onDestroy?.();
236
+ emit('destroy', { state: snapshot() });
237
+ listeners.clear();
238
+ stateListeners.clear();
239
+ },
240
+ });
241
+ for (const plugin of plugins) {
242
+ const cleanup = plugin.setup?.(controller);
243
+ if (typeof cleanup === 'function')
244
+ cleanups.push(cleanup);
245
+ }
246
+ scheduler?.start();
247
+ return controller;
248
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Controls registry and pure input decisions.
3
+ *
4
+ * `DECK_CONTROLS` is the single source for the toolbar, help dialog and
5
+ * `decideDeckInput` key map. No DOM: the host describes the event target as a
6
+ * path of plain element facts (`inputPathFrom` builds it from a real path).
7
+ *
8
+ * Keys (Protoyard registry + Texuli Enter/Backspace):
9
+ * → ↓ PageDown Space Enter / ← ↑ PageUp Shift+Space Backspace, Home/End,
10
+ * O, F, S|P, B|., L, ?, Ctrl/⌘+P, D, Esc stack.
11
+ * Swipe: ≥50 px, 1.5× dominance, <800 ms.
12
+ */
13
+ export declare const DECK_INPUT_ACTIONS: readonly ["next", "prev", "first", "last", "overview", "presenter", "fullscreen", "blackout", "laser", "help", "print", "fullscreen-exit", "copy-link", "jump", "debug"];
14
+ export type DeckAction = (typeof DECK_INPUT_ACTIONS)[number];
15
+ /** @deprecated Use {@link DeckAction}. */
16
+ export type DeckInputAction = DeckAction;
17
+ export declare const DECK_CONTROL_GROUPS: readonly ["navigate", "view", "present", "share", "help", "debug"];
18
+ export type DeckControlGroup = (typeof DECK_CONTROL_GROUPS)[number];
19
+ export declare const DECK_CONTROL_CAPABILITIES: readonly ["fullscreen", "popup", "clipboard", "print", "debug"];
20
+ export type DeckControlCapability = (typeof DECK_CONTROL_CAPABILITIES)[number];
21
+ export type DeckControlEnvironment = Readonly<Record<DeckControlCapability, boolean>>;
22
+ export declare const DEFAULT_DECK_CONTROL_ENVIRONMENT: DeckControlEnvironment;
23
+ export interface DeckControlKey {
24
+ /** `KeyboardEvent.key` (letters match either case). */
25
+ key: string;
26
+ /** How a person reads it: `→`, `Page Down`, `Ctrl + P`. */
27
+ label: string;
28
+ /** `required` (Shift+Space), `allowed` (`?`). Omitted: no Shift. */
29
+ shift?: 'required' | 'allowed';
30
+ modifier?: 'ctrl' | 'meta';
31
+ /** The key only closes this control while it is on (`Esc`). */
32
+ closes?: boolean;
33
+ }
34
+ export interface DeckControl {
35
+ action: DeckAction;
36
+ group: DeckControlGroup;
37
+ /** Message key for the short name (resolved through `DECK_MESSAGES`). */
38
+ labelKey: string;
39
+ /** Message key for the one-sentence description. */
40
+ descriptionKey: string;
41
+ keys: readonly DeckControlKey[];
42
+ toggle: boolean;
43
+ requires: readonly DeckControlCapability[];
44
+ available: (env: DeckControlEnvironment) => boolean;
45
+ }
46
+ /** Every deck control, in display order. */
47
+ export declare const DECK_CONTROLS: readonly DeckControl[];
48
+ /** Look up a control by action. Throws when the action is unknown. */
49
+ export declare function deckControlFor(action: DeckAction): DeckControl;
50
+ /** The controls that work in `env`, in display order. */
51
+ export declare function availableDeckControls(env?: DeckControlEnvironment): DeckControl[];
52
+ /** One help-dialog row: a control's keys, its action and its label key. */
53
+ export interface DeckKeyBinding {
54
+ keys: readonly string[];
55
+ action: DeckAction;
56
+ labelKey: string;
57
+ }
58
+ /** Help-dialog rows for the controls available in `env`. */
59
+ export declare function deckKeyBindings(env?: DeckControlEnvironment): DeckKeyBinding[];
60
+ /** Default-environment key bindings (debug omitted). */
61
+ export declare const DECK_KEY_BINDINGS: readonly DeckKeyBinding[];
62
+ /** A swipe must travel this far (CSS px) along its axis. */
63
+ export declare const DECK_SWIPE_MIN_DISTANCE = 50;
64
+ /** …and be this much longer on its axis than across it (axis lock). */
65
+ export declare const DECK_SWIPE_AXIS_RATIO = 1.5;
66
+ /** Slower drags are not swipes (they are reading or selecting). */
67
+ export declare const DECK_SWIPE_MAX_DURATION_MS = 800;
68
+ /** Interactive blocks put this on their root so clicks/swipes never move the deck. */
69
+ export declare const DECK_INTERACTIVE_ATTRIBUTE = "data-tx-deck-interactive";
70
+ /** Deck chrome (progress, slide number, toolbar) carries this attribute. */
71
+ export declare const DECK_CHROME_ATTRIBUTE = "data-tx-deck-chrome";
72
+ export interface DeckInputTarget {
73
+ tag: string;
74
+ role?: string;
75
+ editable?: boolean;
76
+ href?: boolean;
77
+ attributes?: readonly string[];
78
+ }
79
+ export interface DeckInputState {
80
+ /** `closing` covers the overview's exit transition (the double-advance bug). */
81
+ overview?: 'closed' | 'open' | 'closing';
82
+ blackout?: boolean;
83
+ laser?: boolean;
84
+ help?: boolean;
85
+ fullscreen?: boolean;
86
+ /** True when the debug panel is available; only then does D act. */
87
+ debugAvailable?: boolean;
88
+ /** True while the debug panel is open: Esc closes it first. */
89
+ debug?: boolean;
90
+ /** Flips left/right navigation when `rtl`. */
91
+ dir?: 'ltr' | 'rtl' | 'auto';
92
+ }
93
+ interface Modifiers {
94
+ shiftKey?: boolean;
95
+ ctrlKey?: boolean;
96
+ metaKey?: boolean;
97
+ altKey?: boolean;
98
+ }
99
+ export type DeckInputEvent = (Modifiers & {
100
+ kind: 'key';
101
+ key: string;
102
+ repeat?: boolean;
103
+ isComposing?: boolean;
104
+ path?: readonly DeckInputTarget[];
105
+ }) | (Modifiers & {
106
+ kind: 'click';
107
+ button?: number;
108
+ selectionLength?: number;
109
+ path?: readonly DeckInputTarget[];
110
+ }) | {
111
+ kind: 'swipe';
112
+ dx: number;
113
+ dy: number;
114
+ durationMs?: number;
115
+ pointerType?: 'touch' | 'pen' | 'mouse';
116
+ path?: readonly DeckInputTarget[];
117
+ };
118
+ /** The actions Esc can reach, innermost first. */
119
+ export declare const DECK_ESCAPE_ORDER: readonly DeckAction[];
120
+ /** The action an input event asks for, or `null` when the deck should ignore it. */
121
+ export declare function decideDeckInput(event: DeckInputEvent, state?: DeckInputState): DeckAction | null;
122
+ /**
123
+ * Turns an event path (`event.composedPath()`, innermost first) into the
124
+ * plain targets `decideDeckInput` reads. Non-elements are skipped; the walk
125
+ * stops at `stopAt` (the deck root) when given.
126
+ */
127
+ export declare function inputPathFrom(nodes: readonly unknown[], stopAt?: unknown): DeckInputTarget[];
128
+ export {};