@operato/scene-storage 10.0.0-beta.41 → 10.0.0-beta.42

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 (82) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/MIGRATION-plan-a-slot-api.md +266 -0
  3. package/PLAN-A-rack-as-slot-holder.md +164 -0
  4. package/dist/crane.js +1 -1
  5. package/dist/crane.js.map +1 -1
  6. package/dist/index.d.ts +3 -4
  7. package/dist/index.js +1 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/rack-grid-3d.d.ts +18 -7
  10. package/dist/rack-grid-3d.js +372 -69
  11. package/dist/rack-grid-3d.js.map +1 -1
  12. package/dist/rack-grid-cell.d.ts +21 -72
  13. package/dist/rack-grid-cell.js +147 -243
  14. package/dist/rack-grid-cell.js.map +1 -1
  15. package/dist/rack-grid.d.ts +277 -56
  16. package/dist/rack-grid.js +1230 -695
  17. package/dist/rack-grid.js.map +1 -1
  18. package/dist/rack-materials.d.ts +9 -0
  19. package/dist/rack-materials.js +55 -0
  20. package/dist/rack-materials.js.map +1 -0
  21. package/dist/storage-rack-3d.d.ts +15 -0
  22. package/dist/storage-rack-3d.js +131 -30
  23. package/dist/storage-rack-3d.js.map +1 -1
  24. package/dist/storage-rack.d.ts +242 -45
  25. package/dist/storage-rack.js +684 -106
  26. package/dist/storage-rack.js.map +1 -1
  27. package/package.json +3 -3
  28. package/src/crane.ts +1 -1
  29. package/src/index.ts +3 -4
  30. package/src/rack-grid-3d.ts +383 -80
  31. package/src/rack-grid-cell.ts +161 -305
  32. package/src/rack-grid.ts +1263 -762
  33. package/src/rack-materials.ts +61 -0
  34. package/src/storage-rack-3d.ts +144 -30
  35. package/src/storage-rack.ts +763 -111
  36. package/test/test-carrier-lifecycle.ts +361 -0
  37. package/test/test-coord-alignment.ts +201 -0
  38. package/test/test-external-to-rack.ts +461 -0
  39. package/test/test-mover-concurrent-bug.ts +304 -0
  40. package/test/test-mover-rollback.ts +290 -0
  41. package/test/test-r19-place-absorb.ts +174 -0
  42. package/test/test-rack-3d-attach-real.ts +301 -0
  43. package/test/test-rack-concurrent.ts +254 -0
  44. package/test/test-rack-edge-cases.ts +323 -0
  45. package/test/test-rack-grid-cell.ts +318 -0
  46. package/test/test-rack-grid-location.ts +657 -0
  47. package/test/test-real-3d-positioning.ts +158 -0
  48. package/test/test-slot-center-convention.ts +116 -0
  49. package/test/test-slot-target.ts +189 -0
  50. package/test/test-storage-rack-batched.ts +606 -0
  51. package/test/test-storage-rack-click.ts +329 -0
  52. package/test/test-storage-rack-slot-api.ts +357 -0
  53. package/test/test-toscene-convention.ts +162 -0
  54. package/test/test-user-scenario-sequential.ts +334 -0
  55. package/translations/en.json +2 -0
  56. package/translations/ja.json +2 -0
  57. package/translations/ko.json +2 -0
  58. package/translations/ms.json +2 -0
  59. package/translations/zh.json +2 -0
  60. package/tsconfig.tsbuildinfo +1 -1
  61. package/dist/rack-column.d.ts +0 -35
  62. package/dist/rack-column.js +0 -258
  63. package/dist/rack-column.js.map +0 -1
  64. package/dist/rack-grid-helpers.d.ts +0 -28
  65. package/dist/rack-grid-helpers.js +0 -71
  66. package/dist/rack-grid-helpers.js.map +0 -1
  67. package/dist/rack-grid-location.d.ts +0 -37
  68. package/dist/rack-grid-location.js +0 -227
  69. package/dist/rack-grid-location.js.map +0 -1
  70. package/dist/storage-cell-3d.d.ts +0 -25
  71. package/dist/storage-cell-3d.js +0 -88
  72. package/dist/storage-cell-3d.js.map +0 -1
  73. package/dist/storage-cell.d.ts +0 -73
  74. package/dist/storage-cell.js +0 -215
  75. package/dist/storage-cell.js.map +0 -1
  76. package/src/rack-column.ts +0 -340
  77. package/src/rack-grid-helpers.ts +0 -77
  78. package/src/rack-grid-location.ts +0 -286
  79. package/src/storage-cell-3d.ts +0 -101
  80. package/src/storage-cell.ts +0 -267
  81. package/test/test-cell-position.ts +0 -105
  82. package/test/test-rack-grid.ts +0 -77
@@ -1,7 +1,7 @@
1
- import { Component, Properties, RealObject } from '@hatiolab/things-scene';
1
+ import { Component, ComponentNature, Properties } from '@hatiolab/things-scene';
2
2
  import type { State } from '@hatiolab/things-scene';
3
- /** RackGridCell 컴포넌트 state */
4
3
  export interface RackGridCellState extends State {
4
+ cellId?: string;
5
5
  section?: string;
6
6
  unit?: string;
7
7
  shelfLocations?: string;
@@ -253,89 +253,38 @@ declare const RackGridCell_base: (new (...args: any[]) => {
253
253
  get element(): HTMLElement | null;
254
254
  set element(_v: HTMLElement | null): any;
255
255
  }) & typeof Component;
256
- /**
257
- * 1. 스타일을 상속 받아야 함. (cascade-style)
258
- * 2. 스타일을 동적처리할 수 있음. (로직처리)
259
- * 3. 데이타를 받을 수 있음.
260
- */
261
- export declare class RackGridCell extends RackGridCell_base {
256
+ export default class RackGridCell extends RackGridCell_base {
262
257
  get state(): RackGridCellState;
263
258
  _focused: boolean;
259
+ constructor(model: any, context: any);
264
260
  get hasTextProperty(): boolean;
265
- get nature(): {
266
- mutable: boolean;
267
- resizable: boolean;
268
- rotatable: boolean;
269
- properties: ({
270
- type: string;
271
- label: string;
272
- name: string;
273
- placeholder?: undefined;
274
- property?: undefined;
275
- } | {
276
- type: string;
277
- label: string;
278
- name: string;
279
- placeholder: string;
280
- property?: undefined;
281
- } | {
282
- type: string;
283
- label: string;
284
- name: string;
285
- property: {
286
- event: {
287
- 'increase-location-pattern': (event: CustomEvent) => void;
288
- };
289
- merge?: undefined;
290
- split?: undefined;
291
- };
292
- placeholder?: undefined;
293
- } | {
294
- type: string;
295
- label: string;
296
- name: string;
297
- property: {
298
- merge: boolean;
299
- split: boolean;
300
- event?: undefined;
301
- };
302
- placeholder?: undefined;
303
- })[];
304
- };
305
- buildRealObject(): RealObject | undefined;
306
- set merged(merged: any);
307
- get merged(): any;
308
- set rowspan(rowspan: any);
309
- get rowspan(): any;
310
- set colspan(colspan: any);
311
- get colspan(): any;
261
+ get nature(): ComponentNature;
262
+ /** 3D mesh 미생성 — view mode 의 리소스 0. */
263
+ buildRealObject(): undefined;
264
+ get isEmpty(): boolean;
312
265
  get border(): any;
313
- get isEmpty(): any;
314
- _drawBorder(context: CanvasRenderingContext2D, x: number, y: number, to_x: number, to_y: number, style: any): void;
315
- render(context: CanvasRenderingContext2D): void;
316
- _draw_rack_cell(context: CanvasRenderingContext2D): void;
317
- get decotag(): string;
266
+ set merged(v: boolean);
267
+ get merged(): boolean;
268
+ set rowspan(v: number);
269
+ get rowspan(): number;
270
+ set colspan(v: number);
271
+ get colspan(): number;
272
+ /** Parent (RackGrid) 안에서 자기 (col, row) 인덱스 — components 순서 = row * columns + col. */
318
273
  get index(): {
319
274
  row: number;
320
275
  column: number;
321
276
  };
322
277
  get rowIndex(): number;
323
278
  get columnIndex(): number;
324
- get leftCell(): any;
325
- get rightCell(): any;
326
- get aboveCell(): any;
327
- get belowCell(): any;
328
- get rowCells(): any;
329
- get columnCells(): any;
330
- get aboveRowCells(): any;
331
- get lastUnit(): number;
332
- get firstUnit(): number;
333
- get notEmptyRowCells(): any;
334
- get emptyRowCells(): any;
279
+ get rowCells(): Component[];
280
+ get columnCells(): Component[];
281
+ get notEmptyRowCells(): Component[];
335
282
  get isAisle(): boolean;
336
- onchange(after: Properties, before: Properties): void;
283
+ onchange(after: Properties, _before: Properties): void;
337
284
  onmouseenter(): void;
338
285
  onmouseleave(): void;
339
286
  contains(x: number, y: number): boolean;
287
+ render(context: CanvasRenderingContext2D): void;
288
+ private _drawSide;
340
289
  }
341
290
  export {};
@@ -1,36 +1,56 @@
1
- import { __decorate } from "tslib";
2
1
  /*
3
2
  * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * RackGridCell — RackGrid 의 *configuration storage* 단위 = 한 bay 의 메타데이터 핸들.
5
+ *
6
+ * 모드별 역할:
7
+ * - modeling (2D editor): 자체 사각형 render + click + nature.properties UI
8
+ * (rack-table-cell 패턴 그대로)
9
+ * - 2D / 3D view: *data holder 만* — render/mesh 모두 X. 외부 location API 가
10
+ * 이 cell 의 state.section/unit/isEmpty 등을 *configuration source of truth* 로 lookup.
11
+ *
12
+ * 3D mesh 는 *없음* (buildRealObject = undefined). 3D stock 시각화는 *RackGrid
13
+ * 자체의 RackGrid3D + InstancedMesh* 가 처리. cell-component 는 *3D 리소스 0*.
14
+ *
15
+ * 영속 데이터: cellId (bay 위치), section, unit, shelfLocations, isEmpty, border 등.
16
+ * RackGrid 의 hierarchy override 가 *redundant 좌표 (left/top/width/height)* 만 제거 —
17
+ * 위 own 데이터는 그대로 직렬화 (저장/로드 시 복원).
4
18
  */
19
+ import { __decorate } from "tslib";
5
20
  import { Component, RectPath, sceneComponent } from '@hatiolab/things-scene';
6
- import { Rack } from './rack-column.js';
7
21
  const EMPTY_BORDER = {};
8
- function isBottomMost(idx, rows, columns) {
9
- return idx >= (rows - 1) * columns;
22
+ const EMPTY_CELL_FILL = '#efefef';
23
+ const EMPTY_CELL_STROKE = '#ccc';
24
+ function hasAnyProperty(o, ...props) {
25
+ for (const p of props)
26
+ if (Object.prototype.hasOwnProperty.call(o, p))
27
+ return true;
28
+ return false;
10
29
  }
11
- function isRightMost(idx, rows, columns) {
12
- return (idx + 1) % columns == 0;
30
+ function isRightMost(idx, _rows, columns) {
31
+ return (idx + 1) % columns === 0;
13
32
  }
14
- function hasAnyProperty(o, ...properties) {
15
- for (let p in properties) {
16
- if (o.hasOwnProperty(properties[p])) {
17
- return true;
18
- }
19
- }
33
+ function isBottomMost(idx, rows, columns) {
34
+ return idx >= (rows - 1) * columns;
20
35
  }
21
- const EMPTY_CELL_STROKE_STYLE = '#ccc';
22
- const EMPTY_CELL_LINE_WIDTH = 1;
23
- const EMPTY_CELL_FILL_STYLE = '#efefef';
24
- /**
25
- * 1. 스타일을 상속 받아야 함. (cascade-style)
26
- * 2. 스타일을 동적처리할 수 있음. (로직처리)
27
- * 3. 데이타를 받을 수 있음.
28
- */
36
+ // RackGridCell refid 충돌 회피 — load 시 root.onadded → _addTraverse 가 자식부터
37
+ // addRefidIndex 호출. cell 의 model.refid 가 *비어있으면* root.getNewRefid() = 1,2,3...
38
+ // 작은 부여 → 부모 RackGrid (refid 7) / Crane (202) 등 *기존 refid 와 충돌*.
39
+ // constructor 에서 *큰 시작값 + monotonic counter* 로 자체 부여 → 충돌 0.
40
+ let _cellRefidCounter = 100_000_000;
29
41
  let RackGridCell = class RackGridCell extends RectPath(Component) {
30
42
  get state() {
31
43
  return super.state;
32
44
  }
33
45
  _focused = false;
46
+ constructor(model, context) {
47
+ super(model, context);
48
+ // refid 미부여 시 큰 값 자체 부여 — root.getNewRefid() 의 1,2,3... 충돌 회피.
49
+ // hierarchy override 가 save 시 refid 제거 → load 마다 fresh.
50
+ if (this.model.refid == null) {
51
+ this.model.refid = _cellRefidCounter++;
52
+ }
53
+ }
34
54
  get hasTextProperty() {
35
55
  return false;
36
56
  }
@@ -40,33 +60,11 @@ let RackGridCell = class RackGridCell extends RectPath(Component) {
40
60
  resizable: true,
41
61
  rotatable: true,
42
62
  properties: [
43
- {
44
- type: 'string',
45
- label: 'section',
46
- name: 'section'
47
- },
48
- {
49
- type: 'string',
50
- label: 'unit',
51
- name: 'unit'
52
- },
53
- {
54
- type: 'string',
55
- label: 'shelf-locations',
56
- name: 'shelfLocations',
57
- placeholder: '1,2,3,... / ,,,04'
58
- },
59
- {
60
- type: 'textarea',
61
- label: 'bin-locations',
62
- name: 'binLocations',
63
- placeholder: '1,2,3,...'
64
- },
65
- {
66
- type: 'checkbox',
67
- label: 'is-empty',
68
- name: 'isEmpty'
69
- },
63
+ { type: 'string', label: 'section', name: 'section' },
64
+ { type: 'string', label: 'unit', name: 'unit' },
65
+ { type: 'string', label: 'shelf-locations', name: 'shelfLocations', placeholder: '1,2,3 또는 ,,,04' },
66
+ { type: 'textarea', label: 'bin-locations', name: 'binLocations', placeholder: '1,2,3,...' },
67
+ { type: 'checkbox', label: 'is-empty', name: 'isEmpty' },
70
68
  {
71
69
  type: 'location-increase-pattern',
72
70
  label: '',
@@ -75,8 +73,16 @@ let RackGridCell = class RackGridCell extends RectPath(Component) {
75
73
  event: {
76
74
  'increase-location-pattern': (event) => {
77
75
  const { increasingDirection, skipNumbering, startSection, startUnit } = event.detail;
78
- const rackTable = this.parent;
79
- rackTable.increaseLocation(increasingDirection, skipNumbering, startSection, startUnit);
76
+ const parent = this.parent;
77
+ if (typeof parent?.increaseLocation === 'function') {
78
+ // RackGrid 의 root.selected 활용 — framework 가 자동 갱신
79
+ const selected = this.root?.selected;
80
+ const keys = (selected || [])
81
+ .filter(c => c?.state?.type === 'rack-grid-cell')
82
+ .map(c => c.state.cellId)
83
+ .filter((k) => !!k);
84
+ parent.increaseLocation(keys, increasingDirection, skipNumbering, startSection, startUnit);
85
+ }
80
86
  }
81
87
  }
82
88
  }
@@ -85,237 +91,135 @@ let RackGridCell = class RackGridCell extends RectPath(Component) {
85
91
  type: 'editor-table',
86
92
  label: '',
87
93
  name: '',
88
- property: {
89
- merge: false,
90
- split: false
91
- }
94
+ property: { merge: false, split: false }
92
95
  }
93
96
  ]
94
97
  };
95
98
  }
99
+ /** 3D mesh 미생성 — view mode 의 리소스 0. */
96
100
  buildRealObject() {
97
- // isEmpty cell 은 3D 에 mesh 를 만들지 않는다. v9 에서는 RackGrid3D.createRacks 가
98
- // cell._realObject 를 사전 설정하면 3D pipeline 이 자동 buildRealObject 호출을 skip
99
- // 하던 것을, v10 의 ThreeCapability 도입 이후로는 *항상* 자동 호출됨. RackGrid3D
100
- // createRacks 의 isEmpty 체크와 이 자동 경로 사이의 불일치로 isEmpty=true cell 도
101
- // 3D 에 그려지던 regression — 여기 isEmpty 체크 추가로 두 경로 정합.
102
- if (this.getState('isEmpty'))
103
- return undefined;
104
- return new Rack(this);
101
+ return undefined;
105
102
  }
106
- set merged(merged) {
107
- this.set('merged', !!merged);
108
- if (merged)
109
- this.set('text', '');
110
- }
111
- get merged() {
112
- return this.getState('merged');
113
- }
114
- set rowspan(rowspan) {
115
- this.set('rowspan', rowspan);
116
- }
117
- get rowspan() {
118
- return this.getState('rowspan');
119
- }
120
- set colspan(colspan) {
121
- this.set('colspan', colspan);
122
- }
123
- get colspan() {
124
- return this.getState('colspan');
103
+ // ── Domain getters ────────────────────────────────────
104
+ get isEmpty() {
105
+ return !!this.getState('isEmpty');
125
106
  }
126
107
  get border() {
127
108
  return this.state.border || EMPTY_BORDER;
128
109
  }
129
- get isEmpty() {
130
- return this.getState('isEmpty');
110
+ set merged(v) {
111
+ this.set('merged', !!v);
112
+ if (v)
113
+ this.set('text', '');
131
114
  }
132
- _drawBorder(context, x, y, to_x, to_y, style) {
133
- if (style && style.strokeStyle && style.lineWidth && style.lineDash) {
134
- context.beginPath();
135
- context.moveTo(x, y);
136
- context.lineTo(to_x, to_y);
137
- Component.drawStroke(context, style);
138
- }
115
+ get merged() {
116
+ return !!this.getState('merged');
139
117
  }
140
- render(context) {
141
- const { left, top, width, height } = this.bounds;
142
- const { isEmpty } = this.state;
143
- const border = this.border;
144
- if (!isEmpty) {
145
- this._draw_rack_cell(context);
146
- }
147
- // Cell 채우기.
148
- context.beginPath();
149
- context.lineWidth = 0;
150
- context.rect(left, top, width, height);
151
- // Border 그리기
118
+ set rowspan(v) { this.set('rowspan', v); }
119
+ get rowspan() { return this.getState('rowspan') ?? 1; }
120
+ set colspan(v) { this.set('colspan', v); }
121
+ get colspan() { return this.getState('colspan') ?? 1; }
122
+ /** Parent (RackGrid) 안에서 자기 (col, row) 인덱스 — components 순서 = row * columns + col. */
123
+ get index() {
152
124
  const parent = this.parent;
125
+ if (!parent)
126
+ return { row: 0, column: 0 };
153
127
  const idx = parent.components.indexOf(this);
154
- const columns = parent.columns || 1;
155
- const rows = parent.rows || 1;
156
- this._drawBorder(context, left, top, left + width, top, border.top);
157
- this._drawBorder(context, left, top + height, left, top, border.left);
158
- if (isRightMost(idx, rows, columns))
159
- this._drawBorder(context, left + width, top, left + width, top + height, border.right);
160
- if (isBottomMost(idx, rows, columns))
161
- this._drawBorder(context, left + width, top + height, left, top + height, border.bottom);
162
- }
163
- _draw_rack_cell(context) {
164
- const { left, top, width, height } = this.bounds;
165
- context.save();
166
- context.fillStyle = EMPTY_CELL_FILL_STYLE;
167
- context.fillRect(left, top, width, height);
168
- context.beginPath();
169
- context.lineWidth = EMPTY_CELL_LINE_WIDTH;
170
- context.strokeStyle = EMPTY_CELL_STROKE_STYLE;
171
- context.moveTo(left, top);
172
- context.lineTo(left + width, top + height);
173
- context.moveTo(left + width, top);
174
- context.lineTo(left, top + height);
175
- context.stroke();
176
- context.closePath();
177
- context.restore();
178
- }
179
- get decotag() {
180
- const rackTable = this.parent;
181
- let { locPattern, zone = '' } = rackTable.model;
182
- locPattern = locPattern.substring(0, locPattern.indexOf('{u}') + 3);
183
- let locationString = '';
184
- if (this.getState('section') && this.getState('unit'))
185
- locationString = locPattern
186
- .replace('{z}', zone)
187
- .replace('{s}', this.getState('section'))
188
- .replace('{u}', this.getState('unit'));
189
- return locationString || '';
190
- }
191
- get index() {
192
- const rackTable = this.parent;
193
- const index = rackTable.components.indexOf(this);
194
- const rowIndex = Math.floor(index / rackTable.columns);
195
- const columnIndex = index % rackTable.columns;
196
- return {
197
- row: rowIndex,
198
- column: columnIndex
199
- };
200
- }
201
- get rowIndex() {
202
- return this.index.row;
203
- }
204
- get columnIndex() {
205
- return this.index.column;
206
- }
207
- get leftCell() {
208
- const rackTable = this.parent;
209
- const rowIndex = this.rowIndex;
210
- const columnIndex = this.columnIndex;
211
- if (columnIndex === 0)
212
- return null;
213
- const leftCell = rackTable.components[rowIndex * rackTable.columns + columnIndex - 1];
214
- return leftCell;
215
- }
216
- get rightCell() {
217
- const rackTable = this.parent;
218
- const rowIndex = this.rowIndex;
219
- const columnIndex = this.columnIndex;
220
- if (columnIndex === rackTable.columns)
221
- return null;
222
- const rightCell = rackTable.components[rowIndex * rackTable.columns + columnIndex + 1];
223
- return rightCell;
224
- }
225
- get aboveCell() {
226
- const rackTable = this.parent;
227
- const rowIndex = this.rowIndex;
228
- const columnIndex = this.columnIndex;
229
- if (rowIndex === 0)
230
- return null;
231
- const aboveCell = rackTable.components[(rowIndex - 1) * rackTable.columns + columnIndex];
232
- return aboveCell;
233
- }
234
- get belowCell() {
235
- const rackTable = this.parent;
236
- const rowIndex = this.rowIndex;
237
- const columnIndex = this.columnIndex;
238
- if (rowIndex === rackTable.rows)
239
- return null;
240
- const belowCell = rackTable.components[(rowIndex + 1) * rackTable.columns + columnIndex];
241
- return belowCell;
128
+ const cols = parent.columns ?? 1;
129
+ return { row: Math.floor(idx / cols), column: idx % cols };
242
130
  }
131
+ get rowIndex() { return this.index.row; }
132
+ get columnIndex() { return this.index.column; }
243
133
  get rowCells() {
244
- const rackTable = this.parent;
245
- return rackTable.getCellsByRow(this.rowIndex);
134
+ const parent = this.parent;
135
+ return parent?.getCellsByRow?.(this.rowIndex) ?? [];
246
136
  }
247
137
  get columnCells() {
248
- const rackTable = this.parent;
249
- return rackTable.getCellsByColumn(this.columnIndex);
250
- }
251
- get aboveRowCells() {
252
- let aboveCell = this.aboveCell;
253
- while (1) {
254
- const aboveRowCells = aboveCell.notEmptyRowCells;
255
- if (aboveRowCells.length > 0)
256
- return aboveRowCells;
257
- aboveCell = aboveCell.aboveCell;
258
- }
259
- }
260
- get lastUnit() {
261
- const rowCells = this.aboveRowCells;
262
- for (let i = rowCells.length - 1; i > 0; i--) {
263
- const cell = rowCells[i];
264
- const unit = cell.getState('unit');
265
- if (unit)
266
- return Number(unit);
267
- }
268
- return 0;
269
- }
270
- get firstUnit() {
271
- const rowCells = this.aboveRowCells;
272
- for (let i = 0; i < rowCells.length; i++) {
273
- const cell = rowCells[i];
274
- const unit = cell.getState('unit');
275
- if (unit)
276
- return Number(unit);
277
- }
278
- return 0;
138
+ const parent = this.parent;
139
+ return parent?.getCellsByColumn?.(this.columnIndex) ?? [];
279
140
  }
280
141
  get notEmptyRowCells() {
281
- return this.rowCells.filter((c) => {
282
- return !c.getState('isEmpty');
283
- });
284
- }
285
- get emptyRowCells() {
286
- return this.rowCells.filter((c) => {
287
- return c.getState('isEmpty');
288
- });
142
+ return this.rowCells.filter(c => !c.getState?.('isEmpty'));
289
143
  }
290
144
  get isAisle() {
291
145
  return this.notEmptyRowCells.length === 0;
292
146
  }
293
- onchange(after, before) {
147
+ // ── Lifecycle ─────────────────────────────────────────
148
+ onchange(after, _before) {
294
149
  if (hasAnyProperty(after, 'isEmpty')) {
295
- // FIXME
150
+ // isEmpty 토글 시 section/unit 클리어 (rack-table-cell 동작)
296
151
  delete this.model.unit;
297
152
  delete this.model.section;
298
153
  }
154
+ // Parent 의 location index 무효화 (RackGrid 가 onchange 다시 안 받으므로 자식이 알림)
155
+ const parent = this.parent;
156
+ parent?.invalidateLocationIndex?.();
299
157
  }
300
- onmouseenter() {
301
- this.trigger('deco');
302
- }
303
- onmouseleave() {
304
- this.trigger('decoreset');
305
- }
158
+ onmouseenter() { this.trigger('deco'); }
159
+ onmouseleave() { this.trigger('decoreset'); }
306
160
  contains(x, y) {
307
- const contains = super.contains(x, y);
308
- if (!contains) {
161
+ const c = super.contains(x, y);
162
+ if (!c) {
309
163
  this._focused = false;
310
164
  this.invalidate();
311
165
  }
312
- return contains;
166
+ return c;
167
+ }
168
+ // ── 2D rendering (modeling editor) ────────────────────
169
+ //
170
+ // *view mode* (= app.isViewMode) 면 안 그림 — *configuration storage 만*. modeling
171
+ // 시에만 사각형 + border + isEmpty 패턴 시각화.
172
+ render(context) {
173
+ if (this.app?.isViewMode)
174
+ return;
175
+ const { left, top, width, height } = this.bounds;
176
+ const { isEmpty } = this.state;
177
+ const border = this.border;
178
+ if (isEmpty) {
179
+ // 빈 cell — 회색 fill + 대각선
180
+ context.save();
181
+ context.fillStyle = EMPTY_CELL_FILL;
182
+ context.fillRect(left, top, width, height);
183
+ context.strokeStyle = EMPTY_CELL_STROKE;
184
+ context.lineWidth = 1;
185
+ context.beginPath();
186
+ context.moveTo(left, top);
187
+ context.lineTo(left + width, top + height);
188
+ context.moveTo(left + width, top);
189
+ context.lineTo(left, top + height);
190
+ context.stroke();
191
+ context.restore();
192
+ }
193
+ // 명시적 cell 영역 표시 (border default — 자식 cell 의 미니 frame)
194
+ context.beginPath();
195
+ context.lineWidth = 0;
196
+ context.rect(left, top, width, height);
197
+ const parent = this.parent;
198
+ const idx = parent?.components?.indexOf?.(this) ?? 0;
199
+ const columns = parent?.columns ?? 1;
200
+ const rows = parent?.rackRows ?? 1;
201
+ this._drawSide(context, left, top, left + width, top, border.top);
202
+ this._drawSide(context, left, top + height, left, top, border.left);
203
+ if (isRightMost(idx, rows, columns)) {
204
+ this._drawSide(context, left + width, top, left + width, top + height, border.right);
205
+ }
206
+ if (isBottomMost(idx, rows, columns)) {
207
+ this._drawSide(context, left + width, top + height, left, top + height, border.bottom);
208
+ }
209
+ }
210
+ _drawSide(ctx, x, y, tx, ty, style) {
211
+ if (!style?.strokeStyle || !style.lineWidth)
212
+ return;
213
+ ctx.beginPath();
214
+ ctx.moveTo(x, y);
215
+ ctx.lineTo(tx, ty);
216
+ Component.drawStroke(ctx, style);
313
217
  }
314
218
  };
315
219
  RackGridCell = __decorate([
316
220
  sceneComponent('rack-grid-cell')
317
221
  ], RackGridCell);
318
- export { RackGridCell };
222
+ export default RackGridCell;
319
223
  ;
320
- ['border'].forEach(getter => Component.memoize(RackGridCell.prototype, getter, false));
224
+ ['border'].forEach(g => Component.memoize(RackGridCell.prototype, g, false));
321
225
  //# sourceMappingURL=rack-grid-cell.js.map