@mborecki/crossword 0.0.2 → 0.2.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 (36) hide show
  1. package/dist/cjs/{index-TyGpRn4d.js → index-BlTGwvVC.js} +63 -2
  2. package/dist/cjs/{index-CwHIXXW5.js → index-CEGyCfpa.js} +29 -1
  3. package/dist/cjs/index.cjs.js +2 -1
  4. package/dist/cjs/loader.cjs.js +2 -2
  5. package/dist/cjs/mb-crossword.cjs.entry.js +159 -38
  6. package/dist/cjs/mb-crossword.cjs.js +2 -2
  7. package/dist/collection/components/crossword/cell.js +7 -2
  8. package/dist/collection/components/crossword/clue.js +2 -2
  9. package/dist/collection/components/crossword/mb-crossword.css +107 -13
  10. package/dist/collection/components/crossword/mb-crossword.js +189 -32
  11. package/dist/collection/utils/utils.js +6 -0
  12. package/dist/components/index.js +1 -1
  13. package/dist/components/mb-crossword.js +1 -1
  14. package/dist/esm/{index-B4XIBYtu.js → index-BGHKtxML.js} +63 -2
  15. package/dist/esm/{index-0i8AYf_G.js → index-Dn3GSx6U.js} +29 -2
  16. package/dist/esm/index.js +1 -1
  17. package/dist/esm/loader.js +3 -3
  18. package/dist/esm/mb-crossword.entry.js +159 -38
  19. package/dist/esm/mb-crossword.js +3 -3
  20. package/dist/mb-crossword/index.esm.js +1 -1
  21. package/dist/mb-crossword/mb-crossword.esm.js +1 -1
  22. package/dist/mb-crossword/p-34ea8cf1.entry.js +1 -0
  23. package/dist/mb-crossword/{p-B4XIBYtu.js → p-BGHKtxML.js} +2 -2
  24. package/dist/mb-crossword/p-Dn3GSx6U.js +1 -0
  25. package/dist/types/components/crossword/cell.d.ts +3 -1
  26. package/dist/types/components/crossword/clue.d.ts +5 -3
  27. package/dist/types/components/crossword/mb-crossword.d.ts +18 -6
  28. package/dist/types/components/crossword/types.d.ts +21 -2
  29. package/dist/types/components.d.ts +9 -0
  30. package/dist/types/roboczy/mb-puzzle/apps/crossword/.stencil/shared/grid/src/grid.d.ts +12 -0
  31. package/dist/types/roboczy/mb-puzzle/apps/crossword/.stencil/shared/grid/vitest.config.d.ts +2 -0
  32. package/dist/types/roboczy/mb-puzzle/apps/crossword/.stencil/shared/vec2/src/vec2.d.ts +9 -2
  33. package/dist/types/utils/utils.d.ts +1 -0
  34. package/package.json +6 -1
  35. package/dist/mb-crossword/p-0i8AYf_G.js +0 -1
  36. package/dist/mb-crossword/p-5b227b8e.entry.js +0 -1
@@ -1,44 +1,86 @@
1
1
  *[part=main] {
2
+ position: relative;
2
3
  display: grid;
3
- grid-template-areas: "clues board";
4
- grid-template-columns: 1fr 1fr;
4
+ grid-template-areas: "board" "clues";
5
+ grid-template-columns: 1fr;
6
+ }
7
+ @media (min-width: 768px) {
8
+ *[part=main] {
9
+ grid-template-areas: "clues board" "clues .";
10
+ grid-template-columns: 1fr 1fr;
11
+ }
5
12
  }
6
13
  *[part=main].--focused {
7
14
  background: lightgrey;
8
15
  }
16
+ *[part=main] input.dummy {
17
+ pointer-events: none;
18
+ opacity: 0.2;
19
+ z-index: 100;
20
+ grid-area: board;
21
+ border: none;
22
+ }
9
23
 
10
24
  [part=clues] {
11
25
  grid-area: clues;
12
26
  display: grid;
13
27
  grid-template-columns: 1fr 1fr;
28
+ gap: 16px;
29
+ }
30
+ [part=clues].oneList {
31
+ grid-template-columns: 1fr;
14
32
  }
15
33
 
16
34
  [part=clue].--current {
17
- background: lightcyan;
35
+ background: var(--clue-current-background, lightcyan);
36
+ position: sticky;
37
+ bottom: 0;
38
+ left: 0;
39
+ right: 0;
40
+ }
41
+ [part=clue] .preview {
42
+ font-weight: bold;
43
+ letter-spacing: 0.2em;
44
+ text-align: center;
45
+ display: block;
46
+ text-transform: uppercase;
18
47
  }
19
48
 
20
49
  [part=board] {
21
50
  grid-area: board;
22
- display: grid;
51
+ display: block;
52
+ position: relative;
53
+ container-type: size;
54
+ background: var(--board-background, darkgoldenrod);
55
+ --cell-border-width: 2px;
56
+ --cell-width-temp: calc((100cqw - var(--cell-border-width)) / var(--board-width, 10));
57
+ --cell-width: var(--cell-width-temp);
58
+ --cell-width: round(var(--cell-width-temp), 1px);
23
59
  }
24
60
 
25
61
  [part=cell] {
62
+ display: block;
63
+ color: var(--cell-color, #11138d);
64
+ width: calc(var(--cell-width) - var(--cell-border-width));
26
65
  aspect-ratio: 1;
27
- background: #d9d9d9;
28
- position: relative;
66
+ background: var(--cell-background, #d9d9d9);
67
+ position: absolute;
29
68
  container-type: size;
69
+ top: calc(var(--y, 0) * var(--cell-width));
70
+ left: calc(var(--x, 0) * var(--cell-width));
71
+ border: var(--cell-border-width) solid var(--cell-border-color, red);
30
72
  }
31
- [part=cell]:nth-child(2n) {
32
- background: #999;
33
- }
34
- [part=cell].--blocked {
35
- background: black;
73
+ [part=cell].--alt {
74
+ background: var(--cell-background-alt, #999);
36
75
  }
37
76
  [part=cell].--in-current-clue {
38
- box-shadow: inset 0 0 2px 2px lightblue;
77
+ box-shadow: inset 0 0 2px 2px var(--cell-selected-background, lightblue);
39
78
  }
40
79
  [part=cell].--selected {
41
- background: lightblue;
80
+ background: var(--cell-selected-background, lightblue);
81
+ }
82
+ [part=cell].--special {
83
+ background: var(--cell-special-background, lightgreen);
42
84
  }
43
85
  [part=cell] ._inner {
44
86
  position: absolute;
@@ -54,4 +96,56 @@
54
96
 
55
97
  [part=preview] {
56
98
  display: none;
99
+ }
100
+
101
+ [part=special-border] {
102
+ display: block;
103
+ background: black;
104
+ position: absolute;
105
+ z-index: 100;
106
+ --s-border: max(2px, var(--cell-border-width));
107
+ }
108
+ [part=special-border].left, [part=special-border].right {
109
+ width: var(--s-border);
110
+ height: calc(var(--cell-width) - var(--cell-border-width));
111
+ top: calc(var(--y, 0) * var(--cell-width) + var(--cell-border-width));
112
+ }
113
+ [part=special-border].left {
114
+ left: calc(var(--x, 0) * var(--cell-width) - var(--s-border) / 2);
115
+ }
116
+ [part=special-border].right {
117
+ left: calc((var(--x, 0) + 1) * var(--cell-width) - var(--s-border) / 2);
118
+ }
119
+ [part=special-border].top, [part=special-border].bottom {
120
+ width: calc(var(--cell-width) - var(--cell-border-width));
121
+ height: var(--s-border);
122
+ left: calc(var(--x, 0) * var(--cell-width) + var(--cell-border-width));
123
+ }
124
+ [part=special-border].top {
125
+ top: calc(var(--y, 0) * var(--cell-width));
126
+ }
127
+ [part=special-border].bottom {
128
+ top: calc((var(--y, 0) + 1) * var(--cell-width));
129
+ }
130
+
131
+ [part=label] {
132
+ display: grid;
133
+ place-content: center;
134
+ position: absolute;
135
+ container-type: size;
136
+ top: calc(var(--y, 0) * var(--cell-width));
137
+ left: calc(var(--x, 0) * var(--cell-width));
138
+ width: calc(var(--cell-width) - var(--cell-border-width));
139
+ aspect-ratio: 1;
140
+ }
141
+ [part=label] ._inner {
142
+ position: absolute;
143
+ top: 0;
144
+ left: 0;
145
+ right: 0;
146
+ bottom: 0;
147
+ display: grid;
148
+ place-content: center;
149
+ text-transform: uppercase;
150
+ font-size: 60cqh;
57
151
  }
@@ -1,11 +1,13 @@
1
1
  import { forceUpdate, h } from "@stencil/core";
2
2
  import { Clue } from "./clue";
3
3
  import { Cell } from "./cell";
4
- import { indexFromXY, isKeyboardEventLetter, Vec2fromIndex } from "../../utils/utils";
4
+ import { indexFromXY, isInputEventLetter, Vec2fromIndex } from "../../utils/utils";
5
5
  import { Vec2 } from "@mb-puzzle/vec2";
6
6
  export class MBCrossword {
7
7
  init = true;
8
8
  data;
9
+ showCluePreview = true;
10
+ textInput;
9
11
  hWords = [];
10
12
  vWords = [];
11
13
  currentClueId = null;
@@ -13,6 +15,12 @@ export class MBCrossword {
13
15
  isFocused = false;
14
16
  selectedCell = null;
15
17
  componentWillLoad() {
18
+ console.log('componentWillLoad', this.init, this.data);
19
+ if (this.init && this.data) {
20
+ this.initGame();
21
+ }
22
+ }
23
+ watchData() {
16
24
  if (this.init && this.data) {
17
25
  this.initGame();
18
26
  }
@@ -24,6 +32,7 @@ export class MBCrossword {
24
32
  ];
25
33
  }
26
34
  async initGame(data = this.data) {
35
+ console.log('initGame');
27
36
  if (!data.words) {
28
37
  throw new Error('Words definition missing');
29
38
  }
@@ -41,8 +50,22 @@ export class MBCrossword {
41
50
  this.vWords = [...this.vWords, w];
42
51
  }
43
52
  });
53
+ this.specialBorders = data.specialBorders ?? [];
54
+ this.specialCells = data.specialCells ?? [];
55
+ this.labels = data.labels ?? [];
44
56
  this.buildBoard();
45
57
  }
58
+ async checkAnswer() {
59
+ const cells = this.cells.filter(c => !c.isBlocked);
60
+ console.log(cells);
61
+ cells.forEach(c => {
62
+ console.log(`${c.value} === ${c.answer}`, `${c.x},${c.y}`, c.value === c.answer);
63
+ });
64
+ return cells.every(c => c.value === c.answer);
65
+ }
66
+ specialBorders = [];
67
+ specialCells = [];
68
+ labels = [];
46
69
  boardWidth = 0;
47
70
  boardHeight = 0;
48
71
  cells = [];
@@ -77,6 +100,7 @@ export class MBCrossword {
77
100
  const v = Vec2.from(w);
78
101
  const cellIndex = indexFromXY(new Vec2(v.x + i, v.y), boardWidth);
79
102
  cells[cellIndex].isBlocked = false;
103
+ cells[cellIndex].answer = w.word[i];
80
104
  }
81
105
  }
82
106
  if (w.orientation === 'vertical') {
@@ -84,6 +108,7 @@ export class MBCrossword {
84
108
  const v = Vec2.from(w);
85
109
  const cellIndex = indexFromXY(new Vec2(v.x, v.y + i), boardWidth);
86
110
  cells[cellIndex].isBlocked = false;
111
+ cells[cellIndex].answer = w.word[i];
87
112
  }
88
113
  }
89
114
  });
@@ -91,6 +116,9 @@ export class MBCrossword {
91
116
  this.boardWidth = boardWidth;
92
117
  this.boardHeight = boardHeight;
93
118
  }
119
+ getClueById(id) {
120
+ return this.allWords.find(c => c.id === id) ?? null;
121
+ }
94
122
  isInClue(xy, clue = this.currentClue) {
95
123
  if (clue === null)
96
124
  return false;
@@ -147,25 +175,58 @@ export class MBCrossword {
147
175
  this.selectNextCell();
148
176
  forceUpdate(this);
149
177
  }
178
+ backspace() {
179
+ if (!this.selectedCell)
180
+ return;
181
+ if (this.selectedCell.value) {
182
+ this.selectedCell.value = '';
183
+ this.selectPrevCell();
184
+ }
185
+ else {
186
+ this.selectPrevCell();
187
+ this.selectedCell.value = '';
188
+ }
189
+ forceUpdate(this);
190
+ }
150
191
  selectNextCell() {
151
192
  if (!this.selectedCell || !this.currentClue)
152
193
  return;
153
194
  const currentOrientation = this.currentClue.orientation;
154
- if (isLastCellInWord(this.currentClue, this.selectedCell)) {
195
+ if (isLastCellInClue(this.currentClue, this.selectedCell)) {
155
196
  const nextClueId = this.getNextClueId();
156
- if (nextClueId) {
157
- this.selectClue(nextClueId);
158
- }
159
- else {
160
- this.selectClue(this.hWords[0]?.id ?? this.vWords[0]?.id);
197
+ const clue = this.getClueById(nextClueId);
198
+ if (clue) {
199
+ this.selectClueByXY(Vec2.from(clue));
200
+ return;
161
201
  }
162
202
  return;
163
203
  }
164
204
  if (currentOrientation === 'horizontal') {
165
- this.selectCellByXY(new Vec2(this.selectedCell.x + 1, this.selectedCell.y));
205
+ this.selectClueByXY(new Vec2(this.selectedCell.x + 1, this.selectedCell.y));
166
206
  }
167
207
  if (currentOrientation === 'vertical') {
168
- this.selectCellByXY(new Vec2(this.selectedCell.x, this.selectedCell.y + 1));
208
+ this.selectClueByXY(new Vec2(this.selectedCell.x, this.selectedCell.y + 1));
209
+ }
210
+ }
211
+ selectPrevCell() {
212
+ if (!this.selectedCell || !this.currentClue)
213
+ return;
214
+ const currentOrientation = this.currentClue.orientation;
215
+ if (isFirstCellInWord(this.currentClue, this.selectedCell)) {
216
+ const prevClueId = this.getPrevClueId();
217
+ const clue = this.getClueById(prevClueId);
218
+ if (clue) {
219
+ const cell = getClueLastCell(clue);
220
+ console.log(cell);
221
+ this.selectClueByXY(cell);
222
+ return;
223
+ }
224
+ }
225
+ if (currentOrientation === 'horizontal') {
226
+ this.selectClueByXY(new Vec2(this.selectedCell.x - 1, this.selectedCell.y));
227
+ }
228
+ if (currentOrientation === 'vertical') {
229
+ this.selectClueByXY(new Vec2(this.selectedCell.x, this.selectedCell.y - 1));
169
230
  }
170
231
  }
171
232
  onFocusin() {
@@ -173,6 +234,7 @@ export class MBCrossword {
173
234
  if (!this.currentClueId) {
174
235
  this.currentClueId = this.allWords[0]?.id ?? null;
175
236
  }
237
+ this.textInput.focus();
176
238
  }
177
239
  onFocusOut() {
178
240
  this.isFocused = false;
@@ -236,8 +298,27 @@ export class MBCrossword {
236
298
  if (!clues.length) {
237
299
  return;
238
300
  }
239
- const betterClue = clues.find(c => c.orientation === this.currentClue?.orientation);
240
- this.selectClue(betterClue?.id ?? clues[0].id, { row: v.y, col: v.x });
301
+ if (this.selectedCell && clues.length === 2 && v.eq(Vec2.from(this.selectedCell))) {
302
+ const betterClue = clues.find(c => c.orientation !== this.currentClue?.orientation);
303
+ this.selectClue(betterClue?.id ?? clues[0].id, { row: v.y, col: v.x });
304
+ }
305
+ else {
306
+ const betterClue = clues.find(c => c.orientation === this.currentClue?.orientation);
307
+ this.selectClue(betterClue?.id ?? clues[0].id, { row: v.y, col: v.x });
308
+ }
309
+ }
310
+ getCluePreview(clue) {
311
+ if (!clue)
312
+ return '_';
313
+ const letters = Array(clue.word.length).fill('_');
314
+ for (let i = 0; i < clue.word.length; i++) {
315
+ const pointer = new Vec2(clue.x, clue.y).add(clue.orientation === 'vertical' ? new Vec2(0, 1 * i) : new Vec2(1 * i, 0));
316
+ const cell = this.cells[indexFromXY(pointer, this.boardWidth)];
317
+ if (cell.value) {
318
+ letters[i] = cell.value;
319
+ }
320
+ }
321
+ return letters.join('');
241
322
  }
242
323
  onKeyPress(event) {
243
324
  console.log(event);
@@ -248,9 +329,6 @@ export class MBCrossword {
248
329
  this.selectClue(nextClue);
249
330
  }
250
331
  }
251
- if (isKeyboardEventLetter(event)) {
252
- this.inputLetter(event.key.toLowerCase()[0]);
253
- }
254
332
  if (this.selectedCell) {
255
333
  switch (event.key) {
256
334
  case 'ArrowLeft':
@@ -269,29 +347,61 @@ export class MBCrossword {
269
347
  this.selectClueByXY(this.findNonblockedCell(new Vec2(this.selectedCell.x, this.selectedCell.y), new Vec2(0, 1)));
270
348
  event.preventDefault();
271
349
  return;
350
+ case 'Backspace':
351
+ console.log('BACKSPACE!');
352
+ this.backspace();
272
353
  }
273
354
  }
274
355
  }
356
+ onInput(event) {
357
+ if (isInputEventLetter(event)) {
358
+ this.inputLetter(event.data.toLowerCase()[0]);
359
+ }
360
+ this.textInput.value = '';
361
+ }
275
362
  render() {
276
- return h("div", { key: 'e298ac6f3a1d95bc41c4854b39600f8ac7f1696d', part: "main", class: {
363
+ const hasVWords = Boolean(this.vWords.length);
364
+ const hasHWords = Boolean(this.hWords.length);
365
+ const twoLists = hasHWords && hasVWords;
366
+ return h("div", { key: '87d88806305c6102eeb76c3984d4b9c2ad2d9759', part: "main", class: {
277
367
  "--focused": this.isFocused
278
- }, onFocusin: this.onFocusin.bind(this), onFocusout: this.onFocusOut.bind(this), tabIndex: 0, onKeyDown: this.onKeyPress.bind(this) }, h("div", { key: '74be935779266effb4605f2aa5883bf564610bc0', part: "clues" }, h("div", { key: '125d8938e4f084562b54794158074bdaeda722b5', part: "clue-list" }, this.hWords.map(w => {
368
+ }, onFocusin: this.onFocusin.bind(this), onFocusout: this.onFocusOut.bind(this) }, h("input", { key: '49cdb053a4d2c4ad2c7eb881488b18c68e31fe65', type: "text", class: "dummy", ref: (el) => this.textInput = el, onKeyDown: this.onKeyPress.bind(this), onInput: this.onInput.bind(this) }), h("div", { key: '0ca71a907f70e17a6d2d10bff9462bd0b2284007', part: "clues", class: {
369
+ twoLists, oneList: !twoLists
370
+ } }, hasHWords ? h("div", { part: "clue-list" }, this.hWords.map(w => {
279
371
  const isCurrent = w.id === this.currentClueId;
280
- return h(Clue, { word: w, isCurrent: isCurrent, onPointerDown: () => this.selectClue(w.id) });
281
- })), h("div", { key: '4469227db5156a2d0b30e4dc7ef9ea8f8a1fc3bc', part: "clue-list" }, this.vWords.map(w => {
372
+ return h(Clue, { showPreview: this.showCluePreview, preview: this.getCluePreview(w), word: w, isCurrent: isCurrent, onPointerDown: () => this.selectClue(w.id) });
373
+ })) : '', hasVWords ? h("div", { part: "clue-list" }, this.vWords.map(w => {
282
374
  const isCurrent = w.id === this.currentClueId;
283
- return h(Clue, { word: w, isCurrent: isCurrent, onPointerDown: () => this.selectClue(w.id) });
284
- }))), h("div", { key: '965b2e99d1fbf273907f722e8b15749fbd728070', part: "board", style: {
285
- '--test': '12',
286
- 'grid-template-columns': `repeat(${this.boardWidth}, 1fr)`
287
- } }, this.cells.map(cell => {
375
+ return h(Clue, { showPreview: this.showCluePreview, preview: this.getCluePreview(w), word: w, isCurrent: isCurrent, onPointerDown: () => this.selectClue(w.id) });
376
+ })) : ''), h("div", { key: 'ea16dca25fe7884d25f9748f98740ad83e25293e', part: "board", style: {
377
+ '--board-width': `${this.boardWidth}`,
378
+ 'grid-template-columns': `repeat(${this.boardWidth}, 1fr)`,
379
+ aspectRatio: `${this.boardWidth / this.boardHeight}`
380
+ } }, this.cells.filter(c => !c.isBlocked).map(cell => {
288
381
  const isInCurrentClue = this.isInClue(cell);
289
382
  const isSelected = this.isSelectedCell(cell);
290
- return h(Cell, { isInCurrentClue: isInCurrentClue, isSelected: isSelected, data: cell });
291
- })), h("div", { key: '3d97bcc65c5eb1d25b60aa5183a7c63f6eecebf9', part: "preview" }, "Tu bedzie podgl\u0105d: ", this.currentClueId));
383
+ const isSpecial = this.specialCells.some(c => c.x === cell.x && c.y === cell.y);
384
+ return h(Cell, { isSpecial: isSpecial, onPointerDown: () => this.selectClueByXY(Vec2.from(cell)), isInCurrentClue: isInCurrentClue, isSelected: isSelected, data: cell });
385
+ }), this.specialBorders.map((b, index) => {
386
+ return h("div", { key: index, part: "special-border", class: {
387
+ 'top': Boolean(b.border & 0x1000),
388
+ 'right': Boolean(b.border & 0x0100),
389
+ 'bottom': Boolean(b.border & 0x0010),
390
+ 'left': Boolean(b.border & 0x0001),
391
+ }, style: {
392
+ "--x": `${b.x}`,
393
+ "--y": `${b.y}`
394
+ } });
395
+ }), this.labels.map((label) => {
396
+ return h("div", { part: "label", style: {
397
+ "--x": `${label.x}`,
398
+ "--y": `${label.y}`
399
+ }, onPointerDown: () => this.selectClue(label.clueId) }, h("div", { class: "_inner" }, label.text));
400
+ })), h("div", { key: 'd391413e7be3dee148f2f5e6d8fbf22742278a0c', part: "preview" }, "Tu bedzie podgl\u0105d: ", this.currentClueId));
292
401
  }
293
402
  static get is() { return "mb-crossword"; }
294
403
  static get encapsulation() { return "shadow"; }
404
+ static get delegatesFocus() { return true; }
295
405
  static get originalStyleUrls() {
296
406
  return {
297
407
  "$": ["mb-crossword.scss"]
@@ -347,6 +457,26 @@ export class MBCrossword {
347
457
  },
348
458
  "getter": false,
349
459
  "setter": false
460
+ },
461
+ "showCluePreview": {
462
+ "type": "boolean",
463
+ "mutable": false,
464
+ "complexType": {
465
+ "original": "boolean",
466
+ "resolved": "boolean",
467
+ "references": {}
468
+ },
469
+ "required": false,
470
+ "optional": false,
471
+ "docs": {
472
+ "tags": [],
473
+ "text": ""
474
+ },
475
+ "getter": false,
476
+ "setter": false,
477
+ "reflect": false,
478
+ "attribute": "show-clue-preview",
479
+ "defaultValue": "true"
350
480
  }
351
481
  };
352
482
  }
@@ -358,6 +488,9 @@ export class MBCrossword {
358
488
  "currentClue": {},
359
489
  "isFocused": {},
360
490
  "selectedCell": {},
491
+ "specialBorders": {},
492
+ "specialCells": {},
493
+ "labels": {},
361
494
  "boardWidth": {},
362
495
  "boardHeight": {},
363
496
  "cells": {}
@@ -391,15 +524,39 @@ export class MBCrossword {
391
524
  "text": "",
392
525
  "tags": []
393
526
  }
527
+ },
528
+ "checkAnswer": {
529
+ "complexType": {
530
+ "signature": "() => Promise<boolean>",
531
+ "parameters": [],
532
+ "references": {
533
+ "Promise": {
534
+ "location": "global",
535
+ "id": "global::Promise"
536
+ }
537
+ },
538
+ "return": "Promise<boolean>"
539
+ },
540
+ "docs": {
541
+ "text": "",
542
+ "tags": []
543
+ }
394
544
  }
395
545
  };
396
546
  }
547
+ static get watchers() {
548
+ return [{
549
+ "propName": "data",
550
+ "methodName": "watchData"
551
+ }];
552
+ }
553
+ }
554
+ function isLastCellInClue(clue, cell) {
555
+ return getClueLastCell(clue).eq(Vec2.from(cell));
556
+ }
557
+ function isFirstCellInWord(clue, cell) {
558
+ return Vec2.from(clue).eq(Vec2.from(cell));
397
559
  }
398
- function isLastCellInWord(word, cell) {
399
- const lastCellXY = [
400
- word.x + (word.orientation === 'horizontal' ? (word.word.length - 1) : 0),
401
- word.y + (word.orientation === 'vertical' ? (word.word.length - 1) : 0),
402
- ];
403
- const result = cell.x === lastCellXY[0] && cell.y === lastCellXY[1];
404
- return result;
560
+ function getClueLastCell(clue) {
561
+ return new Vec2(clue.x + (clue.orientation === 'horizontal' ? (clue.word.length - 1) : 0), clue.y + (clue.orientation === 'vertical' ? (clue.word.length - 1) : 0));
405
562
  }
@@ -13,3 +13,9 @@ export function isKeyboardEventLetter(event) {
13
13
  const key = event.key.toLowerCase();
14
14
  return /[0-9a-zA-Z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u024F]/.test(key);
15
15
  }
16
+ export function isInputEventLetter(event) {
17
+ if (event.data.length !== 1)
18
+ return false;
19
+ const key = event.data.toLowerCase();
20
+ return /[0-9a-zA-Z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u024F]/.test(key);
21
+ }