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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
+
6
+ ### [10.29.15](https://github.com/hatiolab/operato/compare/v10.29.14...v10.29.15) (2026-10-09)
7
+
8
+
9
+ ### :rocket: New Features
10
+
11
+ * **slot-layout-editor:** tiers stand on anchors and keep their numbers ([59b06bd](https://github.com/hatiolab/operato/commit/59b06bd8be506008cec6dadc702656fe46af675b))
12
+
13
+
14
+
15
+ ### [10.29.14](https://github.com/hatiolab/operato/compare/v10.29.13...v10.29.14) (2026-10-07)
16
+
17
+
18
+ ### :rocket: New Features
19
+
20
+ * **slot-layout-editor:** an editor for a holder's tiers and slots ([b7a13a5](https://github.com/hatiolab/operato/commit/b7a13a50ebee71a8dfd3ca0686db410dea3fb0d8))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ox-property-panel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.
5
+ * 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.
6
+ */
7
+ import { type GridOrder, type LayoutIssue, type SlotLayout, type SlotLayoutTier, type SlotPattern, type SlotSpec } from '@hatiolab/things-scene';
8
+ /** 프리셋이 받는 크기. slotHeight 는 자리 허용 높이 */
9
+ export interface PresetSize {
10
+ width: number;
11
+ depth: number;
12
+ slotHeight: number;
13
+ }
14
+ /** 프리셋이 크기 외에 더 받는 값 */
15
+ export interface PresetParam {
16
+ key: string;
17
+ label: string;
18
+ /** 양의 정수만 받는다 */
19
+ integer?: boolean;
20
+ }
21
+ /** 호스트가 넘기는 프리셋 */
22
+ export interface SlotLayoutPreset<L extends SlotLayout = SlotLayout> {
23
+ key: string;
24
+ label: string;
25
+ params?: PresetParam[];
26
+ build(size: PresetSize, params: Record<string, number>): L;
27
+ }
28
+ /** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */
29
+ export declare function applyPreset<L extends SlotLayout>(preset: SlotLayoutPreset<L>, size: Partial<PresetSize>, params?: Record<string, unknown>): {
30
+ layout?: L;
31
+ issues: LayoutIssue[];
32
+ };
33
+ /** Tier 설정을 복사해 새 Tier 로 붙인다. 번호는 지금 가장 큰 번호 + 1, 배치는 placement 로 바꾼다. 점유는 복사하지 않는다 */
34
+ export declare function copyTier<L extends SlotLayout>(layout: L, tierNo: number, placement?: TierPatch): L;
35
+ /** Tier 를 지운다. 남은 Tier 의 번호는 그대로다. 마지막 남은 Tier 는 지우지 않는다 */
36
+ export declare function removeTier<L extends SlotLayout>(layout: L, tierNo: number): L;
37
+ /** Tier 가 선 Anchor, 적재 방식, Anchor 기준 배치 */
38
+ export type TierPatch = Partial<Pick<SlotLayoutTier, 'anchorId' | 'mount' | 'position' | 'rotation'>>;
39
+ export declare function updateTier<L extends SlotLayout>(layout: L, tierNo: number, patch: TierPatch): L;
40
+ /**
41
+ * 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.
42
+ * CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.
43
+ */
44
+ export declare function setTierPattern<L extends SlotLayout>(layout: L, tierNo: number, pattern: SlotPattern['pattern']): L;
45
+ export interface PatternPatch {
46
+ count?: number;
47
+ pitch?: number | {
48
+ x: number;
49
+ z: number;
50
+ };
51
+ axis?: 'X' | 'Z';
52
+ origin?: 'MIN' | 'MAX' | {
53
+ x: 'MIN' | 'MAX';
54
+ z: 'MIN' | 'MAX';
55
+ };
56
+ rows?: number;
57
+ cols?: number;
58
+ order?: GridOrder;
59
+ }
60
+ /** 패턴 값(자리 수·간격·축·번호 규칙)을 고친다. 그 패턴에 없는 키는 무시한다 */
61
+ export declare function updatePattern<L extends SlotLayout>(layout: L, tierNo: number, patch: PatternPatch): L;
62
+ export type SlotSpecPatch = Partial<SlotSpec> & {
63
+ offset?: {
64
+ x: number;
65
+ z: number;
66
+ };
67
+ name?: string;
68
+ };
69
+ /** SINGLE·ROW·GRID 의 공통 자리 규격, CUSTOM 이면 slotNo 자리를 고친다. undefined 값은 키를 지운다 */
70
+ export declare function updateSlotSpec<L extends SlotLayout>(layout: L, tierNo: number, patch: SlotSpecPatch, slotNo?: number): L;
71
+ /** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */
72
+ export declare function addCustomSlot<L extends SlotLayout>(layout: L, tierNo: number): L;
73
+ /** CUSTOM Tier 의 자리를 지운다. 남은 자리의 번호는 그대로다. 마지막 남은 자리는 지우지 않는다 */
74
+ export declare function removeCustomSlot<L extends SlotLayout>(layout: L, tierNo: number, slotNo: number): L;
75
+ /** 바꾼 값을 적용하면 뜻이 바뀌는 주소: 위치가 바뀌는 자리, 없어지는 자리 (자리 id) */
76
+ export interface AddressImpact {
77
+ moved: string[];
78
+ gone: string[];
79
+ }
80
+ /**
81
+ * 앞 레이아웃의 주소마다 새 레이아웃에서 같은 주소가 같은 Anchor 의 같은 위치인지 본다. 놓인 Carrier 는 주소로
82
+ * 다시 읽히므로 (빈 자리로 옮겨 채우지 않는다) 여기 나온 주소의 Carrier 는 다른 곳에 그려지거나 예외가 된다.
83
+ * 어느 쪽이든 쓸 수 없는 값이면 빈 결과.
84
+ */
85
+ export declare function addressImpact(before: SlotLayout | undefined, after: SlotLayout | undefined): AddressImpact;
86
+ export interface PreviewSlot {
87
+ slotNo: number;
88
+ name?: string;
89
+ /** 자리 중심 (Tier 로컬). 화면 위가 -z */
90
+ x: number;
91
+ z: number;
92
+ /** 회전한 허용 공간의 외접 폭·깊이 */
93
+ width: number;
94
+ depth: number;
95
+ }
96
+ /** Tier 하나의 자리 위치와 크기 (Tier 로컬). 자리를 만들 수 없는 값이면 빈 목록 */
97
+ export declare function previewTier(layout: SlotLayout | undefined, tierNo: number): PreviewSlot[];
@@ -0,0 +1,235 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.
5
+ * 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.
6
+ */
7
+ import { resolveSlot, rotatedFootprint, slotAddresses, slotCount, slotFrame, slotIdOf, slotNumbers } from '@hatiolab/things-scene';
8
+ // JSON 복사는 NaN·Infinity 를 null 로 바꿔 잘못된 값을 숨기므로 쓰지 않는다
9
+ const clone = (v) => structuredClone(v);
10
+ /** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */
11
+ export function applyPreset(preset, size, params = {}) {
12
+ var _a;
13
+ const issues = [];
14
+ const positive = (v) => typeof v === 'number' && Number.isFinite(v) && v > 0;
15
+ const positiveInt = (v) => Number.isSafeInteger(v) && v > 0;
16
+ for (const k of ['width', 'depth', 'slotHeight'])
17
+ if (!positive(size[k]))
18
+ issues.push({ code: 'POSITIVE_NUMBER_REQUIRED', path: k });
19
+ for (const p of (_a = preset.params) !== null && _a !== void 0 ? _a : [])
20
+ if (!(p.integer ? positiveInt(params[p.key]) : positive(params[p.key])))
21
+ issues.push({ code: p.integer ? 'POSITIVE_INTEGER_REQUIRED' : 'POSITIVE_NUMBER_REQUIRED', path: p.key });
22
+ if (issues.length)
23
+ return { issues };
24
+ return { layout: preset.build(size, params), issues };
25
+ }
26
+ // ── Tier ───────────────────────────────────────────────────────────────────
27
+ // Tier 번호는 홀더 안의 식별 번호다. 지우거나 정렬해도 다시 매기지 않는다 (번호가 바뀌면 놓인 Carrier 의 주소가 바뀐다).
28
+ /** Tier 설정을 복사해 새 Tier 로 붙인다. 번호는 지금 가장 큰 번호 + 1, 배치는 placement 로 바꾼다. 점유는 복사하지 않는다 */
29
+ export function copyTier(layout, tierNo, placement = {}) {
30
+ const next = clone(layout);
31
+ const source = next.tiers.find(t => t.tierNo === tierNo);
32
+ if (!source)
33
+ return next;
34
+ const newNo = Math.max(...next.tiers.map(t => t.tierNo)) + 1;
35
+ next.tiers.push({ ...clone(source), ...clone(placement), tierNo: newNo });
36
+ return next;
37
+ }
38
+ /** Tier 를 지운다. 남은 Tier 의 번호는 그대로다. 마지막 남은 Tier 는 지우지 않는다 */
39
+ export function removeTier(layout, tierNo) {
40
+ const next = clone(layout);
41
+ if (next.tiers.length <= 1)
42
+ return next;
43
+ next.tiers = next.tiers.filter(t => t.tierNo !== tierNo);
44
+ return next;
45
+ }
46
+ export function updateTier(layout, tierNo, patch) {
47
+ const next = clone(layout);
48
+ const tier = next.tiers.find(t => t.tierNo === tierNo);
49
+ if (tier)
50
+ Object.assign(tier, clone(patch));
51
+ return next;
52
+ }
53
+ // ── 패턴 ───────────────────────────────────────────────────────────────────
54
+ const specOf = (l) => {
55
+ if (l.pattern === 'CUSTOM') {
56
+ const first = [...l.slots].sort((a, b) => a.slotNo - b.slotNo)[0];
57
+ const { allowed, accepts, stackLimit, rotation } = first !== null && first !== void 0 ? first : { allowed: { width: 1, depth: 1, height: 1 } };
58
+ return clone({ allowed, accepts, stackLimit, rotation });
59
+ }
60
+ return clone(l.slot);
61
+ };
62
+ /**
63
+ * 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.
64
+ * CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.
65
+ */
66
+ export function setTierPattern(layout, tierNo, pattern) {
67
+ const next = clone(layout);
68
+ const tier = next.tiers.find(t => t.tierNo === tierNo);
69
+ if (!tier || tier.layout.pattern === pattern)
70
+ return next;
71
+ const cur = tier.layout;
72
+ const spec = specOf(cur);
73
+ const count = slotCount(cur);
74
+ switch (pattern) {
75
+ case 'SINGLE':
76
+ tier.layout = { pattern, slot: spec };
77
+ break;
78
+ case 'ROW':
79
+ tier.layout = {
80
+ pattern,
81
+ count: Math.max(count, 1),
82
+ pitch: cur.pattern === 'ROW' ? cur.pitch : cur.pattern === 'GRID' ? cur.pitch.x : spec.allowed.width,
83
+ axis: 'X',
84
+ origin: 'MIN',
85
+ slot: spec
86
+ };
87
+ break;
88
+ case 'GRID':
89
+ tier.layout = {
90
+ pattern,
91
+ rows: cur.pattern === 'GRID' ? cur.rows : 1,
92
+ cols: cur.pattern === 'GRID' ? cur.cols : Math.max(count, 1),
93
+ pitch: { x: cur.pattern === 'ROW' ? cur.pitch : spec.allowed.width, z: spec.allowed.depth },
94
+ order: 'ROW_MAJOR',
95
+ origin: { x: 'MIN', z: 'MIN' },
96
+ slot: spec
97
+ };
98
+ break;
99
+ case 'CUSTOM': {
100
+ const slots = [];
101
+ for (const n of slotNumbers(cur)) {
102
+ const r = resolveSlot(layout, { tier: tierNo, slot: n });
103
+ slots.push({
104
+ slotNo: n,
105
+ offset: { ...r.offset },
106
+ allowed: { ...r.allowed },
107
+ ...(r.accepts ? { accepts: [...r.accepts] } : {}),
108
+ ...(r.stackLimit !== 1 ? { stackLimit: r.stackLimit } : {}),
109
+ ...(r.rotation ? { rotation: r.rotation } : {})
110
+ });
111
+ }
112
+ tier.layout = { pattern, slots };
113
+ break;
114
+ }
115
+ }
116
+ return next;
117
+ }
118
+ /** 패턴 값(자리 수·간격·축·번호 규칙)을 고친다. 그 패턴에 없는 키는 무시한다 */
119
+ export function updatePattern(layout, tierNo, patch) {
120
+ const next = clone(layout);
121
+ const tier = next.tiers.find(t => t.tierNo === tierNo);
122
+ if (!tier)
123
+ return next;
124
+ const allowedKeys = {
125
+ SINGLE: [],
126
+ ROW: ['count', 'pitch', 'axis', 'origin'],
127
+ GRID: ['rows', 'cols', 'pitch', 'order', 'origin'],
128
+ CUSTOM: []
129
+ };
130
+ for (const [k, v] of Object.entries(patch))
131
+ if (allowedKeys[tier.layout.pattern].includes(k))
132
+ tier.layout[k] = clone(v);
133
+ return next;
134
+ }
135
+ /** SINGLE·ROW·GRID 의 공통 자리 규격, CUSTOM 이면 slotNo 자리를 고친다. undefined 값은 키를 지운다 */
136
+ export function updateSlotSpec(layout, tierNo, patch, slotNo) {
137
+ const next = clone(layout);
138
+ const tier = next.tiers.find(t => t.tierNo === tierNo);
139
+ if (!tier)
140
+ return next;
141
+ const target = (tier.layout.pattern === 'CUSTOM' ? tier.layout.slots.find(s => s.slotNo === slotNo) : tier.layout.slot);
142
+ if (!target)
143
+ return next;
144
+ for (const [k, v] of Object.entries(patch)) {
145
+ if (v === undefined)
146
+ delete target[k];
147
+ else
148
+ target[k] = clone(v);
149
+ }
150
+ if (tier.layout.pattern !== 'CUSTOM')
151
+ for (const k of ['offset', 'name'])
152
+ delete target[k];
153
+ return next;
154
+ }
155
+ /** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */
156
+ export function addCustomSlot(layout, tierNo) {
157
+ const next = clone(layout);
158
+ const tier = next.tiers.find(t => t.tierNo === tierNo);
159
+ if (!tier || tier.layout.pattern !== 'CUSTOM')
160
+ return next;
161
+ const last = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo).pop();
162
+ if (last) {
163
+ const { name: _name, ...rest } = clone(last);
164
+ tier.layout.slots.push({ ...rest, slotNo: last.slotNo + 1 });
165
+ }
166
+ else
167
+ tier.layout.slots.push({ slotNo: 1, offset: { x: 0, z: 0 }, allowed: { width: 1, depth: 1, height: 1 } });
168
+ return next;
169
+ }
170
+ /** CUSTOM Tier 의 자리를 지운다. 남은 자리의 번호는 그대로다. 마지막 남은 자리는 지우지 않는다 */
171
+ export function removeCustomSlot(layout, tierNo, slotNo) {
172
+ const next = clone(layout);
173
+ const tier = next.tiers.find(t => t.tierNo === tierNo);
174
+ if (!tier || tier.layout.pattern !== 'CUSTOM' || tier.layout.slots.length <= 1)
175
+ return next;
176
+ tier.layout.slots = tier.layout.slots.filter(s => s.slotNo !== slotNo);
177
+ return next;
178
+ }
179
+ const sameFrame = (a, b) => a.rotation === b.rotation && a.position.x === b.position.x && a.position.y === b.position.y && a.position.z === b.position.z;
180
+ /**
181
+ * 앞 레이아웃의 주소마다 새 레이아웃에서 같은 주소가 같은 Anchor 의 같은 위치인지 본다. 놓인 Carrier 는 주소로
182
+ * 다시 읽히므로 (빈 자리로 옮겨 채우지 않는다) 여기 나온 주소의 Carrier 는 다른 곳에 그려지거나 예외가 된다.
183
+ * 어느 쪽이든 쓸 수 없는 값이면 빈 결과.
184
+ */
185
+ export function addressImpact(before, after) {
186
+ const impact = { moved: [], gone: [] };
187
+ if (!Array.isArray(before === null || before === void 0 ? void 0 : before.tiers) || !Array.isArray(after === null || after === void 0 ? void 0 : after.tiers))
188
+ return impact;
189
+ try {
190
+ for (const address of slotAddresses(before)) {
191
+ const was = resolveSlot(before, address);
192
+ const now = resolveSlot(after, address);
193
+ if (!was)
194
+ continue;
195
+ const id = slotIdOf(address);
196
+ if (!now)
197
+ impact.gone.push(id);
198
+ else if (was.anchorId !== now.anchorId || !sameFrame(slotFrame(was), slotFrame(now)))
199
+ impact.moved.push(id);
200
+ }
201
+ }
202
+ catch (_a) {
203
+ return { moved: [], gone: [] };
204
+ }
205
+ return impact;
206
+ }
207
+ /** Tier 하나의 자리 위치와 크기 (Tier 로컬). 자리를 만들 수 없는 값이면 빈 목록 */
208
+ export function previewTier(layout, tierNo) {
209
+ var _a;
210
+ const tier = (_a = layout === null || layout === void 0 ? void 0 : layout.tiers) === null || _a === void 0 ? void 0 : _a.find(t => (t === null || t === void 0 ? void 0 : t.tierNo) === tierNo);
211
+ if (!layout || !tier)
212
+ return [];
213
+ let numbers = [];
214
+ try {
215
+ const count = slotCount(tier.layout);
216
+ if (!Number.isSafeInteger(count) || count <= 0 || count > 10000)
217
+ return [];
218
+ numbers = slotNumbers(tier.layout);
219
+ }
220
+ catch (_b) {
221
+ return [];
222
+ }
223
+ const out = [];
224
+ for (const n of numbers) {
225
+ const s = resolveSlot(layout, { tier: tierNo, slot: n });
226
+ if (!s)
227
+ continue;
228
+ const fp = rotatedFootprint({ width: s.allowed.width, depth: s.allowed.depth, height: s.allowed.height }, s.rotation);
229
+ if (![s.offset.x, s.offset.z, fp.width, fp.depth].every(Number.isFinite))
230
+ return [];
231
+ out.push({ slotNo: n, name: s.name, x: s.offset.x, z: s.offset.z, width: fp.width, depth: fp.depth });
232
+ }
233
+ return out;
234
+ }
235
+ //# sourceMappingURL=draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/draft.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 자리 레이아웃 편집 동작. 입력 레이아웃을 바꾸지 않고 새 레이아웃을 돌려준다.\n * 레이아웃에 호스트가 덧붙인 키(예: 실시간 주소 읽기 설정)는 그대로 보존한다.\n */\n\nimport {\n resolveSlot,\n rotatedFootprint,\n slotAddresses,\n slotCount,\n slotFrame,\n slotIdOf,\n slotNumbers,\n type GridOrder,\n type LayoutIssue,\n type PatternSlot,\n type SlotLayout,\n type SlotLayoutTier,\n type SlotPattern,\n type SlotSpec\n} from '@hatiolab/things-scene'\n\n// JSON 복사는 NaN·Infinity 를 null 로 바꿔 잘못된 값을 숨기므로 쓰지 않는다\nconst clone = <T>(v: T): T => structuredClone(v)\n\n// ── 프리셋 ──────────────────────────────────────────────────────────────────\n\n/** 프리셋이 받는 크기. slotHeight 는 자리 허용 높이 */\nexport interface PresetSize {\n width: number\n depth: number\n slotHeight: number\n}\n\n/** 프리셋이 크기 외에 더 받는 값 */\nexport interface PresetParam {\n key: string\n label: string\n /** 양의 정수만 받는다 */\n integer?: boolean\n}\n\n/** 호스트가 넘기는 프리셋 */\nexport interface SlotLayoutPreset<L extends SlotLayout = SlotLayout> {\n key: string\n label: string\n params?: PresetParam[]\n build(size: PresetSize, params: Record<string, number>): L\n}\n\n/** 크기·값을 검사하고 프리셋으로 레이아웃을 만든다. 모자라거나 잘못되면 layout 없이 문제 목록 */\nexport function applyPreset<L extends SlotLayout>(\n preset: SlotLayoutPreset<L>,\n size: Partial<PresetSize>,\n params: Record<string, unknown> = {}\n): { layout?: L; issues: LayoutIssue[] } {\n const issues: LayoutIssue[] = []\n const positive = (v: unknown) => typeof v === 'number' && Number.isFinite(v) && v > 0\n const positiveInt = (v: unknown) => Number.isSafeInteger(v) && (v as number) > 0\n for (const k of ['width', 'depth', 'slotHeight'] as const) if (!positive(size[k])) issues.push({ code: 'POSITIVE_NUMBER_REQUIRED', path: k })\n for (const p of preset.params ?? [])\n if (!(p.integer ? positiveInt(params[p.key]) : positive(params[p.key])))\n issues.push({ code: p.integer ? 'POSITIVE_INTEGER_REQUIRED' : 'POSITIVE_NUMBER_REQUIRED', path: p.key })\n if (issues.length) return { issues }\n return { layout: preset.build(size as PresetSize, params as Record<string, number>), issues }\n}\n\n// ── Tier ───────────────────────────────────────────────────────────────────\n// Tier 번호는 홀더 안의 식별 번호다. 지우거나 정렬해도 다시 매기지 않는다 (번호가 바뀌면 놓인 Carrier 의 주소가 바뀐다).\n\n/** Tier 설정을 복사해 새 Tier 로 붙인다. 번호는 지금 가장 큰 번호 + 1, 배치는 placement 로 바꾼다. 점유는 복사하지 않는다 */\nexport function copyTier<L extends SlotLayout>(layout: L, tierNo: number, placement: TierPatch = {}): L {\n const next = clone(layout)\n const source = next.tiers.find(t => t.tierNo === tierNo)\n if (!source) return next\n const newNo = Math.max(...next.tiers.map(t => t.tierNo)) + 1\n next.tiers.push({ ...clone(source), ...clone(placement), tierNo: newNo })\n return next\n}\n\n/** Tier 를 지운다. 남은 Tier 의 번호는 그대로다. 마지막 남은 Tier 는 지우지 않는다 */\nexport function removeTier<L extends SlotLayout>(layout: L, tierNo: number): L {\n const next = clone(layout)\n if (next.tiers.length <= 1) return next\n next.tiers = next.tiers.filter(t => t.tierNo !== tierNo)\n return next\n}\n\n/** Tier 가 선 Anchor, 적재 방식, Anchor 기준 배치 */\nexport type TierPatch = Partial<Pick<SlotLayoutTier, 'anchorId' | 'mount' | 'position' | 'rotation'>>\n\nexport function updateTier<L extends SlotLayout>(layout: L, tierNo: number, patch: TierPatch): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (tier) Object.assign(tier, clone(patch))\n return next\n}\n\n// ── 패턴 ───────────────────────────────────────────────────────────────────\n\nconst specOf = (l: SlotPattern): SlotSpec => {\n if (l.pattern === 'CUSTOM') {\n const first = [...l.slots].sort((a, b) => a.slotNo - b.slotNo)[0]\n const { allowed, accepts, stackLimit, rotation } = first ?? { allowed: { width: 1, depth: 1, height: 1 } }\n return clone({ allowed, accepts, stackLimit, rotation })\n }\n return clone(l.slot)\n}\n\n/**\n * 층의 패턴을 바꾼다. 자리 규격은 그대로 두고 자리 수·간격은 지금 배치를 이어받는다.\n * CUSTOM 으로 바꾸면 지금 자리들의 위치·규격을 그대로 적어 둔다.\n */\nexport function setTierPattern<L extends SlotLayout>(layout: L, tierNo: number, pattern: SlotPattern['pattern']): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern === pattern) return next\n const cur = tier.layout\n const spec = specOf(cur)\n const count = slotCount(cur)\n\n switch (pattern) {\n case 'SINGLE':\n tier.layout = { pattern, slot: spec }\n break\n case 'ROW':\n tier.layout = {\n pattern,\n count: Math.max(count, 1),\n pitch: cur.pattern === 'ROW' ? cur.pitch : cur.pattern === 'GRID' ? cur.pitch.x : spec.allowed.width,\n axis: 'X',\n origin: 'MIN',\n slot: spec\n }\n break\n case 'GRID':\n tier.layout = {\n pattern,\n rows: cur.pattern === 'GRID' ? cur.rows : 1,\n cols: cur.pattern === 'GRID' ? cur.cols : Math.max(count, 1),\n pitch: { x: cur.pattern === 'ROW' ? cur.pitch : spec.allowed.width, z: spec.allowed.depth },\n order: 'ROW_MAJOR',\n origin: { x: 'MIN', z: 'MIN' },\n slot: spec\n }\n break\n case 'CUSTOM': {\n const slots: PatternSlot[] = []\n for (const n of slotNumbers(cur)) {\n const r = resolveSlot(layout, { tier: tierNo, slot: n })!\n slots.push({\n slotNo: n,\n offset: { ...r.offset },\n allowed: { ...r.allowed },\n ...(r.accepts ? { accepts: [...r.accepts] } : {}),\n ...(r.stackLimit !== 1 ? { stackLimit: r.stackLimit } : {}),\n ...(r.rotation ? { rotation: r.rotation } : {})\n })\n }\n tier.layout = { pattern, slots }\n break\n }\n }\n return next\n}\n\nexport interface PatternPatch {\n count?: number\n pitch?: number | { x: number; z: number }\n axis?: 'X' | 'Z'\n origin?: 'MIN' | 'MAX' | { x: 'MIN' | 'MAX'; z: 'MIN' | 'MAX' }\n rows?: number\n cols?: number\n order?: GridOrder\n}\n\n/** 패턴 값(자리 수·간격·축·번호 규칙)을 고친다. 그 패턴에 없는 키는 무시한다 */\nexport function updatePattern<L extends SlotLayout>(layout: L, tierNo: number, patch: PatternPatch): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier) return next\n const allowedKeys: Record<string, string[]> = {\n SINGLE: [],\n ROW: ['count', 'pitch', 'axis', 'origin'],\n GRID: ['rows', 'cols', 'pitch', 'order', 'origin'],\n CUSTOM: []\n }\n for (const [k, v] of Object.entries(patch))\n if (allowedKeys[tier.layout.pattern].includes(k)) (tier.layout as unknown as Record<string, unknown>)[k] = clone(v)\n return next\n}\n\nexport type SlotSpecPatch = Partial<SlotSpec> & { offset?: { x: number; z: number }; name?: string }\n\n/** SINGLE·ROW·GRID 의 공통 자리 규격, CUSTOM 이면 slotNo 자리를 고친다. undefined 값은 키를 지운다 */\nexport function updateSlotSpec<L extends SlotLayout>(layout: L, tierNo: number, patch: SlotSpecPatch, slotNo?: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier) return next\n const target = (tier.layout.pattern === 'CUSTOM' ? tier.layout.slots.find(s => s.slotNo === slotNo) : tier.layout.slot) as\n | Record<string, unknown>\n | undefined\n if (!target) return next\n for (const [k, v] of Object.entries(patch)) {\n if (v === undefined) delete target[k]\n else target[k] = clone(v)\n }\n if (tier.layout.pattern !== 'CUSTOM') for (const k of ['offset', 'name']) delete target[k]\n return next\n}\n\n/** CUSTOM 층에 자리를 하나 더한다 (마지막 자리 규격 복사, 번호는 마지막 + 1) */\nexport function addCustomSlot<L extends SlotLayout>(layout: L, tierNo: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern !== 'CUSTOM') return next\n const last = [...tier.layout.slots].sort((a, b) => a.slotNo - b.slotNo).pop()\n if (last) {\n const { name: _name, ...rest } = clone(last)\n tier.layout.slots.push({ ...rest, slotNo: last.slotNo + 1 })\n } else tier.layout.slots.push({ slotNo: 1, offset: { x: 0, z: 0 }, allowed: { width: 1, depth: 1, height: 1 } })\n return next\n}\n\n/** CUSTOM Tier 의 자리를 지운다. 남은 자리의 번호는 그대로다. 마지막 남은 자리는 지우지 않는다 */\nexport function removeCustomSlot<L extends SlotLayout>(layout: L, tierNo: number, slotNo: number): L {\n const next = clone(layout)\n const tier = next.tiers.find(t => t.tierNo === tierNo)\n if (!tier || tier.layout.pattern !== 'CUSTOM' || tier.layout.slots.length <= 1) return next\n tier.layout.slots = tier.layout.slots.filter(s => s.slotNo !== slotNo)\n return next\n}\n\n// ── 주소 영향 ──────────────────────────────────────────────────────────────\n\n/** 바꾼 값을 적용하면 뜻이 바뀌는 주소: 위치가 바뀌는 자리, 없어지는 자리 (자리 id) */\nexport interface AddressImpact {\n moved: string[]\n gone: string[]\n}\n\nconst sameFrame = (a: ReturnType<typeof slotFrame>, b: ReturnType<typeof slotFrame>) =>\n a.rotation === b.rotation && a.position.x === b.position.x && a.position.y === b.position.y && a.position.z === b.position.z\n\n/**\n * 앞 레이아웃의 주소마다 새 레이아웃에서 같은 주소가 같은 Anchor 의 같은 위치인지 본다. 놓인 Carrier 는 주소로\n * 다시 읽히므로 (빈 자리로 옮겨 채우지 않는다) 여기 나온 주소의 Carrier 는 다른 곳에 그려지거나 예외가 된다.\n * 어느 쪽이든 쓸 수 없는 값이면 빈 결과.\n */\nexport function addressImpact(before: SlotLayout | undefined, after: SlotLayout | undefined): AddressImpact {\n const impact: AddressImpact = { moved: [], gone: [] }\n if (!Array.isArray(before?.tiers) || !Array.isArray(after?.tiers)) return impact\n try {\n for (const address of slotAddresses(before!)) {\n const was = resolveSlot(before!, address)\n const now = resolveSlot(after!, address)\n if (!was) continue\n const id = slotIdOf(address)\n if (!now) impact.gone.push(id)\n else if (was.anchorId !== now.anchorId || !sameFrame(slotFrame(was), slotFrame(now))) impact.moved.push(id)\n }\n } catch {\n return { moved: [], gone: [] }\n }\n return impact\n}\n\n// ── 미리보기 ───────────────────────────────────────────────────────────────\n\nexport interface PreviewSlot {\n slotNo: number\n name?: string\n /** 자리 중심 (Tier 로컬). 화면 위가 -z */\n x: number\n z: number\n /** 회전한 허용 공간의 외접 폭·깊이 */\n width: number\n depth: number\n}\n\n/** Tier 하나의 자리 위치와 크기 (Tier 로컬). 자리를 만들 수 없는 값이면 빈 목록 */\nexport function previewTier(layout: SlotLayout | undefined, tierNo: number): PreviewSlot[] {\n const tier = layout?.tiers?.find(t => t?.tierNo === tierNo)\n if (!layout || !tier) return []\n let numbers: number[] = []\n try {\n const count = slotCount(tier.layout)\n if (!Number.isSafeInteger(count) || count <= 0 || count > 10000) return []\n numbers = slotNumbers(tier.layout)\n } catch {\n return []\n }\n const out: PreviewSlot[] = []\n for (const n of numbers) {\n const s = resolveSlot(layout, { tier: tierNo, slot: n })\n if (!s) continue\n const fp = rotatedFootprint({ width: s.allowed.width, depth: s.allowed.depth, height: s.allowed.height }, s.rotation)\n if (![s.offset.x, s.offset.z, fp.width, fp.depth].every(Number.isFinite)) return []\n out.push({ slotNo: n, name: s.name, x: s.offset.x, z: s.offset.z, width: fp.width, depth: fp.depth })\n }\n return out\n}\n"]}
@@ -0,0 +1,9 @@
1
+ export * from './draft.js';
2
+ export * from './issue-text.js';
3
+ export * from './ox-slot-layout-editor.js';
4
+ export * from './ox-property-editor-slot-layout.js';
5
+ /** 속성 편집기 등록용. things-scene.config 의 editors 목록에 넣는다 */
6
+ export declare const SLOT_LAYOUT_EDITORS: {
7
+ type: string;
8
+ element: string;
9
+ }[];
@@ -0,0 +1,7 @@
1
+ export * from './draft.js';
2
+ export * from './issue-text.js';
3
+ export * from './ox-slot-layout-editor.js';
4
+ export * from './ox-property-editor-slot-layout.js';
5
+ /** 속성 편집기 등록용. things-scene.config 의 editors 목록에 넣는다 */
6
+ export const SLOT_LAYOUT_EDITORS = [{ type: 'slot-layout', element: 'ox-property-editor-slot-layout' }];
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,4BAA4B,CAAA;AAC1C,cAAc,qCAAqC,CAAA;AAEnD,wDAAwD;AACxD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC,CAAA","sourcesContent":["export * from './draft.js'\nexport * from './issue-text.js'\nexport * from './ox-slot-layout-editor.js'\nexport * from './ox-property-editor-slot-layout.js'\n\n/** 속성 편집기 등록용. things-scene.config 의 editors 목록에 넣는다 */\nexport const SLOT_LAYOUT_EDITORS = [{ type: 'slot-layout', element: 'ox-property-editor-slot-layout' }]\n"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,
5
+ * 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.
6
+ */
7
+ import type { LayoutIssueCode, SlotSpaceCode } from '@hatiolab/things-scene';
8
+ /** 문제 하나. 호스트 고유 코드도 같은 모양으로 온다 */
9
+ export interface EditorIssue {
10
+ code: string;
11
+ path: string;
12
+ params?: Record<string, string | number>;
13
+ }
14
+ /** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */
15
+ export declare const ISSUE_TEXT: Record<LayoutIssueCode, string>;
16
+ export declare const t: (key: string, fallback: string, opts?: Record<string, unknown>) => string;
17
+ /** 자리 공간 경고의 기본 문구. 번역 키는 label.slot-layout.space.{코드} */
18
+ export declare const SPACE_TEXT: Record<SlotSpaceCode, string>;
19
+ export declare function spaceWarningText(warning: EditorIssue): string;
20
+ /** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */
21
+ export declare function issueText(issue: EditorIssue): string;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,
5
+ * 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.
6
+ */
7
+ import { i18next } from '@operato/i18n';
8
+ /** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */
9
+ export const ISSUE_TEXT = {
10
+ TIERS_REQUIRED: 'At least one tier is required',
11
+ MOUNT_INVALID: 'Must be ON, IN or HANG',
12
+ ANCHOR_REQUIRED: 'Choose an anchor',
13
+ ANCHOR_UNKNOWN: 'This holder has no anchor {anchorId}',
14
+ TIER_NUMBER_DUPLICATE: 'Tier {tierNo} is used twice',
15
+ FINITE_NUMBER_REQUIRED: 'A number is required',
16
+ POSITIVE_NUMBER_REQUIRED: 'A number greater than 0 is required',
17
+ POSITIVE_INTEGER_REQUIRED: 'A whole number of 1 or more is required',
18
+ ARRAY_REQUIRED: 'A list is required',
19
+ OBJECT_REQUIRED: 'The value is not a layout',
20
+ AXIS_INVALID: 'Must be X or Z',
21
+ PATTERN_INVALID: 'Must be SINGLE, ROW, GRID or CUSTOM',
22
+ SLOTS_REQUIRED: 'At least one slot is required',
23
+ SLOT_NUMBER_DUPLICATE: 'Slot {slotNo} is used twice',
24
+ NOT_PERSISTABLE: 'Cannot be saved ({value})'
25
+ };
26
+ export const t = (key, fallback, opts = {}) => i18next.t(`label.slot-layout.${key}`, { defaultValue: fallback, ...opts });
27
+ /** 자리 공간 경고의 기본 문구. 번역 키는 label.slot-layout.space.{코드} */
28
+ export const SPACE_TEXT = {
29
+ SLOT_OVERLAP: 'Tier {tier} slot {slot} overlaps tier {otherTier} slot {otherSlot}',
30
+ SLOT_OUTSIDE_ANCHOR: 'Tier {tier} slot {slot} reaches past its anchor face',
31
+ ANCHOR_SPACE_UNKNOWN: 'Anchor {anchorId} gives no face size: room not checked'
32
+ };
33
+ export function spaceWarningText(warning) {
34
+ var _a;
35
+ const fallback = (_a = SPACE_TEXT[warning.code]) !== null && _a !== void 0 ? _a : warning.code;
36
+ return t(`space.${warning.code}`, fallback, { ...warning.params, interpolation: { escapeValue: false } });
37
+ }
38
+ /** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */
39
+ export function issueText(issue) {
40
+ var _a;
41
+ const fallback = (_a = ISSUE_TEXT[issue.code]) !== null && _a !== void 0 ? _a : issue.code;
42
+ const text = t(`issue.${issue.code}`, fallback, { ...issue.params, interpolation: { escapeValue: false } });
43
+ return issue.path ? `${issue.path}: ${text}` : text;
44
+ }
45
+ //# sourceMappingURL=issue-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-text.js","sourceRoot":"","sources":["../../src/issue-text.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAUvC,oEAAoE;AACpE,MAAM,CAAC,MAAM,UAAU,GAAoC;IACzD,cAAc,EAAE,+BAA+B;IAC/C,aAAa,EAAE,wBAAwB;IACvC,eAAe,EAAE,kBAAkB;IACnC,cAAc,EAAE,sCAAsC;IACtD,qBAAqB,EAAE,6BAA6B;IACpD,sBAAsB,EAAE,sBAAsB;IAC9C,wBAAwB,EAAE,qCAAqC;IAC/D,yBAAyB,EAAE,yCAAyC;IACpE,cAAc,EAAE,oBAAoB;IACpC,eAAe,EAAE,2BAA2B;IAC5C,YAAY,EAAE,gBAAgB;IAC9B,eAAe,EAAE,qCAAqC;IACtD,cAAc,EAAE,+BAA+B;IAC/C,qBAAqB,EAAE,6BAA6B;IACpD,eAAe,EAAE,2BAA2B;CAC7C,CAAA;AAED,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,OAAgC,EAAE,EAAE,EAAE,CACrF,OAAO,CAAC,CAAC,CAAC,qBAAqB,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC,CAAA;AAE5E,0DAA0D;AAC1D,MAAM,CAAC,MAAM,UAAU,GAAkC;IACvD,YAAY,EAAE,oEAAoE;IAClF,mBAAmB,EAAE,sDAAsD;IAC3E,oBAAoB,EAAE,wDAAwD;CAC/E,CAAA;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAoB;;IACnD,MAAM,QAAQ,GAAG,MAAC,UAAqC,CAAC,OAAO,CAAC,IAAI,CAAC,mCAAI,OAAO,CAAC,IAAI,CAAA;IACrF,OAAO,CAAC,CAAC,SAAS,OAAO,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;AAC3G,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,SAAS,CAAC,KAAkB;;IAC1C,MAAM,QAAQ,GAAG,MAAC,UAAqC,CAAC,KAAK,CAAC,IAAI,CAAC,mCAAI,KAAK,CAAC,IAAI,CAAA;IACjF,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;IAC3G,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACrD,CAAC","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n *\n * 검증 문제(코드·경로·매개변수)를 화면 문구로. 번역 키는 label.slot-layout.issue.{코드} 이고,\n * 번역이 없으면 아래 영어 기본 문구를 쓴다. 호스트 고유 코드는 호스트가 번역을 넣거나 issueText 옵션으로 바꾼다.\n */\n\nimport { i18next } from '@operato/i18n'\nimport type { LayoutIssueCode, SlotSpaceCode } from '@hatiolab/things-scene'\n\n/** 문제 하나. 호스트 고유 코드도 같은 모양으로 온다 */\nexport interface EditorIssue {\n code: string\n path: string\n params?: Record<string, string | number>\n}\n\n/** things-scene 검증 코드의 기본 문구. 자리표시자가 중괄호 하나라서 문구에 중괄호 글자를 쓰지 않는다 */\nexport const ISSUE_TEXT: Record<LayoutIssueCode, string> = {\n TIERS_REQUIRED: 'At least one tier is required',\n MOUNT_INVALID: 'Must be ON, IN or HANG',\n ANCHOR_REQUIRED: 'Choose an anchor',\n ANCHOR_UNKNOWN: 'This holder has no anchor {anchorId}',\n TIER_NUMBER_DUPLICATE: 'Tier {tierNo} is used twice',\n FINITE_NUMBER_REQUIRED: 'A number is required',\n POSITIVE_NUMBER_REQUIRED: 'A number greater than 0 is required',\n POSITIVE_INTEGER_REQUIRED: 'A whole number of 1 or more is required',\n ARRAY_REQUIRED: 'A list is required',\n OBJECT_REQUIRED: 'The value is not a layout',\n AXIS_INVALID: 'Must be X or Z',\n PATTERN_INVALID: 'Must be SINGLE, ROW, GRID or CUSTOM',\n SLOTS_REQUIRED: 'At least one slot is required',\n SLOT_NUMBER_DUPLICATE: 'Slot {slotNo} is used twice',\n NOT_PERSISTABLE: 'Cannot be saved ({value})'\n}\n\nexport const t = (key: string, fallback: string, opts: Record<string, unknown> = {}) =>\n i18next.t(`label.slot-layout.${key}`, { defaultValue: fallback, ...opts })\n\n/** 자리 공간 경고의 기본 문구. 번역 키는 label.slot-layout.space.{코드} */\nexport const SPACE_TEXT: Record<SlotSpaceCode, string> = {\n SLOT_OVERLAP: 'Tier {tier} slot {slot} overlaps tier {otherTier} slot {otherSlot}',\n SLOT_OUTSIDE_ANCHOR: 'Tier {tier} slot {slot} reaches past its anchor face',\n ANCHOR_SPACE_UNKNOWN: 'Anchor {anchorId} gives no face size: room not checked'\n}\n\nexport function spaceWarningText(warning: EditorIssue): string {\n const fallback = (SPACE_TEXT as Record<string, string>)[warning.code] ?? warning.code\n return t(`space.${warning.code}`, fallback, { ...warning.params, interpolation: { escapeValue: false } })\n}\n\n/** 문제 하나를 화면 문구로. 앞에 값의 위치를 붙인다 */\nexport function issueText(issue: EditorIssue): string {\n const fallback = (ISSUE_TEXT as Record<string, string>)[issue.code] ?? issue.code\n const text = t(`issue.${issue.code}`, fallback, { ...issue.params, interpolation: { escapeValue: false } })\n return issue.path ? `${issue.path}: ${text}` : text\n}\n"]}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 속성 패널용 자리 레이아웃 편집기 (편집기 타입 'slot-layout').
5
+ *
6
+ * 기존 속성 편집기 계약을 따른다: 값은 this.value, 바뀌면 change 를 보내고 호스트(속성 패널)가 컴포넌트에 반영한다.
7
+ * 선택 컴포넌트가 필요한 옵션(값의 출처, 기본값, 상태 문구)은 'i-need-selected' 로 선택 목록을 받아
8
+ * 속성 정의의 property.options(selected) 가 만든다. 편집기는 컴포넌트를 직접 바꾸지 않는다.
9
+ *
10
+ * [기본값 사용]은 값을 비우는 편집과 구분해 'property-use-default'(detail.name, 취소 가능) 를 먼저 알린다.
11
+ * 호스트가 그것을 처리하고 preventDefault() 하면 끝이고, 아니면 값을 비우는 change 하나를 보낸다.
12
+ * 그 뒤 보일 기본값은 호스트가 property.options 로 다시 준다.
13
+ *
14
+ * 속성 정의 예:
15
+ * { type: 'slot-layout', name: 'loadLayout', label: 'load-slots',
16
+ * property: { options: selected => ({ effective, notes, presets, ... }) } }
17
+ */
18
+ import { OxPropertyEditor, type PropertySpec } from '@operato/property-editor';
19
+ import './ox-slot-layout-editor.js';
20
+ import type { SlotLayoutEditorOptions } from './ox-slot-layout-editor.js';
21
+ export interface SlotLayoutPropertyOptions {
22
+ /** 선택 컴포넌트로 편집기 옵션을 만든다. 값이 바뀔 때마다 다시 부른다 */
23
+ options?: (selected: unknown[]) => SlotLayoutEditorOptions | undefined;
24
+ }
25
+ export declare class OxPropertyEditorSlotLayout extends OxPropertyEditor {
26
+ private _options;
27
+ private _optionsFor;
28
+ editorTemplate(value: any, spec: PropertySpec): import("lit-html").TemplateResult<1>;
29
+ /** 'i-need-selected' 로 선택 목록을 받아 property.options 를 부른다. 받을 호스트가 없으면 옵션 없음 */
30
+ private _resolveOptions;
31
+ private _onUseDefault;
32
+ }
33
+ declare global {
34
+ interface HTMLElementTagNameMap {
35
+ 'ox-property-editor-slot-layout': OxPropertyEditorSlotLayout;
36
+ }
37
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @license Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * 속성 패널용 자리 레이아웃 편집기 (편집기 타입 'slot-layout').
5
+ *
6
+ * 기존 속성 편집기 계약을 따른다: 값은 this.value, 바뀌면 change 를 보내고 호스트(속성 패널)가 컴포넌트에 반영한다.
7
+ * 선택 컴포넌트가 필요한 옵션(값의 출처, 기본값, 상태 문구)은 'i-need-selected' 로 선택 목록을 받아
8
+ * 속성 정의의 property.options(selected) 가 만든다. 편집기는 컴포넌트를 직접 바꾸지 않는다.
9
+ *
10
+ * [기본값 사용]은 값을 비우는 편집과 구분해 'property-use-default'(detail.name, 취소 가능) 를 먼저 알린다.
11
+ * 호스트가 그것을 처리하고 preventDefault() 하면 끝이고, 아니면 값을 비우는 change 하나를 보낸다.
12
+ * 그 뒤 보일 기본값은 호스트가 property.options 로 다시 준다.
13
+ *
14
+ * 속성 정의 예:
15
+ * { type: 'slot-layout', name: 'loadLayout', label: 'load-slots',
16
+ * property: { options: selected => ({ effective, notes, presets, ... }) } }
17
+ */
18
+ import { __decorate } from "tslib";
19
+ import { html } from 'lit';
20
+ import { customElement } from 'lit/decorators.js';
21
+ import { OxPropertyEditor } from '@operato/property-editor';
22
+ import './ox-slot-layout-editor.js';
23
+ let OxPropertyEditorSlotLayout = class OxPropertyEditorSlotLayout extends OxPropertyEditor {
24
+ constructor() {
25
+ super(...arguments);
26
+ this._options = {};
27
+ this._optionsFor = Symbol('none');
28
+ }
29
+ editorTemplate(value, spec) {
30
+ var _a;
31
+ // 값이 바뀌었을 때만 옵션을 다시 만든다 (렌더마다 새 옵션을 주면 편집 중인 값이 초기화된다)
32
+ if (this._optionsFor !== value) {
33
+ this._optionsFor = value;
34
+ this._options = (_a = this._resolveOptions(spec)) !== null && _a !== void 0 ? _a : {};
35
+ }
36
+ return html `
37
+ <ox-slot-layout-editor
38
+ id="editor"
39
+ .value=${value}
40
+ .options=${this._options}
41
+ @slot-layout-use-default=${this._onUseDefault}
42
+ ></ox-slot-layout-editor>
43
+ `;
44
+ }
45
+ /** 'i-need-selected' 로 선택 목록을 받아 property.options 를 부른다. 받을 호스트가 없으면 옵션 없음 */
46
+ _resolveOptions(spec) {
47
+ var _a;
48
+ const make = (_a = spec.property) === null || _a === void 0 ? void 0 : _a.options;
49
+ if (typeof make !== 'function')
50
+ return undefined;
51
+ let selected;
52
+ this.dispatchEvent(new CustomEvent('i-need-selected', {
53
+ bubbles: true,
54
+ composed: true,
55
+ detail: { callback: (s) => (selected = s) }
56
+ }));
57
+ return selected ? make(selected) : undefined;
58
+ }
59
+ _onUseDefault(e) {
60
+ e.stopPropagation();
61
+ // 의도를 먼저 알린다. 호스트가 직접 처리하고 preventDefault() 하면 값 변경을 보내지 않는다 (두 번 저장하지 않도록).
62
+ // 처리하지 않는 호스트(기존 속성 패널)에는 값을 비우는 change 하나로 알린다.
63
+ const intent = new CustomEvent('property-use-default', { bubbles: true, composed: true, cancelable: true, detail: { name: this.name } });
64
+ if (!this.dispatchEvent(intent))
65
+ return;
66
+ this.value = undefined;
67
+ this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
68
+ }
69
+ };
70
+ OxPropertyEditorSlotLayout = __decorate([
71
+ customElement('ox-property-editor-slot-layout')
72
+ ], OxPropertyEditorSlotLayout);
73
+ export { OxPropertyEditorSlotLayout };
74
+ //# sourceMappingURL=ox-property-editor-slot-layout.js.map