taleem-player 1.0.10 → 1.0.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 CHANGED
@@ -1,25 +1,40 @@
1
+
1
2
  # Taleem Player
2
3
 
3
4
  **Taleem Player** converts **Taleem JSON slide data** into **web-based presentations**.
4
5
 
5
6
  It renders the **same Taleem JSON** in multiple display modes using a single, stable rendering engine.
6
7
 
7
- > **Stable · deck-v1 frozen**
8
- > The slide language, schema, and registry are complete.
8
+ > **Stable · deck-v1 frozen**
9
+ > The slide language, schema, and registry are complete.
9
10
  > Internal improvements may continue without breaking public contracts.
10
11
 
11
12
  ---
12
- Demo & Documentation
13
- 👉 https://bilza2023.github.io/taleem/
13
+
14
+ ## Demo & Documentation
15
+
16
+ 👉 [https://bilza2023.github.io/taleem/](https://bilza2023.github.io/taleem/)
14
17
 
15
18
  The live demo shows:
16
19
 
17
- - Browser Mode (index-based rendering)
18
- - Player Mode (time-based rendering)
19
- - Real production Taleem JSON
20
- - Shared CSS across all modes
21
- - No screenshots. No mock data.
22
- - What you see is the real engine running in the browser.
20
+ * Browser Mode (index-based rendering)
21
+ * Player Mode (time-based rendering)
22
+ * Real production Taleem JSON
23
+ * Shared CSS across all modes
24
+ * No screenshots. No mock data.
25
+ * What you see is the real engine running in the browser.
26
+
27
+ ---
28
+ ## Visual Editor
29
+
30
+ Taleem Player has a companion visual editor.
31
+
32
+ Create structured `deck-v1` presentations in seconds — no manual slide layout required.
33
+
34
+ 👉 https://bilza2023.github.io/taleem-editor/
35
+
36
+ The editor outputs valid Taleem JSON that runs directly in Taleem Player.
37
+
23
38
  ---
24
39
 
25
40
  ## What this project is
@@ -27,8 +42,9 @@ The live demo shows:
27
42
  Taleem Player is a **presentation engine**, not a slide editor.
28
43
 
29
44
  It takes **validated Taleem decks** and turns them into:
30
- - index-based presentations
31
- - time-based presentations
45
+
46
+ * index-based presentations
47
+ * time-based presentations
32
48
 
33
49
  The system is **declarative**, **predictable**, and **CSS-driven**.
34
50
 
@@ -38,7 +54,7 @@ The system is **declarative**, **predictable**, and **CSS-driven**.
38
54
 
39
55
  ```bash
40
56
  npm install taleem-player
41
- ````
57
+ ```
42
58
 
43
59
  ---
44
60
 
@@ -137,6 +153,87 @@ They never change slide meaning.
137
153
 
138
154
  ---
139
155
 
156
+ ## Validation & Authoring Utilities (offline)
157
+
158
+ Taleem Player also exposes **optional authoring utilities** for **offline use**
159
+ during deck creation, build steps, or publishing pipelines.
160
+
161
+ These utilities are **not runtime concerns** and are intentionally kept
162
+ separate from the player rendering path.
163
+
164
+ ```js
165
+ import {
166
+ validateDeckV1,
167
+ validatePlayerDeckV1,
168
+ normalizeDeckForPlayerV1,
169
+ } from "taleem-player/validation";
170
+ ```
171
+
172
+ ---
173
+
174
+ ### `validateDeckV1(deck)`
175
+
176
+ Schema-level validation.
177
+
178
+ * Ensures the deck conforms to **deck-v1**
179
+ * Validates slide types and data shapes
180
+ * Does **not** validate timing semantics
181
+
182
+ Returns `{ ok, value }` or `{ ok: false, errors }`.
183
+
184
+ ---
185
+
186
+ ### `validatePlayerDeckV1(deck)`
187
+
188
+ Player-level semantic validation.
189
+
190
+ * Assumes schema validation has already passed
191
+ * Enforces:
192
+
193
+ * slide start / end correctness
194
+ * strict slide sequencing
195
+ * `showAt` within slide bounds
196
+ * EQ slide timing rules
197
+ * Does **not** mutate or fix data
198
+
199
+ This validator answers:
200
+
201
+ > “Can time safely move forward without ambiguity?”
202
+
203
+ ---
204
+
205
+ ### `normalizeDeckForPlayerV1(deck, options?)`
206
+
207
+ Offline normalization utility.
208
+
209
+ * Patches missing or invalid timing
210
+ * Assigns deterministic mock timings
211
+ * Ensures the deck is **safe for player playback**
212
+ * Intended for build steps, CLIs, or publishing workflows
213
+
214
+ This is **not validation** — it is a controlled fixer.
215
+
216
+ ---
217
+
218
+ ### Usage model
219
+
220
+ **Static editors / CDN usage**
221
+
222
+ * no validation
223
+ * no fixing
224
+ * UX hints only
225
+
226
+ **Authoring / build / publish step**
227
+
228
+ ```
229
+ normalize → validate → publish
230
+ ```
231
+
232
+ These utilities are **explicit opt-in** and are never executed
233
+ by the player runtime.
234
+
235
+ ---
236
+
140
237
  ## CSS
141
238
 
142
239
  ```js
@@ -186,7 +283,8 @@ The core registry supports **additive registration only**.
186
283
 
187
284
  The `eq` slide type is implemented and tested.
188
285
 
189
- It represents **structured symbolic content** and intentionally marks the **upper boundary** of the system.
286
+ It represents **structured symbolic content** and intentionally marks the
287
+ **upper boundary** of the system.
190
288
 
191
289
  Anything more complex than this belongs in a **separate product**, not in the core player.
192
290
 
@@ -405,9 +405,9 @@
405
405
  /* ---------- Side panel items ---------- */
406
406
 
407
407
  .slide.eq.imageRightBulletsLeft .eq-explain-item {
408
- font-size: 1.4rem;
409
- line-height: 1.45;
410
- text-align: center;
408
+ font-size: 1.6rem;
409
+ line-height: 1.75;
410
+ text-align:justify;
411
411
 
412
412
  word-wrap: break-word;
413
413
  overflow-wrap: break-word;
@@ -415,6 +415,8 @@
415
415
  background: rgba(255, 255, 255, 0.06);
416
416
  padding: 14px 18px;
417
417
  border-radius: 12px;
418
+
419
+
418
420
  }
419
421
 
420
422
  /* ---------- Side panel image ---------- */
@@ -13960,7 +13960,179 @@ function validateDeckV1(deck) {
13960
13960
  const r = zodDeckV1.safeParse(deck);
13961
13961
  return r.success ? { ok: true, value: r.data } : { ok: false, errors: r.error.issues };
13962
13962
  }
13963
+
13964
+ // src/validation/validatePlayerDeckV1.js
13965
+ function validatePlayerDeckV1(deck) {
13966
+ const errors = [];
13967
+ if (!deck || !Array.isArray(deck.deck)) {
13968
+ return fail("DECK_INVALID", "deck.deck must be an array");
13969
+ }
13970
+ const slides = deck.deck;
13971
+ validateSlideTiming(slides, errors);
13972
+ validateSlideSequence(slides, errors);
13973
+ validateShowAtRanges(slides, errors);
13974
+ validateEqSlides(slides, errors);
13975
+ return errors.length === 0 ? { ok: true } : { ok: false, errors };
13976
+ }
13977
+ function validateSlideTiming(slides, errors) {
13978
+ slides.forEach((slide, i) => {
13979
+ if (typeof slide.start !== "number" || typeof slide.end !== "number") {
13980
+ errors.push(err(
13981
+ "SLIDE_TIME_MISSING",
13982
+ `Slide ${i} must have numeric start/end`
13983
+ ));
13984
+ return;
13985
+ }
13986
+ if (slide.start >= slide.end) {
13987
+ errors.push(err(
13988
+ "SLIDE_TIME_ORDER",
13989
+ `Slide ${i} start (${slide.start}) must be < end (${slide.end})`
13990
+ ));
13991
+ }
13992
+ if (i === 0 && slide.start !== 0) {
13993
+ errors.push(err(
13994
+ "DECK_START_NOT_ZERO",
13995
+ `First slide must start at 0 (got ${slide.start})`
13996
+ ));
13997
+ }
13998
+ });
13999
+ }
14000
+ function validateSlideSequence(slides, errors) {
14001
+ for (let i = 0; i < slides.length - 1; i++) {
14002
+ const a = slides[i];
14003
+ const b = slides[i + 1];
14004
+ if (a.end !== b.start) {
14005
+ errors.push(err(
14006
+ "SLIDE_SEQUENCE_BREAK",
14007
+ `Slide ${i} ends at ${a.end} but next starts at ${b.start}`
14008
+ ));
14009
+ }
14010
+ if (b.start < a.start) {
14011
+ errors.push(err(
14012
+ "SLIDE_TIME_REVERSE",
14013
+ `Slide ${i + 1} starts before previous slide`
14014
+ ));
14015
+ }
14016
+ }
14017
+ }
14018
+ function validateShowAtRanges(slides, errors) {
14019
+ slides.forEach((slide, si) => {
14020
+ let lastShowAt = null;
14021
+ if (!Array.isArray(slide.data)) return;
14022
+ slide.data.forEach((item, di) => {
14023
+ if (typeof item.showAt !== "number") return;
14024
+ if (item.showAt < slide.start || item.showAt >= slide.end) {
14025
+ errors.push(err(
14026
+ "SHOWAT_OUT_OF_RANGE",
14027
+ `Slide ${si} item ${di} showAt ${item.showAt} outside [${slide.start}, ${slide.end})`
14028
+ ));
14029
+ }
14030
+ if (lastShowAt !== null && item.showAt < lastShowAt) {
14031
+ errors.push(err(
14032
+ "SHOWAT_ORDER",
14033
+ `Slide ${si} item ${di} showAt decreases (${item.showAt} < ${lastShowAt})`
14034
+ ));
14035
+ }
14036
+ lastShowAt = item.showAt;
14037
+ });
14038
+ });
14039
+ }
14040
+ function validateEqSlides(slides, errors) {
14041
+ slides.forEach((slide, si) => {
14042
+ if (slide.type !== "eq") return;
14043
+ let lastLineShowAt = null;
14044
+ slide.data.forEach((line, li) => {
14045
+ if (typeof line.showAt === "number") {
14046
+ if (line.showAt < slide.start || line.showAt >= slide.end) {
14047
+ errors.push(err(
14048
+ "EQ_LINE_SHOWAT_RANGE",
14049
+ `EQ slide ${si} line ${li} showAt ${line.showAt} outside slide range`
14050
+ ));
14051
+ }
14052
+ if (lastLineShowAt !== null && line.showAt < lastLineShowAt) {
14053
+ errors.push(err(
14054
+ "EQ_LINE_ORDER",
14055
+ `EQ slide ${si} line ${li} showAt decreases`
14056
+ ));
14057
+ }
14058
+ lastLineShowAt = line.showAt;
14059
+ }
14060
+ if (Array.isArray(line.spItems)) {
14061
+ line.spItems.forEach((sp, spi) => {
14062
+ if ("showAt" in sp) {
14063
+ errors.push(err(
14064
+ "EQ_SPITEM_SHOWAT_FORBIDDEN",
14065
+ `EQ slide ${si} line ${li} spItem ${spi} must not define showAt`
14066
+ ));
14067
+ }
14068
+ });
14069
+ }
14070
+ });
14071
+ });
14072
+ }
14073
+ function err(code, message) {
14074
+ return { code, message };
14075
+ }
14076
+ function fail(code, message) {
14077
+ return { ok: false, errors: [{ code, message }] };
14078
+ }
14079
+
14080
+ // src/validation/normalizeDeckForPlayerV1.js
14081
+ function normalizeDeckForPlayerV1(deck, opts = {}) {
14082
+ const {
14083
+ slideDuration = 5,
14084
+ // default seconds per slide
14085
+ itemStep = 1
14086
+ // default gap between showAt items
14087
+ } = opts;
14088
+ let time3 = 0;
14089
+ const slides = deck.deck.map((slide) => {
14090
+ const start = isNumber(slide.start) ? slide.start : time3;
14091
+ const end = isNumber(slide.end) && slide.end > start ? slide.end : start + slideDuration;
14092
+ const fixedSlide = {
14093
+ ...slide,
14094
+ start,
14095
+ end,
14096
+ data: fixShowAt(slide.data, start, end, itemStep)
14097
+ };
14098
+ time3 = end;
14099
+ return fixedSlide;
14100
+ });
14101
+ return {
14102
+ ...deck,
14103
+ deck: slides
14104
+ };
14105
+ }
14106
+ function fixShowAt(data, start, end, step) {
14107
+ if (!Array.isArray(data)) return data;
14108
+ let t = start;
14109
+ return data.map((item) => {
14110
+ const showAt2 = isNumber(item.showAt) ? clamp(item.showAt, start, end - 1e-4) : t;
14111
+ t = showAt2 + step;
14112
+ if (item.name === "line" && Array.isArray(item.spItems)) {
14113
+ return {
14114
+ ...item,
14115
+ showAt: showAt2,
14116
+ spItems: item.spItems.map((sp) => stripShowAt(sp))
14117
+ };
14118
+ }
14119
+ return { ...item, showAt: showAt2 };
14120
+ });
14121
+ }
14122
+ function stripShowAt(obj) {
14123
+ if (!obj || typeof obj !== "object") return obj;
14124
+ const { showAt: showAt2, ...rest } = obj;
14125
+ return rest;
14126
+ }
14127
+ function clamp(v, min, max) {
14128
+ return Math.min(Math.max(v, min), max);
14129
+ }
14130
+ function isNumber(v) {
14131
+ return typeof v === "number" && !Number.isNaN(v);
14132
+ }
13963
14133
  export {
14134
+ normalizeDeckForPlayerV1,
13964
14135
  validateDeckV1,
14136
+ validatePlayerDeckV1,
13965
14137
  zodDeckV1
13966
14138
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taleem-player",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "type": "module",
5
5
  "main": "./dist/taleem-player.umd.js",
6
6
  "module": "./dist/taleem-player.esm.js",