@johnmorrisdotca/kumimoji 1.2.2 → 1.3.1

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 (83) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/CONTRIBUTING.md +11 -2
  3. package/README.md +80 -39
  4. package/dist/check.d.ts +26 -1
  5. package/dist/check.js +30 -3
  6. package/dist/computer.types.d.ts +24 -2
  7. package/dist/computerPlay.d.ts +45 -2
  8. package/dist/computerPlay.js +56 -6
  9. package/dist/computerTurn.d.ts +42 -3
  10. package/dist/computerTurn.js +47 -5
  11. package/dist/daily.d.ts +22 -2
  12. package/dist/daily.js +23 -3
  13. package/dist/element-define.d.ts +1 -1
  14. package/dist/element-define.js +3 -13
  15. package/dist/element.d.ts +85 -4
  16. package/dist/element.js +359 -77
  17. package/dist/embed-day.d.ts +4 -0
  18. package/dist/embed-day.js +23 -0
  19. package/dist/export.d.ts +123 -6
  20. package/dist/export.js +109 -9
  21. package/dist/generate.d.ts +14 -0
  22. package/dist/generate.js +32 -5
  23. package/dist/grid.d.ts +181 -10
  24. package/dist/grid.js +148 -11
  25. package/dist/help.d.ts +36 -3
  26. package/dist/help.js +36 -3
  27. package/dist/index.d.ts +22 -22
  28. package/dist/index.js +22 -22
  29. package/dist/judge.d.ts +14 -0
  30. package/dist/judge.js +14 -0
  31. package/dist/kana.d.ts +23 -1
  32. package/dist/kana.js +62 -7
  33. package/dist/kumimoji.types.d.ts +70 -5
  34. package/dist/party.d.ts +268 -14
  35. package/dist/party.js +296 -19
  36. package/dist/party.types.d.ts +50 -2
  37. package/dist/partyKept.d.ts +74 -4
  38. package/dist/partyKept.js +127 -12
  39. package/dist/partySeats.d.ts +92 -6
  40. package/dist/partySeats.js +83 -8
  41. package/dist/partyTurns.d.ts +171 -5
  42. package/dist/partyTurns.js +187 -8
  43. package/dist/placement.d.ts +50 -2
  44. package/dist/placement.js +35 -2
  45. package/dist/play.d.ts +247 -15
  46. package/dist/play.js +251 -18
  47. package/dist/random.d.ts +34 -3
  48. package/dist/random.js +23 -2
  49. package/dist/react.d.ts +42 -4
  50. package/dist/react.js +54 -7
  51. package/dist/showcase.d.ts +83 -7
  52. package/dist/showcase.js +61 -7
  53. package/dist/strings.d.ts +54 -5
  54. package/dist/strings.js +34 -3
  55. package/dist/tableView.d.ts +151 -8
  56. package/dist/tableView.js +129 -6
  57. package/dist/tileFace.d.ts +59 -5
  58. package/dist/tileFace.js +48 -4
  59. package/dist/tileFamily.d.ts +9 -0
  60. package/dist/tileFamily.js +9 -0
  61. package/dist/tileWords.d.ts +93 -6
  62. package/dist/tileWords.js +94 -12
  63. package/dist/tileWordsModule.d.ts +12 -2
  64. package/dist/tileWordsModule.js +14 -2
  65. package/dist/tiles.constants.d.ts +99 -5
  66. package/dist/tiles.constants.js +171 -13
  67. package/dist/turn.d.ts +82 -4
  68. package/dist/turn.js +88 -5
  69. package/dist/ui/board.d.ts +60 -5
  70. package/dist/ui/board.js +44 -4
  71. package/dist/ui/draw.d.ts +62 -0
  72. package/dist/ui/draw.js +229 -0
  73. package/dist/ui/mount.d.ts +41 -2
  74. package/dist/ui/mount.js +190 -265
  75. package/dist/ui/style.d.ts +6 -1
  76. package/dist/ui/style.js +6 -0
  77. package/dist/ui.d.ts +3 -3
  78. package/dist/ui.js +2 -2
  79. package/dist/version.d.ts +9 -2
  80. package/dist/version.js +9 -2
  81. package/dist/wilds.d.ts +22 -1
  82. package/dist/wilds.js +25 -3
  83. package/package.json +68 -61
package/dist/element.js CHANGED
@@ -1,22 +1,12 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
- if (kind === "m") throw new TypeError("Private method is not writable");
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
- };
12
- var _KumimojiTable_instances, _KumimojiTable_table, _KumimojiTable_key, _KumimojiTable_queued, _KumimojiTable_seed, _KumimojiTable_refresh;
13
1
  import { kumimojiDailySeed } from "./daily.js";
2
+ import { embedDay } from "./embed-day.js";
14
3
  import { KUMIMOJI_HANDS } from "./tiles.constants.js";
15
4
  import { mountKumimoji } from "./ui/mount.js";
5
+ import { KUMIMOJI_STYLE } from "./ui/style.js";
16
6
  const ElementBase = typeof HTMLElement === "undefined" ? class {
17
7
  } : HTMLElement;
18
- const oneOf = (value, allowed) => (allowed.includes(value) ? value : undefined);
19
- const isOn = (value) => value !== null && ["true", "on", "1", "yes"].includes(value.toLowerCase());
8
+ const oneOf = (value, allowed) => allowed.includes(value) ? value : undefined;
9
+ const isOn = (value, fallback = false) => value === null ? fallback : !["false", "off", "no", "0"].includes(value.trim().toLowerCase());
20
10
  const HANDS = Object.values(KUMIMOJI_HANDS);
21
11
  /**
22
12
  * THE `<kumimoji-table>` ELEMENT: a game of Kumimoji alone in a tag, with no
@@ -29,8 +19,7 @@ const HANDS = Object.values(KUMIMOJI_HANDS);
29
19
  * <kumimoji-table language="japanese" level="easy" seed="daily"></kumimoji-table>
30
20
  * ```
31
21
  *
32
- * Attributes (each is read again when it changes; a change to any but `lang`
33
- * deals a new game):
22
+ * Attributes configure the starting game; display, language and control changes preserve play:
34
23
  * - `language`: the tiles, `english` (default) or `japanese` kana.
35
24
  * - `hand`: the opening hand, 3, 7 (default) or 11.
36
25
  * - `level`: how many tiles are wild, `easy` (most), `medium` (default) or `hard` (none).
@@ -42,89 +31,382 @@ const HANDS = Object.values(KUMIMOJI_HANDS);
42
31
  * It fires `kumimoji-change` after every change to the game, with the game as it would
43
32
  * be saved as `event.detail.saved`, and `kumimoji-finish` when the last tile is laid and every run
44
33
  * is a word, with `event.detail` the deal, the play and the time it took. It has the methods
45
- * `newGame()` and `setGame()`. A table the rules do not offer (a hand of 5, say) draws nothing.
34
+ * `newGame()` and `setGame()`. Invalid input shows a status and a plain error event.
35
+ */
36
+ /**
37
+ * Native table options, checked alongside builder coverage.
38
+ * @example
39
+ * ```ts
40
+ * import { KUMIMOJI_EMBED_OPTION_KEYS } from "@johnmorrisdotca/kumimoji/element";
41
+ * console.log(KUMIMOJI_EMBED_OPTION_KEYS.includes("saved"));
42
+ * ```
43
+ */
44
+ export const KUMIMOJI_EMBED_OPTION_KEYS = Object.keys({
45
+ language: true,
46
+ hand: true,
47
+ level: true,
48
+ gameLength: true,
49
+ diagonals: true,
50
+ seed: true,
51
+ onFinish: true,
52
+ onChange: true,
53
+ locale: true,
54
+ strings: true,
55
+ theme: true,
56
+ keep: true,
57
+ controls: true,
58
+ saved: true,
59
+ onError: true,
60
+ });
61
+ /**
62
+ * A reflected crossword table with the native table handle and saved-state getter.
63
+ * @example
64
+ * ```ts
65
+ * import { KumimojiTable } from "@johnmorrisdotca/kumimoji/element";
66
+ * console.log(KumimojiTable.observedAttributes.includes("daily"));
67
+ * ```
46
68
  */
47
69
  export class KumimojiTable extends ElementBase {
48
- constructor() {
49
- super(...arguments);
50
- _KumimojiTable_instances.add(this);
51
- _KumimojiTable_table.set(this, null);
52
- _KumimojiTable_key.set(this, "");
53
- _KumimojiTable_queued.set(this, false);
54
- _KumimojiTable_seed.set(this, null);
70
+ static observedAttributes = [
71
+ "language",
72
+ "hand",
73
+ "level",
74
+ "length",
75
+ "diagonals",
76
+ "seed",
77
+ "lang",
78
+ "keep",
79
+ "daily",
80
+ "controls",
81
+ "theme",
82
+ "size",
83
+ "width",
84
+ "label",
85
+ ];
86
+ #table = null;
87
+ #key = "";
88
+ #queued = false;
89
+ #seed = null;
90
+ #options = {};
91
+ #revision = 0;
92
+ #widthBefore = null;
93
+ #widthOwned = false;
94
+ #labelBefore = null;
95
+ /** Native host options. Callback/presentation changes preserve the current game. */
96
+ get options() {
97
+ return this.#options;
98
+ }
99
+ set options(value) {
100
+ this.#options = value ?? {};
101
+ if (this.isConnected)
102
+ this.#refresh();
103
+ }
104
+ #write(name, value) {
105
+ if (value === null || value === undefined)
106
+ this.removeAttribute(name);
107
+ else
108
+ this.setAttribute(name, String(value));
55
109
  }
56
110
  connectedCallback() {
57
- __classPrivateFieldGet(this, _KumimojiTable_instances, "m", _KumimojiTable_refresh).call(this);
111
+ this.#refresh();
58
112
  }
59
113
  disconnectedCallback() {
60
- __classPrivateFieldGet(this, _KumimojiTable_table, "f")?.destroy();
61
- __classPrivateFieldSet(this, _KumimojiTable_table, null, "f");
62
- __classPrivateFieldSet(this, _KumimojiTable_key, "", "f");
114
+ ++this.#revision;
115
+ this.#table?.destroy();
116
+ this.#table = null;
117
+ this.#key = "";
63
118
  }
64
119
  attributeChangedCallback() {
65
- if (!this.isConnected || __classPrivateFieldGet(this, _KumimojiTable_queued, "f"))
120
+ if (!this.isConnected || this.#queued)
66
121
  return;
67
- __classPrivateFieldSet(this, _KumimojiTable_queued, true, "f");
122
+ this.#queued = true;
68
123
  queueMicrotask(() => {
69
- __classPrivateFieldSet(this, _KumimojiTable_queued, false, "f");
70
- __classPrivateFieldGet(this, _KumimojiTable_instances, "m", _KumimojiTable_refresh).call(this);
124
+ this.#queued = false;
125
+ if (this.isConnected)
126
+ this.#refresh();
71
127
  });
72
128
  }
73
129
  /** The mounted table's handle (`mountKumimoji`), or null while the attributes name no table the rules offer. */
74
130
  get table() {
75
- return __classPrivateFieldGet(this, _KumimojiTable_table, "f");
131
+ return this.#table;
76
132
  }
77
133
  /** The game as it would be saved, or null while there is none (or it is still being dealt). */
78
134
  get saved() {
79
- return __classPrivateFieldGet(this, _KumimojiTable_table, "f")?.saved() ?? null;
135
+ return this.#table?.saved() ?? null;
80
136
  }
81
137
  /** A new game at the same table: see `KumimojiTableHandle.newGame`. */
82
138
  newGame(options) {
83
- return __classPrivateFieldGet(this, _KumimojiTable_table, "f")?.newGame(options) ?? Promise.resolve();
139
+ return this.#table?.newGame(options) ?? Promise.resolve();
84
140
  }
85
141
  /** Put a game on the table: see `KumimojiTableHandle.setGame`. */
86
142
  setGame(saved) {
87
- return __classPrivateFieldGet(this, _KumimojiTable_table, "f")?.setGame(saved) ?? Promise.resolve();
143
+ return this.#table?.setGame(saved) ?? Promise.resolve();
144
+ }
145
+ /** The `language` attribute, reflected. */
146
+ get language() {
147
+ return this.getAttribute("language");
148
+ }
149
+ set language(value) {
150
+ this.#write("language", value);
151
+ }
152
+ /** The `hand` attribute, reflected. */
153
+ get hand() {
154
+ return this.getAttribute("hand");
155
+ }
156
+ set hand(value) {
157
+ this.#write("hand", value);
158
+ }
159
+ /** The `level` attribute, reflected. */
160
+ get level() {
161
+ return this.getAttribute("level");
162
+ }
163
+ set level(value) {
164
+ this.#write("level", value);
165
+ }
166
+ /** The `length` attribute, reflected. */
167
+ get length() {
168
+ return this.getAttribute("length");
169
+ }
170
+ set length(value) {
171
+ this.#write("length", value);
172
+ }
173
+ /** The `diagonals` attribute, reflected. */
174
+ get diagonals() {
175
+ return this.getAttribute("diagonals");
176
+ }
177
+ set diagonals(value) {
178
+ this.#write("diagonals", value);
179
+ }
180
+ /** The `seed` attribute, reflected. */
181
+ get seed() {
182
+ return this.getAttribute("seed");
183
+ }
184
+ set seed(value) {
185
+ this.#write("seed", value);
186
+ }
187
+ /** The `keep` attribute, reflected. */
188
+ get keep() {
189
+ return this.getAttribute("keep");
88
190
  }
191
+ set keep(value) {
192
+ this.#write("keep", value);
193
+ }
194
+ /** The `daily` attribute, reflected. */
195
+ get daily() {
196
+ return this.getAttribute("daily");
197
+ }
198
+ set daily(value) {
199
+ this.#write("daily", value);
200
+ }
201
+ /** The `controls` attribute, reflected. */
202
+ get controls() {
203
+ return this.getAttribute("controls");
204
+ }
205
+ set controls(value) {
206
+ this.#write("controls", value);
207
+ }
208
+ /** The `theme` attribute, reflected. */
209
+ get theme() {
210
+ return this.getAttribute("theme");
211
+ }
212
+ set theme(value) {
213
+ this.#write("theme", value);
214
+ }
215
+ /** The `size` attribute, reflected. */
216
+ get size() {
217
+ return this.getAttribute("size");
218
+ }
219
+ set size(value) {
220
+ this.#write("size", value);
221
+ }
222
+ /** The `width` attribute, reflected. */
223
+ get width() {
224
+ return this.getAttribute("width");
225
+ }
226
+ set width(value) {
227
+ this.#write("width", value);
228
+ }
229
+ /** The `label` attribute, reflected. */
230
+ get label() {
231
+ return this.getAttribute("label");
232
+ }
233
+ set label(value) {
234
+ this.#write("label", value);
235
+ }
236
+ #frame() {
237
+ this.style.display = "block";
238
+ this.style.maxWidth = "100%";
239
+ const width = Number(this.getAttribute("width"));
240
+ const size = this.getAttribute("size");
241
+ const pixels = this.hasAttribute("width") && width > 0 && Number.isFinite(width)
242
+ ? Math.min(width, 2000)
243
+ : size === "small"
244
+ ? 320
245
+ : size === "medium"
246
+ ? 480
247
+ : size === "large"
248
+ ? 640
249
+ : null;
250
+ if (pixels !== null) {
251
+ if (!this.#widthOwned)
252
+ this.#widthBefore = this.style.width;
253
+ this.#widthOwned = true;
254
+ this.style.width = `${pixels}px`;
255
+ }
256
+ else if (this.#widthOwned) {
257
+ this.style.width = this.#widthBefore ?? "";
258
+ this.#widthOwned = false;
259
+ }
260
+ const theme = this.getAttribute("theme");
261
+ this.style.colorScheme = theme === "light" || theme === "dark" ? theme : "light dark";
262
+ this.setAttribute("data-kumimoji-embed", "");
263
+ const root = this.getRootNode();
264
+ const target = typeof ShadowRoot !== "undefined" && root instanceof ShadowRoot ? root : this.ownerDocument.head;
265
+ if (target.querySelector("style[data-kumimoji-embed-style]") === null) {
266
+ const style = this.ownerDocument.createElement("style");
267
+ style.setAttribute("data-kumimoji-embed-style", "");
268
+ const light = KUMIMOJI_STYLE.match(/\.km-root \{([^}]+)\}/)?.[1] ?? "";
269
+ const dark = KUMIMOJI_STYLE.match(/@media \(prefers-color-scheme: dark\) \{\s*\.km-root \{([^}]+)\}/)?.[1] ?? "";
270
+ style.textContent = `[data-kumimoji-embed][theme="light"] .km-root{${light}}[data-kumimoji-embed][theme="dark"] .km-root{${dark}}[data-kumimoji-embed]>.km-root{background:var(--km-panel);padding:12px;border-radius:12px}[data-kumimoji-embed] .km-controls[hidden]{display:none}`;
271
+ target.append(style);
272
+ }
273
+ if (this.hasAttribute("label")) {
274
+ this.#labelBefore ??= { role: this.getAttribute("role"), label: this.getAttribute("aria-label") };
275
+ this.setAttribute("role", "group");
276
+ this.setAttribute("aria-label", this.getAttribute("label"));
277
+ }
278
+ else if (this.#labelBefore !== null) {
279
+ for (const [name, value] of [
280
+ ["role", this.#labelBefore.role],
281
+ ["aria-label", this.#labelBefore.label],
282
+ ]) {
283
+ if (value === null)
284
+ this.removeAttribute(name);
285
+ else
286
+ this.setAttribute(name, value);
287
+ }
288
+ this.#labelBefore = null;
289
+ }
290
+ }
291
+ #refresh() {
292
+ this.#frame();
293
+ try {
294
+ const language = oneOf(this.getAttribute("language"), ["english", "japanese"]) ?? this.#options.language;
295
+ const hand = this.hasAttribute("hand") ? Number(this.getAttribute("hand")) : this.#options.hand;
296
+ const level = oneOf(this.getAttribute("level"), ["easy", "medium", "hard"]) ?? this.#options.level;
297
+ const gameLength = oneOf(this.getAttribute("length"), ["short", "medium", "full"]) ?? this.#options.gameLength;
298
+ const diagonals = isOn(this.getAttribute("diagonals"), this.#options.diagonals ?? false);
299
+ const seedAttribute = this.getAttribute("seed");
300
+ const asked = Number(seedAttribute);
301
+ const day = embedDay(this.hasAttribute("daily") ? this.getAttribute("daily") : seedAttribute === "daily" ? "today" : null);
302
+ let seed;
303
+ if (day !== null)
304
+ seed = kumimojiDailySeed(new Date(`${day}T00:00:00Z`));
305
+ else if (seedAttribute !== null && Number.isInteger(asked) && asked >= 0 && asked <= 0xffffffff)
306
+ seed = asked;
307
+ else if (this.#options.seed !== undefined && this.#options.seed !== null)
308
+ seed = this.#options.seed;
309
+ else {
310
+ let held = this.#seed;
311
+ if (held === null) {
312
+ held = Math.floor(Math.random() * 2 ** 31);
313
+ this.#seed = held;
314
+ }
315
+ seed = held;
316
+ }
317
+ const lang = this.getAttribute("lang");
318
+ const locale = lang === "en" || lang === "ja"
319
+ ? lang
320
+ : (this.#options.locale ??
321
+ (this.ownerDocument.documentElement.lang.toLowerCase().startsWith("ja") ? "ja" : "en"));
322
+ const keep = isOn(this.getAttribute("keep"), this.#options.keep ?? true);
323
+ const controls = isOn(this.getAttribute("controls"), this.#options.controls ?? true);
324
+ const key = JSON.stringify([language, hand, level, gameLength, diagonals, seed, day, this.#options.saved]);
325
+ if (key === this.#key && this.#table !== null) {
326
+ this.#table.set({ locale, strings: this.#options.strings, theme: this.#options.theme ?? {}, keep, controls });
327
+ return;
328
+ }
329
+ if (hand !== undefined && !HANDS.includes(hand))
330
+ throw new RangeError("Choose a hand of 3, 7 or 11 tiles.");
331
+ this.#table?.destroy();
332
+ this.#table = null;
333
+ this.#key = "";
334
+ this.replaceChildren();
335
+ const revision = ++this.#revision;
336
+ const table = mountKumimoji(this, {
337
+ ...this.#options,
338
+ language,
339
+ hand,
340
+ level,
341
+ gameLength,
342
+ diagonals,
343
+ seed,
344
+ locale,
345
+ keep,
346
+ controls,
347
+ onChange: (saved) => {
348
+ this.dispatchEvent(new CustomEvent("kumimoji-change", {
349
+ detail: JSON.parse(JSON.stringify({ saved })),
350
+ bubbles: true,
351
+ composed: true,
352
+ }));
353
+ this.#options.onChange?.(saved);
354
+ },
355
+ onFinish: (result) => {
356
+ this.dispatchEvent(new CustomEvent("kumimoji-finish", {
357
+ detail: JSON.parse(JSON.stringify(result)),
358
+ bubbles: true,
359
+ composed: true,
360
+ }));
361
+ this.#options.onFinish?.(result);
362
+ },
363
+ onError: (error) => {
364
+ this.dispatchEvent(new CustomEvent("kumimoji-error", { detail: { message: error.message }, bubbles: true, composed: true }));
365
+ this.#options.onError?.(error);
366
+ },
367
+ });
368
+ this.#table = table;
369
+ this.#key = key;
370
+ void table.ready
371
+ .then(() => {
372
+ if (this.isConnected && revision === this.#revision && table.saved() !== null)
373
+ this.dispatchEvent(new CustomEvent("kumimoji-load", {
374
+ bubbles: true,
375
+ composed: true,
376
+ detail: { seed: table.saved().deal.seed, day },
377
+ }));
378
+ })
379
+ .catch(() => {
380
+ /* The native ready error handler reports the load failure. */
381
+ });
382
+ }
383
+ catch (error) {
384
+ this.#table?.destroy();
385
+ this.#table = null;
386
+ this.#key = "";
387
+ const message = error instanceof Error ? error.message : String(error);
388
+ const status = this.ownerDocument.createElement("p");
389
+ status.setAttribute("role", "status");
390
+ status.textContent = message;
391
+ this.replaceChildren(status);
392
+ this.dispatchEvent(new CustomEvent("kumimoji-error", { detail: { message }, bubbles: true, composed: true }));
393
+ }
394
+ }
395
+ }
396
+ /**
397
+ * Defines the table or a subclass under a custom tag. Server-safe and idempotent.
398
+ * @param tagName - The tag name; kumimoji-table by default.
399
+ * @param registry - The page registry, or undefined on a server.
400
+ * @returns Nothing; existing definitions are left alone.
401
+ * @example
402
+ * ```ts
403
+ * import { defineTable } from "@johnmorrisdotca/kumimoji/element";
404
+ * defineTable();
405
+ * console.log(typeof defineTable);
406
+ * ```
407
+ */
408
+ export function defineTable(tagName = "kumimoji-table", registry = typeof customElements === "undefined" ? undefined : customElements) {
409
+ if (registry !== undefined && registry.get(tagName) === undefined)
410
+ registry.define(tagName, tagName === "kumimoji-table" ? KumimojiTable : class extends KumimojiTable {
411
+ });
89
412
  }
90
- _KumimojiTable_table = new WeakMap(), _KumimojiTable_key = new WeakMap(), _KumimojiTable_queued = new WeakMap(), _KumimojiTable_seed = new WeakMap(), _KumimojiTable_instances = new WeakSet(), _KumimojiTable_refresh = function _KumimojiTable_refresh() {
91
- const language = oneOf(this.getAttribute("language"), ["english", "japanese"]);
92
- const handAttribute = this.getAttribute("hand");
93
- const hand = handAttribute === null ? undefined : Number(handAttribute);
94
- const level = oneOf(this.getAttribute("level"), ["easy", "medium", "hard"]);
95
- const gameLength = oneOf(this.getAttribute("length"), ["short", "medium", "full"]);
96
- const diagonals = isOn(this.getAttribute("diagonals"));
97
- const seedAttribute = this.getAttribute("seed");
98
- const asked = Number(seedAttribute);
99
- const seed = seedAttribute === "daily"
100
- ? kumimojiDailySeed(new Date())
101
- : seedAttribute !== null && Number.isInteger(asked) && asked >= 0 && asked <= 0xffffffff
102
- ? asked
103
- : (__classPrivateFieldSet(this, _KumimojiTable_seed, __classPrivateFieldGet(this, _KumimojiTable_seed, "f") ?? Math.floor(Math.random() * 2 ** 31), "f"));
104
- const lang = this.getAttribute("lang");
105
- const locale = lang === "en" || lang === "ja" ? lang : undefined;
106
- const keep = !["false", "off", "0", "no"].includes((this.getAttribute("keep") ?? "").toLowerCase());
107
- const key = JSON.stringify([language, hand, level, gameLength, diagonals, seed, keep]);
108
- if (key === __classPrivateFieldGet(this, _KumimojiTable_key, "f") && __classPrivateFieldGet(this, _KumimojiTable_table, "f") !== null) {
109
- __classPrivateFieldGet(this, _KumimojiTable_table, "f").setLocale(locale ?? (document.documentElement.lang.toLowerCase().startsWith("ja") ? "ja" : "en"));
110
- return;
111
- }
112
- __classPrivateFieldGet(this, _KumimojiTable_table, "f")?.destroy();
113
- __classPrivateFieldSet(this, _KumimojiTable_table, null, "f");
114
- __classPrivateFieldSet(this, _KumimojiTable_key, key, "f");
115
- if (hand !== undefined && !HANDS.includes(hand))
116
- return;
117
- __classPrivateFieldSet(this, _KumimojiTable_table, mountKumimoji(this, {
118
- language,
119
- hand,
120
- level,
121
- gameLength,
122
- diagonals,
123
- seed,
124
- locale,
125
- keep,
126
- onChange: (saved) => this.dispatchEvent(new CustomEvent("kumimoji-change", { detail: { saved }, bubbles: true })),
127
- onFinish: (result) => this.dispatchEvent(new CustomEvent("kumimoji-finish", { detail: result, bubbles: true })),
128
- }), "f");
129
- };
130
- KumimojiTable.observedAttributes = ["language", "hand", "level", "length", "diagonals", "seed", "lang", "keep"];
@@ -0,0 +1,4 @@
1
+ /** Shared UTC day parsing for the family's daily embeds. No storage, service or rollover timer. */
2
+ export declare function embedDay(value: string | null | undefined, now?: Date): string | null;
3
+ /** The same day and namespace give the same nonzero 32-bit seed on every machine. */
4
+ export declare function dailySeed(day: string, namespace: string): number;
@@ -0,0 +1,23 @@
1
+ /** Shared UTC day parsing for the family's daily embeds. No storage, service or rollover timer. */
2
+ export function embedDay(value, now = new Date()) {
3
+ if (value === null || value === undefined || ["off", "false", "no", "0"].includes(value.trim().toLowerCase()))
4
+ return null;
5
+ const asked = value.trim().toLowerCase();
6
+ const day = ["", "today", "on", "true", "1"].includes(asked) ? Number.isNaN(now.getTime()) ? "" : now.toISOString().slice(0, 10) : value.trim();
7
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day))
8
+ throw new RangeError("Use a real UTC date written YYYY-MM-DD.");
9
+ const time = Date.parse(`${day}T00:00:00Z`);
10
+ if (!Number.isFinite(time) || new Date(time).toISOString().slice(0, 10) !== day)
11
+ throw new RangeError("Use a real UTC date written YYYY-MM-DD.");
12
+ return day;
13
+ }
14
+ /** The same day and namespace give the same nonzero 32-bit seed on every machine. */
15
+ export function dailySeed(day, namespace) {
16
+ const parsed = embedDay(day);
17
+ if (parsed === null)
18
+ throw new RangeError("A daily seed needs a UTC date.");
19
+ let hash = 2166136261;
20
+ for (const letter of `${parsed}/${namespace}`)
21
+ hash = Math.imul(hash ^ letter.charCodeAt(0), 16777619) >>> 0;
22
+ return hash || 1;
23
+ }