@operato/slot-layout-editor 0.0.0-stage → 10.29.14

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.
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 자리 레이아웃 편집기. 홀더의 층·자리 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.
5
+ *
6
+ * 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:
7
+ * change — 적용한 레이아웃 (this.value)
8
+ * slot-layout-use-default — 개별 설정을 지우고 기본값을 쓰겠다는 의도 (값을 비우는 편집과 다르다)
9
+ * 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.
10
+ */
11
+ import { LitElement } from 'lit';
12
+ import { type SlotLayout, type SlotMount } from '@hatiolab/things-scene';
13
+ import { type PresetSize, type SlotLayoutPreset } from './draft.js';
14
+ import { type EditorIssue } from './issue-text.js';
15
+ export interface SlotLayoutEditorNote {
16
+ text: string;
17
+ level?: 'info' | 'error';
18
+ }
19
+ export interface SlotLayoutEditorOptions<L extends SlotLayout = SlotLayout> {
20
+ /** 개별 설정이 없을 때 보이는 값 (호스트가 고른 기본값). 편집 시작점이 된다 */
21
+ effective?: L;
22
+ /** [기본값 사용]을 보일지. 기본은 개별 설정(value)이 있을 때 */
23
+ canUseDefault?: boolean;
24
+ /** 호스트가 주는 프리셋 */
25
+ presets?: SlotLayoutPreset<L>[];
26
+ /** 프리셋 크기 초기값 */
27
+ presetSize?: Partial<PresetSize>;
28
+ /** 상태 문구 (값의 출처, 단위 상태 등) */
29
+ notes?: SlotLayoutEditorNote[];
30
+ /** 프리셋 없이 새로 시작할 때의 mount. 기본 ON */
31
+ mount?: SlotMount;
32
+ /** 호스트 고유 검증 (경로는 레이아웃 기준) */
33
+ validate?: (layout: L) => EditorIssue[];
34
+ /** 문제 문구. undefined 를 돌려주면 기본 문구 */
35
+ issueText?: (issue: EditorIssue) => string | undefined;
36
+ /** 레이아웃에 덧붙인 호스트 키를 고치는 구역 (예: 실시간 주소 읽기). edit 로 새 레이아웃을 넘긴다 */
37
+ renderExtra?: (draft: L, edit: (next: L) => void) => unknown;
38
+ }
39
+ declare const OxSlotLayoutEditor_base: (new (...args: any[]) => LitElement) & typeof LitElement;
40
+ export declare class OxSlotLayoutEditor extends OxSlotLayoutEditor_base {
41
+ static styles: import("lit").CSSResult;
42
+ /** 개별 설정. 없으면 undefined */
43
+ value: SlotLayout | undefined;
44
+ options: SlotLayoutEditorOptions;
45
+ draft: SlotLayout | undefined;
46
+ tier: number;
47
+ dirty: boolean;
48
+ presetKey: string;
49
+ presetSize: Partial<PresetSize>;
50
+ presetParams: Record<string, number>;
51
+ copyElevation: number;
52
+ willUpdate(changed: Map<string, unknown>): void;
53
+ /** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다 */
54
+ reset(): void;
55
+ get issues(): EditorIssue[];
56
+ private _text;
57
+ private _edit;
58
+ private _apply;
59
+ private _useDefault;
60
+ render(): import("lit-html").TemplateResult<1>;
61
+ private _renderPreset;
62
+ /** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(층·자리 밖의 키)는 가져온다 */
63
+ private _startFrom;
64
+ private _renderDraft;
65
+ private _renderTier;
66
+ /** 받는 타입: 모든 타입(accepts 생략) / 지정 타입만(빈 목록이면 아무것도 받지 않음) */
67
+ private _acceptsEditor;
68
+ private _renderSpec;
69
+ private _renderCustom;
70
+ /** 위에서 본 자리 배치. 오른쪽이 +X, 위쪽이 −Z */
71
+ private _renderPreview;
72
+ }
73
+ declare global {
74
+ interface HTMLElementTagNameMap {
75
+ 'ox-slot-layout-editor': OxSlotLayoutEditor;
76
+ }
77
+ }
78
+ export {};
@@ -0,0 +1,436 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 자리 레이아웃 편집기. 홀더의 층·자리 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.
5
+ *
6
+ * 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:
7
+ * change — 적용한 레이아웃 (this.value)
8
+ * slot-layout-use-default — 개별 설정을 지우고 기본값을 쓰겠다는 의도 (값을 비우는 편집과 다르다)
9
+ * 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.
10
+ */
11
+ import { __decorate } from "tslib";
12
+ import { css, html, nothing, LitElement, svg } from 'lit';
13
+ import { customElement, property, state } from 'lit/decorators.js';
14
+ import { i18next, localize } from '@operato/i18n';
15
+ import { persistenceIssues, validateSlotLayout } from '@hatiolab/things-scene';
16
+ import { addCustomSlot, applyPreset, copyTier, previewTier, removeCustomSlot, removeTier, setTierPattern, updatePattern, updateSlotSpec, updateTier } from './draft.js';
17
+ import { issueText as defaultIssueText, t } from './issue-text.js';
18
+ const num = (e) => {
19
+ const n = e.target.valueAsNumber;
20
+ return Number.isFinite(n) ? n : NaN;
21
+ };
22
+ let OxSlotLayoutEditor = class OxSlotLayoutEditor extends localize(i18next)(LitElement) {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.options = {};
26
+ this.tier = 1;
27
+ this.dirty = false;
28
+ this.presetKey = '';
29
+ this.presetSize = {};
30
+ this.presetParams = {};
31
+ this.copyElevation = NaN;
32
+ }
33
+ willUpdate(changed) {
34
+ var _a, _b, _c, _d, _e, _f, _g;
35
+ // 호스트가 렌더마다 새 options 를 넘겨도 편집 중인 값을 잃지 않도록, 값이나 호스트 기본값이 바뀔 때만 다시 시작한다
36
+ const before = changed.get('options');
37
+ if (changed.has('value') || (changed.has('options') && (before === null || before === void 0 ? void 0 : before.effective) !== ((_a = this.options) === null || _a === void 0 ? void 0 : _a.effective)))
38
+ this.reset();
39
+ else if (changed.has('options') && !((_c = (_b = this.options) === null || _b === void 0 ? void 0 : _b.presets) === null || _c === void 0 ? void 0 : _c.some(p => p.key === this.presetKey)))
40
+ this.presetKey = (_g = (_f = (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.presets) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.key) !== null && _g !== void 0 ? _g : '';
41
+ }
42
+ /** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다 */
43
+ reset() {
44
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
45
+ const start = (_a = this.value) !== null && _a !== void 0 ? _a : (_b = this.options) === null || _b === void 0 ? void 0 : _b.effective;
46
+ this.draft = start ? structuredClone(start) : undefined;
47
+ this.tier = 1;
48
+ this.dirty = false;
49
+ this.presetSize = { ...((_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.presetSize) !== null && _d !== void 0 ? _d : {}) };
50
+ if (!((_f = (_e = this.options) === null || _e === void 0 ? void 0 : _e.presets) === null || _f === void 0 ? void 0 : _f.some(p => p.key === this.presetKey)))
51
+ this.presetKey = (_k = (_j = (_h = (_g = this.options) === null || _g === void 0 ? void 0 : _g.presets) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.key) !== null && _k !== void 0 ? _k : '';
52
+ }
53
+ get issues() {
54
+ var _a, _b, _c;
55
+ if (!this.draft)
56
+ return [];
57
+ const invalid = [...validateSlotLayout(this.draft), ...((_c = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.validate) === null || _b === void 0 ? void 0 : _b.call(_a, this.draft)) !== null && _c !== void 0 ? _c : [])];
58
+ // 저장 불가 검사는 검증을 통과한 값에만 보인다 (같은 문제를 두 번 적지 않는다)
59
+ return invalid.length ? invalid : persistenceIssues(this.draft);
60
+ }
61
+ _text(issue) {
62
+ var _a, _b, _c;
63
+ return (_c = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.issueText) === null || _b === void 0 ? void 0 : _b.call(_a, issue)) !== null && _c !== void 0 ? _c : defaultIssueText(issue);
64
+ }
65
+ _edit(next) {
66
+ var _a, _b;
67
+ this.draft = next;
68
+ this.dirty = true;
69
+ if (!next.tiers.some(x => x.tierNo === this.tier))
70
+ this.tier = (_b = (_a = next.tiers[next.tiers.length - 1]) === null || _a === void 0 ? void 0 : _a.tierNo) !== null && _b !== void 0 ? _b : 1;
71
+ }
72
+ _apply() {
73
+ this.value = structuredClone(this.draft);
74
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
75
+ }
76
+ _useDefault() {
77
+ this.dispatchEvent(new CustomEvent('slot-layout-use-default', { bubbles: true, composed: true }));
78
+ }
79
+ render() {
80
+ var _a, _b, _c, _d;
81
+ const issues = this.issues;
82
+ const canUseDefault = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.canUseDefault) !== null && _b !== void 0 ? _b : this.value !== undefined;
83
+ return html `
84
+ ${((_d = (_c = this.options) === null || _c === void 0 ? void 0 : _c.notes) === null || _d === void 0 ? void 0 : _d.length)
85
+ ? html `<div class="notes">
86
+ ${this.options.notes.map(n => html `<div class=${n.level === 'error' ? 'error' : 'note'}>${n.text}</div>`)}
87
+ </div>`
88
+ : nothing}
89
+ ${this._renderPreset()} ${this.draft ? this._renderDraft(issues) : nothing}
90
+ <div class="actions">
91
+ ${canUseDefault ? html `<button data-action="use-default" @click=${this._useDefault}>${t('use-default', 'Use default')}</button>` : nothing}
92
+ <button data-action="discard" ?disabled=${!this.dirty} @click=${() => this.reset()}>${t('discard', 'Discard')}</button>
93
+ <button data-action="apply" ?disabled=${!this.dirty || !this.draft || issues.length > 0} @click=${this._apply}>${t('apply', 'Apply')}</button>
94
+ </div>
95
+ `;
96
+ }
97
+ _renderPreset() {
98
+ var _a, _b, _c, _d;
99
+ const presets = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.presets) !== null && _b !== void 0 ? _b : [];
100
+ if (!presets.length)
101
+ return nothing;
102
+ const preset = (_c = presets.find(p => p.key === this.presetKey)) !== null && _c !== void 0 ? _c : presets[0];
103
+ const built = applyPreset(preset, this.presetSize, this.presetParams);
104
+ const numberRow = (label, value, onChange, key) => html `
105
+ <div class="row">
106
+ <label>${label}</label>
107
+ <input type="number" data-preset=${key} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e) => onChange(num(e))} />
108
+ </div>
109
+ `;
110
+ return html `
111
+ <h4>${t('preset', 'Preset')}</h4>
112
+ <div class="row">
113
+ <label>${t('preset-kind', 'Kind')}</label>
114
+ <select data-preset="kind" @change=${(e) => (this.presetKey = e.target.value)}>
115
+ ${presets.map(p => html `<option value=${p.key} ?selected=${p.key === preset.key}>${p.label}</option>`)}
116
+ </select>
117
+ </div>
118
+ ${numberRow(t('width', 'Width (X)'), this.presetSize.width, n => (this.presetSize = { ...this.presetSize, width: n }), 'width')}
119
+ ${numberRow(t('depth', 'Depth (Z)'), this.presetSize.depth, n => (this.presetSize = { ...this.presetSize, depth: n }), 'depth')}
120
+ ${numberRow(t('slot-height', 'Slot height'), this.presetSize.slotHeight, n => (this.presetSize = { ...this.presetSize, slotHeight: n }), 'slotHeight')}
121
+ ${((_d = preset.params) !== null && _d !== void 0 ? _d : []).map(p => numberRow(p.label, this.presetParams[p.key], n => (this.presetParams = { ...this.presetParams, [p.key]: n }), p.key))}
122
+ <div class="note">${t('preset-note', 'Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.')}</div>
123
+ <div class="actions">
124
+ <button data-action="start-preset" ?disabled=${!built.layout} @click=${() => built.layout && this._startFrom(built.layout)}>
125
+ ${t('start-from-preset', 'Start from preset')}
126
+ </button>
127
+ </div>
128
+ `;
129
+ }
130
+ /** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(층·자리 밖의 키)는 가져온다 */
131
+ _startFrom(layout) {
132
+ var _a;
133
+ const { mount: _m, tiers: _t, ...extra } = ((_a = this.draft) !== null && _a !== void 0 ? _a : {});
134
+ this._edit({ ...extra, ...structuredClone(layout) });
135
+ }
136
+ _renderDraft(issues) {
137
+ var _a, _b, _c, _d, _e, _f;
138
+ const draft = this.draft;
139
+ const tier = (_b = (_a = draft.tiers) === null || _a === void 0 ? void 0 : _a.find(x => x.tierNo === this.tier)) !== null && _b !== void 0 ? _b : (_c = draft.tiers) === null || _c === void 0 ? void 0 : _c[0];
140
+ const issueList = issues.length ? html `<div class="error" data-issues>${issues.map(i => html `<div>· ${this._text(i)}</div>`)}</div>` : nothing;
141
+ // 층이 없는 값(잘못된 저장값)은 편집할 층이 없으므로 문제만 보인다. 프리셋으로 다시 시작한다
142
+ if (!tier)
143
+ return issueList;
144
+ return html `
145
+ <h4>${t('tiers', 'Tiers')}</h4>
146
+ <div class="chips">
147
+ ${draft.tiers.map(x => html `<button data-tier=${x.tierNo} ?active=${x.tierNo === tier.tierNo} @click=${() => (this.tier = x.tierNo)}>${t('tier-n', 'Tier {n}', { n: x.tierNo })}</button>`)}
148
+ </div>
149
+ <div class="row">
150
+ <label>${t('copy-elevation', 'New tier elevation')}</label>
151
+ <input type="number" data-field="copy-elevation" .value=${Number.isFinite(this.copyElevation) ? String(this.copyElevation) : ''} @change=${(e) => (this.copyElevation = num(e))} />
152
+ </div>
153
+ <div class="actions">
154
+ <button data-action="copy-tier" ?disabled=${!Number.isFinite(this.copyElevation)} @click=${() => this._edit(copyTier(draft, tier.tierNo, this.copyElevation))}>
155
+ ${t('copy-tier', 'Copy tier')}
156
+ </button>
157
+ <button data-action="remove-tier" ?disabled=${draft.tiers.length <= 1} @click=${() => this._edit(removeTier(draft, tier.tierNo))}>${t('remove-tier', 'Remove tier')}</button>
158
+ </div>
159
+ ${this._renderTier(draft, tier.tierNo)} ${(_f = (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.renderExtra) === null || _e === void 0 ? void 0 : _e.call(_d, draft, next => this._edit(next))) !== null && _f !== void 0 ? _f : nothing}
160
+ <h4>${t('preview', 'Preview (top view, tier {n})', { n: tier.tierNo })}</h4>
161
+ ${this._renderPreview(draft, tier.tierNo)} ${issueList}
162
+ `;
163
+ }
164
+ _renderTier(draft, tierNo) {
165
+ var _a, _b, _c, _d, _e, _f, _g, _h;
166
+ const tier = draft.tiers.find(x => x.tierNo === tierNo);
167
+ const l = tier.layout;
168
+ const patternInput = (k, label, value, parse = num) => html `
169
+ <div class="row">
170
+ <label>${label}</label>
171
+ <input type="number" data-field=${k} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e) => this._edit(updatePattern(draft, tierNo, { [k]: parse(e) }))} />
172
+ </div>
173
+ `;
174
+ const select = (field, label, value, options, onChange) => html `
175
+ <div class="row">
176
+ <label>${label}</label>
177
+ <select data-field=${field} @change=${(e) => onChange(e.target.value)}>
178
+ ${options.map(o => html `<option value=${o} ?selected=${o === value}>${o}</option>`)}
179
+ </select>
180
+ </div>
181
+ `;
182
+ return html `
183
+ <div class="row">
184
+ <label>${t('elevation', 'Elevation')}</label>
185
+ <input type="number" data-field="elevation" .value=${String(tier.elevation)} @change=${(e) => this._edit(updateTier(draft, tierNo, { elevation: num(e) }))} />
186
+ </div>
187
+ ${select('pattern', t('pattern', 'Pattern'), l.pattern, ['SINGLE', 'ROW', 'GRID', 'CUSTOM'], v => this._edit(setTierPattern(draft, tierNo, v)))}
188
+ ${l.pattern === 'ROW'
189
+ ? html `
190
+ ${patternInput('count', t('count', 'Slots'), l.count)} ${patternInput('pitch', t('pitch', 'Pitch'), l.pitch)}
191
+ ${select('axis', t('axis', 'Axis'), l.axis, ['X', 'Z'], v => this._edit(updatePattern(draft, tierNo, { axis: v })))}
192
+ ${select('origin', t('origin', 'Slot 1 at'), (_a = l.origin) !== null && _a !== void 0 ? _a : 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: v })))}
193
+ `
194
+ : nothing}
195
+ ${l.pattern === 'GRID'
196
+ ? html `
197
+ ${patternInput('rows', t('rows', 'Rows (Z)'), l.rows)} ${patternInput('cols', t('cols', 'Columns (X)'), l.cols)}
198
+ ${patternInput('pitch', t('pitch-x', 'Pitch X'), (_b = l.pitch) === null || _b === void 0 ? void 0 : _b.x, e => ({ ...l.pitch, x: num(e) }))}
199
+ ${patternInput('pitch', t('pitch-z', 'Pitch Z'), (_c = l.pitch) === null || _c === void 0 ? void 0 : _c.z, e => ({ ...l.pitch, z: num(e) }))}
200
+ ${select('order', t('order', 'Numbering'), (_d = l.order) !== null && _d !== void 0 ? _d : 'ROW_MAJOR', ['ROW_MAJOR', 'COL_MAJOR', 'SERPENTINE_ROW', 'SERPENTINE_COL'], v => this._edit(updatePattern(draft, tierNo, { order: v })))}
201
+ ${select('origin-x', t('origin-x', 'Slot 1 at X'), (_f = (_e = l.origin) === null || _e === void 0 ? void 0 : _e.x) !== null && _f !== void 0 ? _f : 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: { z: 'MIN', ...l.origin, x: v } })))}
202
+ ${select('origin-z', t('origin-z', 'Slot 1 at Z'), (_h = (_g = l.origin) === null || _g === void 0 ? void 0 : _g.z) !== null && _h !== void 0 ? _h : 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: { x: 'MIN', ...l.origin, z: v } })))}
203
+ `
204
+ : nothing}
205
+ ${l.pattern === 'CUSTOM' ? this._renderCustom(draft, tierNo) : this._renderSpec(draft, tierNo, l.slot)}
206
+ `;
207
+ }
208
+ /** 받는 타입: 모든 타입(accepts 생략) / 지정 타입만(빈 목록이면 아무것도 받지 않음) */
209
+ _acceptsEditor(spec, onChange) {
210
+ var _a;
211
+ const restricted = Array.isArray(spec.accepts);
212
+ return html `
213
+ <div class="row">
214
+ <label>${t('accepts', 'Accepts')}</label>
215
+ <select data-field="accepts-mode" @change=${(e) => { var _a; return onChange(e.target.value === 'ALL' ? undefined : (_a = spec.accepts) !== null && _a !== void 0 ? _a : []); }}>
216
+ <option value="ALL" ?selected=${!restricted}>${t('accepts-all', 'Any type')}</option>
217
+ <option value="LIST" ?selected=${restricted}>${t('accepts-list', 'Listed types only')}</option>
218
+ </select>
219
+ </div>
220
+ ${restricted
221
+ ? html `<div class="row">
222
+ <label>${t('accepts-types', 'Types (comma)')}</label>
223
+ <input
224
+ data-field="accepts"
225
+ .value=${((_a = spec.accepts) !== null && _a !== void 0 ? _a : []).join(', ')}
226
+ placeholder=${t('accepts-none', 'Empty = accepts nothing')}
227
+ @change=${(e) => onChange(e.target.value
228
+ .split(',')
229
+ .map(s => s.trim())
230
+ .filter(Boolean))}
231
+ />
232
+ </div>`
233
+ : nothing}
234
+ `;
235
+ }
236
+ _renderSpec(draft, tierNo, spec) {
237
+ var _a, _b;
238
+ const edit = (patch) => this._edit(updateSlotSpec(draft, tierNo, patch));
239
+ const allowed = (k, label) => {
240
+ var _a, _b;
241
+ return html `
242
+ <div class="row">
243
+ <label>${label}</label>
244
+ <input type="number" data-field=${`allowed-${k}`} .value=${String((_b = (_a = spec.allowed) === null || _a === void 0 ? void 0 : _a[k]) !== null && _b !== void 0 ? _b : '')} @change=${(e) => edit({ allowed: { ...spec.allowed, [k]: num(e) } })} />
245
+ </div>
246
+ `;
247
+ };
248
+ return html `
249
+ <h4>${t('slot-spec', 'Slot (same for every slot in this tier)')}</h4>
250
+ ${allowed('width', t('allowed-width', 'Allowed width'))} ${allowed('depth', t('allowed-depth', 'Allowed depth'))}
251
+ ${allowed('height', t('allowed-height', 'Allowed height'))}
252
+ <div class="row">
253
+ <label>${t('stack-limit', 'Stack limit')}</label>
254
+ <input type="number" data-field="stack-limit" min="1" step="1" .value=${String((_a = spec.stackLimit) !== null && _a !== void 0 ? _a : 1)} @change=${(e) => edit({ stackLimit: num(e) })} />
255
+ </div>
256
+ <div class="row">
257
+ <label>${t('rotation', 'Rotation (deg)')}</label>
258
+ <input type="number" data-field="rotation" .value=${String((_b = spec.rotation) !== null && _b !== void 0 ? _b : 0)} @change=${(e) => edit({ rotation: num(e) || undefined })} />
259
+ </div>
260
+ ${this._acceptsEditor(spec, accepts => edit({ accepts }))}
261
+ `;
262
+ }
263
+ _renderCustom(draft, tierNo) {
264
+ const tier = draft.tiers.find(x => x.tierNo === tierNo);
265
+ const slots = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo);
266
+ const cell = (slotNo, value, patch) => html `<td><input type="number" .value=${String(value !== null && value !== void 0 ? value : '')} @change=${(e) => this._edit(updateSlotSpec(draft, tierNo, patch(e), slotNo))} /></td>`;
267
+ return html `
268
+ <h4>${t('custom-slots', 'Slots')}</h4>
269
+ <table>
270
+ <tr>
271
+ <th>#</th>
272
+ <th>X</th>
273
+ <th>Z</th>
274
+ <th>${t('w', 'W')}</th>
275
+ <th>${t('d', 'D')}</th>
276
+ <th>${t('h', 'H')}</th>
277
+ <th>${t('stack', 'Stack')}</th>
278
+ <th>${t('rot', 'Rot')}</th>
279
+ <th></th>
280
+ </tr>
281
+ ${slots.map(s => {
282
+ var _a, _b, _c, _d, _e, _f, _g;
283
+ return html `
284
+ <tr>
285
+ <td>${s.slotNo}</td>
286
+ ${cell(s.slotNo, (_a = s.offset) === null || _a === void 0 ? void 0 : _a.x, e => ({ offset: { ...s.offset, x: num(e) } }))}
287
+ ${cell(s.slotNo, (_b = s.offset) === null || _b === void 0 ? void 0 : _b.z, e => ({ offset: { ...s.offset, z: num(e) } }))}
288
+ ${cell(s.slotNo, (_c = s.allowed) === null || _c === void 0 ? void 0 : _c.width, e => ({ allowed: { ...s.allowed, width: num(e) } }))}
289
+ ${cell(s.slotNo, (_d = s.allowed) === null || _d === void 0 ? void 0 : _d.depth, e => ({ allowed: { ...s.allowed, depth: num(e) } }))}
290
+ ${cell(s.slotNo, (_e = s.allowed) === null || _e === void 0 ? void 0 : _e.height, e => ({ allowed: { ...s.allowed, height: num(e) } }))}
291
+ ${cell(s.slotNo, (_f = s.stackLimit) !== null && _f !== void 0 ? _f : 1, e => ({ stackLimit: num(e) }))} ${cell(s.slotNo, (_g = s.rotation) !== null && _g !== void 0 ? _g : 0, e => ({ rotation: num(e) || undefined }))}
292
+ <td><button ?disabled=${slots.length <= 1} @click=${() => this._edit(removeCustomSlot(draft, tierNo, s.slotNo))}>−</button></td>
293
+ </tr>
294
+ `;
295
+ })}
296
+ </table>
297
+ <div class="actions"><button data-action="add-slot" @click=${() => this._edit(addCustomSlot(draft, tierNo))}>${t('add-slot', 'Add slot')}</button></div>
298
+ `;
299
+ }
300
+ /** 위에서 본 자리 배치. 오른쪽이 +X, 위쪽이 −Z */
301
+ _renderPreview(draft, tierNo) {
302
+ const slots = previewTier(draft, tierNo);
303
+ if (!slots.length)
304
+ return html `<div class="note" data-preview="none">${t('preview-none', 'No slots to show with the current values.')}</div>`;
305
+ const minX = Math.min(...slots.map(s => s.x - s.width / 2));
306
+ const maxX = Math.max(...slots.map(s => s.x + s.width / 2));
307
+ const minZ = Math.min(...slots.map(s => s.z - s.depth / 2));
308
+ const maxZ = Math.max(...slots.map(s => s.z + s.depth / 2));
309
+ const w = Math.max(maxX - minX, 1);
310
+ const h = Math.max(maxZ - minZ, 1);
311
+ const pad = Math.max(w, h) * 0.08;
312
+ const font = Math.max(w, h) * 0.07;
313
+ return html `
314
+ <svg data-preview viewBox="${minX - pad} ${minZ - pad} ${w + pad * 2} ${h + pad * 2}" preserveAspectRatio="xMidYMid meet">
315
+ ${slots.map(s => svg `
316
+ <rect data-slot=${s.slotNo} x=${s.x - s.width / 2} y=${s.z - s.depth / 2} width=${s.width} height=${s.depth}
317
+ fill="rgba(25,118,210,0.08)" stroke="rgba(25,118,210,0.8)" stroke-width=${Math.max(w, h) * 0.006} />
318
+ <text x=${s.x} y=${s.z} font-size=${font} text-anchor="middle" dominant-baseline="central" fill="#0d47a1">${s.slotNo}</text>
319
+ `)}
320
+ </svg>
321
+ <div class="note">${t('preview-axes', 'Right = +X, up = −Z')}</div>
322
+ `;
323
+ }
324
+ };
325
+ OxSlotLayoutEditor.styles = css `
326
+ :host {
327
+ display: flex;
328
+ flex-direction: column;
329
+ gap: 6px;
330
+ font-size: 12px;
331
+ }
332
+ .row {
333
+ display: grid;
334
+ grid-template-columns: 40% 1fr;
335
+ align-items: center;
336
+ gap: 4px;
337
+ }
338
+ .notes {
339
+ padding: 4px 6px;
340
+ border-radius: 4px;
341
+ background: rgba(0, 0, 0, 0.04);
342
+ }
343
+ .error {
344
+ color: #c62828;
345
+ }
346
+ .note {
347
+ color: rgba(0, 0, 0, 0.55);
348
+ }
349
+ .chips {
350
+ display: flex;
351
+ flex-wrap: wrap;
352
+ gap: 4px;
353
+ }
354
+ .chips button[active] {
355
+ border-color: var(--md-sys-color-primary, #1976d2);
356
+ color: var(--md-sys-color-primary, #1976d2);
357
+ font-weight: bold;
358
+ }
359
+ button {
360
+ font-size: 12px;
361
+ padding: 2px 8px;
362
+ border: 1px solid rgba(0, 0, 0, 0.2);
363
+ border-radius: 4px;
364
+ background: #fff;
365
+ cursor: pointer;
366
+ }
367
+ button[disabled] {
368
+ opacity: 0.4;
369
+ cursor: default;
370
+ }
371
+ input,
372
+ select {
373
+ font-size: 12px;
374
+ min-width: 0;
375
+ }
376
+ table {
377
+ border-collapse: collapse;
378
+ width: 100%;
379
+ }
380
+ td,
381
+ th {
382
+ border-bottom: 1px solid rgba(0, 0, 0, 0.08);
383
+ padding: 1px;
384
+ }
385
+ td input {
386
+ width: 100%;
387
+ box-sizing: border-box;
388
+ }
389
+ h4 {
390
+ margin: 6px 0 2px;
391
+ font-size: 12px;
392
+ }
393
+ svg {
394
+ width: 100%;
395
+ height: 160px;
396
+ background: rgba(0, 0, 0, 0.02);
397
+ border: 1px solid rgba(0, 0, 0, 0.08);
398
+ }
399
+ .actions {
400
+ display: flex;
401
+ gap: 4px;
402
+ justify-content: flex-end;
403
+ }
404
+ `;
405
+ __decorate([
406
+ property({ attribute: false })
407
+ ], OxSlotLayoutEditor.prototype, "value", void 0);
408
+ __decorate([
409
+ property({ attribute: false })
410
+ ], OxSlotLayoutEditor.prototype, "options", void 0);
411
+ __decorate([
412
+ state()
413
+ ], OxSlotLayoutEditor.prototype, "draft", void 0);
414
+ __decorate([
415
+ state()
416
+ ], OxSlotLayoutEditor.prototype, "tier", void 0);
417
+ __decorate([
418
+ state()
419
+ ], OxSlotLayoutEditor.prototype, "dirty", void 0);
420
+ __decorate([
421
+ state()
422
+ ], OxSlotLayoutEditor.prototype, "presetKey", void 0);
423
+ __decorate([
424
+ state()
425
+ ], OxSlotLayoutEditor.prototype, "presetSize", void 0);
426
+ __decorate([
427
+ state()
428
+ ], OxSlotLayoutEditor.prototype, "presetParams", void 0);
429
+ __decorate([
430
+ state()
431
+ ], OxSlotLayoutEditor.prototype, "copyElevation", void 0);
432
+ OxSlotLayoutEditor = __decorate([
433
+ customElement('ox-slot-layout-editor')
434
+ ], OxSlotLayoutEditor);
435
+ export { OxSlotLayoutEditor };
436
+ //# sourceMappingURL=ox-slot-layout-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ox-slot-layout-editor.js","sourceRoot":"","sources":["../../src/ox-slot-layout-editor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 자리 레이아웃 편집기. 홀더의 층·자리 구성을 고치고, 검증하고, 미리 보고, [적용]으로 알린다.\n *\n * 컴포넌트 상태를 직접 바꾸지 않는다. 호스트에 두 가지만 알린다:\n * change — 적용한 레이아웃 (this.value)\n * slot-layout-use-default — 개별 설정을 지우고 기본값을 쓰겠다는 의도 (값을 비우는 편집과 다르다)\n * 기본값을 쓰게 된 뒤 보일 값은 호스트가 options.effective 로 다시 준다.\n */\n\nimport { css, html, nothing, LitElement, svg } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { persistenceIssues, validateSlotLayout, type GridOrder, type SlotLayout, type SlotMount, type SlotPattern, type SlotSpec } from '@hatiolab/things-scene'\n\nimport {\n addCustomSlot,\n applyPreset,\n copyTier,\n previewTier,\n removeCustomSlot,\n removeTier,\n setTierPattern,\n updatePattern,\n updateSlotSpec,\n updateTier,\n type PresetSize,\n type SlotLayoutPreset\n} from './draft.js'\nimport { issueText as defaultIssueText, t, type EditorIssue } from './issue-text.js'\n\nexport interface SlotLayoutEditorNote {\n text: string\n level?: 'info' | 'error'\n}\n\nexport interface SlotLayoutEditorOptions<L extends SlotLayout = SlotLayout> {\n /** 개별 설정이 없을 때 보이는 값 (호스트가 고른 기본값). 편집 시작점이 된다 */\n effective?: L\n /** [기본값 사용]을 보일지. 기본은 개별 설정(value)이 있을 때 */\n canUseDefault?: boolean\n /** 호스트가 주는 프리셋 */\n presets?: SlotLayoutPreset<L>[]\n /** 프리셋 크기 초기값 */\n presetSize?: Partial<PresetSize>\n /** 상태 문구 (값의 출처, 단위 상태 등) */\n notes?: SlotLayoutEditorNote[]\n /** 프리셋 없이 새로 시작할 때의 mount. 기본 ON */\n mount?: SlotMount\n /** 호스트 고유 검증 (경로는 레이아웃 기준) */\n validate?: (layout: L) => EditorIssue[]\n /** 문제 문구. undefined 를 돌려주면 기본 문구 */\n issueText?: (issue: EditorIssue) => string | undefined\n /** 레이아웃에 덧붙인 호스트 키를 고치는 구역 (예: 실시간 주소 읽기). edit 로 새 레이아웃을 넘긴다 */\n renderExtra?: (draft: L, edit: (next: L) => void) => unknown\n}\n\nconst num = (e: Event) => {\n const n = (e.target as HTMLInputElement).valueAsNumber\n return Number.isFinite(n) ? n : NaN\n}\n\n@customElement('ox-slot-layout-editor')\nexport class OxSlotLayoutEditor extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n gap: 6px;\n font-size: 12px;\n }\n .row {\n display: grid;\n grid-template-columns: 40% 1fr;\n align-items: center;\n gap: 4px;\n }\n .notes {\n padding: 4px 6px;\n border-radius: 4px;\n background: rgba(0, 0, 0, 0.04);\n }\n .error {\n color: #c62828;\n }\n .note {\n color: rgba(0, 0, 0, 0.55);\n }\n .chips {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n }\n .chips button[active] {\n border-color: var(--md-sys-color-primary, #1976d2);\n color: var(--md-sys-color-primary, #1976d2);\n font-weight: bold;\n }\n button {\n font-size: 12px;\n padding: 2px 8px;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 4px;\n background: #fff;\n cursor: pointer;\n }\n button[disabled] {\n opacity: 0.4;\n cursor: default;\n }\n input,\n select {\n font-size: 12px;\n min-width: 0;\n }\n table {\n border-collapse: collapse;\n width: 100%;\n }\n td,\n th {\n border-bottom: 1px solid rgba(0, 0, 0, 0.08);\n padding: 1px;\n }\n td input {\n width: 100%;\n box-sizing: border-box;\n }\n h4 {\n margin: 6px 0 2px;\n font-size: 12px;\n }\n svg {\n width: 100%;\n height: 160px;\n background: rgba(0, 0, 0, 0.02);\n border: 1px solid rgba(0, 0, 0, 0.08);\n }\n .actions {\n display: flex;\n gap: 4px;\n justify-content: flex-end;\n }\n `\n\n /** 개별 설정. 없으면 undefined */\n @property({ attribute: false }) value: SlotLayout | undefined\n @property({ attribute: false }) options: SlotLayoutEditorOptions = {}\n\n @state() draft: SlotLayout | undefined\n @state() tier = 1\n @state() dirty = false\n @state() presetKey = ''\n @state() presetSize: Partial<PresetSize> = {}\n @state() presetParams: Record<string, number> = {}\n @state() copyElevation = NaN\n\n willUpdate(changed: Map<string, unknown>) {\n // 호스트가 렌더마다 새 options 를 넘겨도 편집 중인 값을 잃지 않도록, 값이나 호스트 기본값이 바뀔 때만 다시 시작한다\n const before = changed.get('options') as SlotLayoutEditorOptions | undefined\n if (changed.has('value') || (changed.has('options') && before?.effective !== this.options?.effective)) this.reset()\n else if (changed.has('options') && !this.options?.presets?.some(p => p.key === this.presetKey))\n this.presetKey = this.options?.presets?.[0]?.key ?? ''\n }\n\n /** 편집 중인 값을 버리고 개별 설정(없으면 호스트 기본값)에서 다시 시작한다 */\n reset() {\n const start = this.value ?? this.options?.effective\n this.draft = start ? structuredClone(start) : undefined\n this.tier = 1\n this.dirty = false\n this.presetSize = { ...(this.options?.presetSize ?? {}) }\n if (!this.options?.presets?.some(p => p.key === this.presetKey)) this.presetKey = this.options?.presets?.[0]?.key ?? ''\n }\n\n get issues(): EditorIssue[] {\n if (!this.draft) return []\n const invalid: EditorIssue[] = [...validateSlotLayout(this.draft), ...(this.options?.validate?.(this.draft) ?? [])]\n // 저장 불가 검사는 검증을 통과한 값에만 보인다 (같은 문제를 두 번 적지 않는다)\n return invalid.length ? invalid : persistenceIssues(this.draft)\n }\n\n private _text(issue: EditorIssue) {\n return this.options?.issueText?.(issue) ?? defaultIssueText(issue)\n }\n\n private _edit(next: SlotLayout) {\n this.draft = next\n this.dirty = true\n if (!next.tiers.some(x => x.tierNo === this.tier)) this.tier = next.tiers[next.tiers.length - 1]?.tierNo ?? 1\n }\n\n private _apply() {\n this.value = structuredClone(this.draft)\n this.dispatchEvent(new Event('change', { bubbles: true, composed: true }))\n }\n\n private _useDefault() {\n this.dispatchEvent(new CustomEvent('slot-layout-use-default', { bubbles: true, composed: true }))\n }\n\n render() {\n const issues = this.issues\n const canUseDefault = this.options?.canUseDefault ?? this.value !== undefined\n return html`\n ${this.options?.notes?.length\n ? html`<div class=\"notes\">\n ${this.options.notes.map(n => html`<div class=${n.level === 'error' ? 'error' : 'note'}>${n.text}</div>`)}\n </div>`\n : nothing}\n ${this._renderPreset()} ${this.draft ? this._renderDraft(issues) : nothing}\n <div class=\"actions\">\n ${canUseDefault ? html`<button data-action=\"use-default\" @click=${this._useDefault}>${t('use-default', 'Use default')}</button>` : nothing}\n <button data-action=\"discard\" ?disabled=${!this.dirty} @click=${() => this.reset()}>${t('discard', 'Discard')}</button>\n <button data-action=\"apply\" ?disabled=${!this.dirty || !this.draft || issues.length > 0} @click=${this._apply}>${t('apply', 'Apply')}</button>\n </div>\n `\n }\n\n private _renderPreset() {\n const presets = this.options?.presets ?? []\n if (!presets.length) return nothing\n const preset = presets.find(p => p.key === this.presetKey) ?? presets[0]\n const built = applyPreset(preset, this.presetSize, this.presetParams)\n const numberRow = (label: string, value: unknown, onChange: (n: number) => void, key: string) => html`\n <div class=\"row\">\n <label>${label}</label>\n <input type=\"number\" data-preset=${key} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e: Event) => onChange(num(e))} />\n </div>\n `\n return html`\n <h4>${t('preset', 'Preset')}</h4>\n <div class=\"row\">\n <label>${t('preset-kind', 'Kind')}</label>\n <select data-preset=\"kind\" @change=${(e: Event) => (this.presetKey = (e.target as HTMLSelectElement).value)}>\n ${presets.map(p => html`<option value=${p.key} ?selected=${p.key === preset.key}>${p.label}</option>`)}\n </select>\n </div>\n ${numberRow(t('width', 'Width (X)'), this.presetSize.width, n => (this.presetSize = { ...this.presetSize, width: n }), 'width')}\n ${numberRow(t('depth', 'Depth (Z)'), this.presetSize.depth, n => (this.presetSize = { ...this.presetSize, depth: n }), 'depth')}\n ${numberRow(t('slot-height', 'Slot height'), this.presetSize.slotHeight, n => (this.presetSize = { ...this.presetSize, slotHeight: n }), 'slotHeight')}\n ${(preset.params ?? []).map(p =>\n numberRow(p.label, this.presetParams[p.key], n => (this.presetParams = { ...this.presetParams, [p.key]: n }), p.key)\n )}\n <div class=\"note\">${t('preset-note', 'Pitch and slot size are split evenly as starting values. Confirm them against the real equipment.')}</div>\n <div class=\"actions\">\n <button data-action=\"start-preset\" ?disabled=${!built.layout} @click=${() => built.layout && this._startFrom(built.layout)}>\n ${t('start-from-preset', 'Start from preset')}\n </button>\n </div>\n `\n }\n\n /** 프리셋으로 다시 시작한다. 지금 값에 덧붙은 호스트 키(층·자리 밖의 키)는 가져온다 */\n private _startFrom(layout: SlotLayout) {\n const { mount: _m, tiers: _t, ...extra } = (this.draft ?? {}) as Record<string, unknown>\n this._edit({ ...extra, ...structuredClone(layout) } as SlotLayout)\n }\n\n private _renderDraft(issues: EditorIssue[]) {\n const draft = this.draft!\n const tier = draft.tiers?.find(x => x.tierNo === this.tier) ?? draft.tiers?.[0]\n const issueList = issues.length ? html`<div class=\"error\" data-issues>${issues.map(i => html`<div>· ${this._text(i)}</div>`)}</div>` : nothing\n // 층이 없는 값(잘못된 저장값)은 편집할 층이 없으므로 문제만 보인다. 프리셋으로 다시 시작한다\n if (!tier) return issueList\n return html`\n <h4>${t('tiers', 'Tiers')}</h4>\n <div class=\"chips\">\n ${draft.tiers.map(\n x => html`<button data-tier=${x.tierNo} ?active=${x.tierNo === tier.tierNo} @click=${() => (this.tier = x.tierNo)}>${t('tier-n', 'Tier {n}', { n: x.tierNo })}</button>`\n )}\n </div>\n <div class=\"row\">\n <label>${t('copy-elevation', 'New tier elevation')}</label>\n <input type=\"number\" data-field=\"copy-elevation\" .value=${Number.isFinite(this.copyElevation) ? String(this.copyElevation) : ''} @change=${(e: Event) => (this.copyElevation = num(e))} />\n </div>\n <div class=\"actions\">\n <button data-action=\"copy-tier\" ?disabled=${!Number.isFinite(this.copyElevation)} @click=${() => this._edit(copyTier(draft, tier.tierNo, this.copyElevation))}>\n ${t('copy-tier', 'Copy tier')}\n </button>\n <button data-action=\"remove-tier\" ?disabled=${draft.tiers.length <= 1} @click=${() => this._edit(removeTier(draft, tier.tierNo))}>${t('remove-tier', 'Remove tier')}</button>\n </div>\n ${this._renderTier(draft, tier.tierNo)} ${this.options?.renderExtra?.(draft, next => this._edit(next)) ?? nothing}\n <h4>${t('preview', 'Preview (top view, tier {n})', { n: tier.tierNo })}</h4>\n ${this._renderPreview(draft, tier.tierNo)} ${issueList}\n `\n }\n\n private _renderTier(draft: SlotLayout, tierNo: number) {\n const tier = draft.tiers.find(x => x.tierNo === tierNo)!\n const l = tier.layout as SlotPattern & Record<string, any>\n const patternInput = (k: string, label: string, value: unknown, parse: (e: Event) => unknown = num) => html`\n <div class=\"row\">\n <label>${label}</label>\n <input type=\"number\" data-field=${k} .value=${value === undefined || Number.isNaN(value) ? '' : String(value)} @change=${(e: Event) => this._edit(updatePattern(draft, tierNo, { [k]: parse(e) }))} />\n </div>\n `\n const select = (field: string, label: string, value: string, options: string[], onChange: (v: string) => void) => html`\n <div class=\"row\">\n <label>${label}</label>\n <select data-field=${field} @change=${(e: Event) => onChange((e.target as HTMLSelectElement).value)}>\n ${options.map(o => html`<option value=${o} ?selected=${o === value}>${o}</option>`)}\n </select>\n </div>\n `\n return html`\n <div class=\"row\">\n <label>${t('elevation', 'Elevation')}</label>\n <input type=\"number\" data-field=\"elevation\" .value=${String(tier.elevation)} @change=${(e: Event) => this._edit(updateTier(draft, tierNo, { elevation: num(e) }))} />\n </div>\n ${select('pattern', t('pattern', 'Pattern'), l.pattern, ['SINGLE', 'ROW', 'GRID', 'CUSTOM'], v => this._edit(setTierPattern(draft, tierNo, v as SlotPattern['pattern'])))}\n ${l.pattern === 'ROW'\n ? html`\n ${patternInput('count', t('count', 'Slots'), l.count)} ${patternInput('pitch', t('pitch', 'Pitch'), l.pitch)}\n ${select('axis', t('axis', 'Axis'), l.axis, ['X', 'Z'], v => this._edit(updatePattern(draft, tierNo, { axis: v as 'X' | 'Z' })))}\n ${select('origin', t('origin', 'Slot 1 at'), l.origin ?? 'MIN', ['MIN', 'MAX'], v => this._edit(updatePattern(draft, tierNo, { origin: v as 'MIN' | 'MAX' })))}\n `\n : nothing}\n ${l.pattern === 'GRID'\n ? html`\n ${patternInput('rows', t('rows', 'Rows (Z)'), l.rows)} ${patternInput('cols', t('cols', 'Columns (X)'), l.cols)}\n ${patternInput('pitch', t('pitch-x', 'Pitch X'), l.pitch?.x, e => ({ ...l.pitch, x: num(e) }))}\n ${patternInput('pitch', t('pitch-z', 'Pitch Z'), l.pitch?.z, e => ({ ...l.pitch, z: num(e) }))}\n ${select('order', t('order', 'Numbering'), l.order ?? 'ROW_MAJOR', ['ROW_MAJOR', 'COL_MAJOR', 'SERPENTINE_ROW', 'SERPENTINE_COL'], v =>\n this._edit(updatePattern(draft, tierNo, { order: v as GridOrder }))\n )}\n ${select('origin-x', t('origin-x', 'Slot 1 at X'), l.origin?.x ?? 'MIN', ['MIN', 'MAX'], v =>\n this._edit(updatePattern(draft, tierNo, { origin: { z: 'MIN', ...l.origin, x: v as 'MIN' | 'MAX' } }))\n )}\n ${select('origin-z', t('origin-z', 'Slot 1 at Z'), l.origin?.z ?? 'MIN', ['MIN', 'MAX'], v =>\n this._edit(updatePattern(draft, tierNo, { origin: { x: 'MIN', ...l.origin, z: v as 'MIN' | 'MAX' } }))\n )}\n `\n : nothing}\n ${l.pattern === 'CUSTOM' ? this._renderCustom(draft, tierNo) : this._renderSpec(draft, tierNo, l.slot)}\n `\n }\n\n /** 받는 타입: 모든 타입(accepts 생략) / 지정 타입만(빈 목록이면 아무것도 받지 않음) */\n private _acceptsEditor(spec: SlotSpec, onChange: (accepts: string[] | undefined) => void) {\n const restricted = Array.isArray(spec.accepts)\n return html`\n <div class=\"row\">\n <label>${t('accepts', 'Accepts')}</label>\n <select data-field=\"accepts-mode\" @change=${(e: Event) => onChange((e.target as HTMLSelectElement).value === 'ALL' ? undefined : spec.accepts ?? [])}>\n <option value=\"ALL\" ?selected=${!restricted}>${t('accepts-all', 'Any type')}</option>\n <option value=\"LIST\" ?selected=${restricted}>${t('accepts-list', 'Listed types only')}</option>\n </select>\n </div>\n ${restricted\n ? html`<div class=\"row\">\n <label>${t('accepts-types', 'Types (comma)')}</label>\n <input\n data-field=\"accepts\"\n .value=${(spec.accepts ?? []).join(', ')}\n placeholder=${t('accepts-none', 'Empty = accepts nothing')}\n @change=${(e: Event) =>\n onChange(\n (e.target as HTMLInputElement).value\n .split(',')\n .map(s => s.trim())\n .filter(Boolean)\n )}\n />\n </div>`\n : nothing}\n `\n }\n\n private _renderSpec(draft: SlotLayout, tierNo: number, spec: SlotSpec) {\n const edit = (patch: Partial<SlotSpec>) => this._edit(updateSlotSpec(draft, tierNo, patch))\n const allowed = (k: 'width' | 'depth' | 'height', label: string) => html`\n <div class=\"row\">\n <label>${label}</label>\n <input type=\"number\" data-field=${`allowed-${k}`} .value=${String(spec.allowed?.[k] ?? '')} @change=${(e: Event) => edit({ allowed: { ...spec.allowed, [k]: num(e) } })} />\n </div>\n `\n return html`\n <h4>${t('slot-spec', 'Slot (same for every slot in this tier)')}</h4>\n ${allowed('width', t('allowed-width', 'Allowed width'))} ${allowed('depth', t('allowed-depth', 'Allowed depth'))}\n ${allowed('height', t('allowed-height', 'Allowed height'))}\n <div class=\"row\">\n <label>${t('stack-limit', 'Stack limit')}</label>\n <input type=\"number\" data-field=\"stack-limit\" min=\"1\" step=\"1\" .value=${String(spec.stackLimit ?? 1)} @change=${(e: Event) => edit({ stackLimit: num(e) })} />\n </div>\n <div class=\"row\">\n <label>${t('rotation', 'Rotation (deg)')}</label>\n <input type=\"number\" data-field=\"rotation\" .value=${String(spec.rotation ?? 0)} @change=${(e: Event) => edit({ rotation: num(e) || undefined })} />\n </div>\n ${this._acceptsEditor(spec, accepts => edit({ accepts }))}\n `\n }\n\n private _renderCustom(draft: SlotLayout, tierNo: number) {\n const tier = draft.tiers.find(x => x.tierNo === tierNo)!\n const slots = [...(tier.layout as Extract<SlotPattern, { pattern: 'CUSTOM' }>).slots].sort((a, b) => a.slotNo - b.slotNo)\n const cell = (slotNo: number, value: unknown, patch: (e: Event) => Record<string, unknown>) =>\n html`<td><input type=\"number\" .value=${String(value ?? '')} @change=${(e: Event) => this._edit(updateSlotSpec(draft, tierNo, patch(e), slotNo))} /></td>`\n return html`\n <h4>${t('custom-slots', 'Slots')}</h4>\n <table>\n <tr>\n <th>#</th>\n <th>X</th>\n <th>Z</th>\n <th>${t('w', 'W')}</th>\n <th>${t('d', 'D')}</th>\n <th>${t('h', 'H')}</th>\n <th>${t('stack', 'Stack')}</th>\n <th>${t('rot', 'Rot')}</th>\n <th></th>\n </tr>\n ${slots.map(\n s => html`\n <tr>\n <td>${s.slotNo}</td>\n ${cell(s.slotNo, s.offset?.x, e => ({ offset: { ...s.offset, x: num(e) } }))}\n ${cell(s.slotNo, s.offset?.z, e => ({ offset: { ...s.offset, z: num(e) } }))}\n ${cell(s.slotNo, s.allowed?.width, e => ({ allowed: { ...s.allowed, width: num(e) } }))}\n ${cell(s.slotNo, s.allowed?.depth, e => ({ allowed: { ...s.allowed, depth: num(e) } }))}\n ${cell(s.slotNo, s.allowed?.height, e => ({ allowed: { ...s.allowed, height: num(e) } }))}\n ${cell(s.slotNo, s.stackLimit ?? 1, e => ({ stackLimit: num(e) }))} ${cell(s.slotNo, s.rotation ?? 0, e => ({ rotation: num(e) || undefined }))}\n <td><button ?disabled=${slots.length <= 1} @click=${() => this._edit(removeCustomSlot(draft, tierNo, s.slotNo))}>−</button></td>\n </tr>\n `\n )}\n </table>\n <div class=\"actions\"><button data-action=\"add-slot\" @click=${() => this._edit(addCustomSlot(draft, tierNo))}>${t('add-slot', 'Add slot')}</button></div>\n `\n }\n\n /** 위에서 본 자리 배치. 오른쪽이 +X, 위쪽이 −Z */\n private _renderPreview(draft: SlotLayout, tierNo: number) {\n const slots = previewTier(draft, tierNo)\n if (!slots.length) return html`<div class=\"note\" data-preview=\"none\">${t('preview-none', 'No slots to show with the current values.')}</div>`\n const minX = Math.min(...slots.map(s => s.x - s.width / 2))\n const maxX = Math.max(...slots.map(s => s.x + s.width / 2))\n const minZ = Math.min(...slots.map(s => s.z - s.depth / 2))\n const maxZ = Math.max(...slots.map(s => s.z + s.depth / 2))\n const w = Math.max(maxX - minX, 1)\n const h = Math.max(maxZ - minZ, 1)\n const pad = Math.max(w, h) * 0.08\n const font = Math.max(w, h) * 0.07\n return html`\n <svg data-preview viewBox=\"${minX - pad} ${minZ - pad} ${w + pad * 2} ${h + pad * 2}\" preserveAspectRatio=\"xMidYMid meet\">\n ${slots.map(\n s => svg`\n <rect data-slot=${s.slotNo} x=${s.x - s.width / 2} y=${s.z - s.depth / 2} width=${s.width} height=${s.depth}\n fill=\"rgba(25,118,210,0.08)\" stroke=\"rgba(25,118,210,0.8)\" stroke-width=${Math.max(w, h) * 0.006} />\n <text x=${s.x} y=${s.z} font-size=${font} text-anchor=\"middle\" dominant-baseline=\"central\" fill=\"#0d47a1\">${s.slotNo}</text>\n `\n )}\n </svg>\n <div class=\"note\">${t('preview-axes', 'Right = +X, up = −Z')}</div>\n `\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ox-slot-layout-editor': OxSlotLayoutEditor\n }\n}\n"]}