@things-factory/figure-ui 10.1.44 → 10.1.46

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 (129) hide show
  1. package/client/modeller/figure-v3-links.ts +394 -0
  2. package/client/modeller/modeller-route.ts +6 -21
  3. package/client/modeller/v3-editor.ts +47 -5
  4. package/client/modeller/v3-gizmo.ts +13 -2
  5. package/client/modeller/v3-links.ts +170 -0
  6. package/client/pages/figure-v3-modeller-page.ts +2 -5
  7. package/client/route.ts +2 -6
  8. package/dist-client/modeller/figure-v3-links.d.ts +47 -0
  9. package/dist-client/modeller/figure-v3-links.js +379 -0
  10. package/dist-client/modeller/figure-v3-links.js.map +1 -0
  11. package/dist-client/modeller/modeller-route.d.ts +4 -11
  12. package/dist-client/modeller/modeller-route.js +5 -22
  13. package/dist-client/modeller/modeller-route.js.map +1 -1
  14. package/dist-client/modeller/v3-editor.d.ts +5 -0
  15. package/dist-client/modeller/v3-editor.js +49 -4
  16. package/dist-client/modeller/v3-editor.js.map +1 -1
  17. package/dist-client/modeller/v3-gizmo.js +11 -1
  18. package/dist-client/modeller/v3-gizmo.js.map +1 -1
  19. package/dist-client/modeller/v3-links.d.ts +67 -0
  20. package/dist-client/modeller/v3-links.js +141 -0
  21. package/dist-client/modeller/v3-links.js.map +1 -0
  22. package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -5
  23. package/dist-client/pages/figure-v3-modeller-page.js +2 -5
  24. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  25. package/dist-client/route.d.ts +1 -1
  26. package/dist-client/route.js +2 -5
  27. package/dist-client/route.js.map +1 -1
  28. package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -0
  29. package/dist-client/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +4 -4
  31. package/test/ai-proposal-contract.test.ts +0 -88
  32. package/test/figure-source.test.ts +0 -102
  33. package/test/i18n-prefix-guard.test.ts +0 -24
  34. package/test/modeller-route.test.ts +14 -25
  35. package/test/part-edits.test.ts +0 -7
  36. package/test/scene-budget.test.ts +0 -7
  37. package/test/v3-gizmo.test.ts +10 -0
  38. package/test/v3-links.test.ts +163 -0
  39. package/things-factory.config.js +2 -5
  40. package/translations/en.json +21 -1
  41. package/translations/ja.json +21 -1
  42. package/translations/ko.json +21 -1
  43. package/translations/ms.json +21 -1
  44. package/translations/zh.json +21 -1
  45. package/client/modeller/angle-dial.ts +0 -70
  46. package/client/modeller/base-box.ts +0 -175
  47. package/client/modeller/figure-animations.ts +0 -1249
  48. package/client/modeller/figure-canvas.ts +0 -567
  49. package/client/modeller/figure-capabilities.ts +0 -379
  50. package/client/modeller/figure-inspector.ts +0 -2208
  51. package/client/modeller/figure-parts.ts +0 -479
  52. package/client/modeller/figure-preview.ts +0 -598
  53. package/client/modeller/figure-report.ts +0 -966
  54. package/client/modeller/figure-settings.ts +0 -328
  55. package/client/modeller/figure-side.ts +0 -272
  56. package/client/modeller/figure-thumbnail.ts +0 -123
  57. package/client/modeller/figure-view.ts +0 -152
  58. package/client/modeller/joint-edits.ts +0 -262
  59. package/client/modeller/joint-gizmo.ts +0 -180
  60. package/client/modeller/maturity.ts +0 -288
  61. package/client/modeller/new-figure-entry.ts +0 -4
  62. package/client/modeller/pose-defaults.ts +0 -67
  63. package/client/pages/figure-modeller-page.ts +0 -2082
  64. package/dist-client/modeller/angle-dial.d.ts +0 -32
  65. package/dist-client/modeller/angle-dial.js +0 -41
  66. package/dist-client/modeller/angle-dial.js.map +0 -1
  67. package/dist-client/modeller/base-box.d.ts +0 -71
  68. package/dist-client/modeller/base-box.js +0 -110
  69. package/dist-client/modeller/base-box.js.map +0 -1
  70. package/dist-client/modeller/figure-animations.d.ts +0 -159
  71. package/dist-client/modeller/figure-animations.js +0 -1132
  72. package/dist-client/modeller/figure-animations.js.map +0 -1
  73. package/dist-client/modeller/figure-canvas.d.ts +0 -150
  74. package/dist-client/modeller/figure-canvas.js +0 -506
  75. package/dist-client/modeller/figure-canvas.js.map +0 -1
  76. package/dist-client/modeller/figure-capabilities.d.ts +0 -42
  77. package/dist-client/modeller/figure-capabilities.js +0 -349
  78. package/dist-client/modeller/figure-capabilities.js.map +0 -1
  79. package/dist-client/modeller/figure-inspector.d.ts +0 -329
  80. package/dist-client/modeller/figure-inspector.js +0 -2095
  81. package/dist-client/modeller/figure-inspector.js.map +0 -1
  82. package/dist-client/modeller/figure-parts.d.ts +0 -64
  83. package/dist-client/modeller/figure-parts.js +0 -478
  84. package/dist-client/modeller/figure-parts.js.map +0 -1
  85. package/dist-client/modeller/figure-preview.d.ts +0 -138
  86. package/dist-client/modeller/figure-preview.js +0 -549
  87. package/dist-client/modeller/figure-preview.js.map +0 -1
  88. package/dist-client/modeller/figure-report.d.ts +0 -165
  89. package/dist-client/modeller/figure-report.js +0 -952
  90. package/dist-client/modeller/figure-report.js.map +0 -1
  91. package/dist-client/modeller/figure-settings.d.ts +0 -39
  92. package/dist-client/modeller/figure-settings.js +0 -317
  93. package/dist-client/modeller/figure-settings.js.map +0 -1
  94. package/dist-client/modeller/figure-side.d.ts +0 -78
  95. package/dist-client/modeller/figure-side.js +0 -317
  96. package/dist-client/modeller/figure-side.js.map +0 -1
  97. package/dist-client/modeller/figure-thumbnail.d.ts +0 -3
  98. package/dist-client/modeller/figure-thumbnail.js +0 -95
  99. package/dist-client/modeller/figure-thumbnail.js.map +0 -1
  100. package/dist-client/modeller/figure-view.d.ts +0 -118
  101. package/dist-client/modeller/figure-view.js +0 -78
  102. package/dist-client/modeller/figure-view.js.map +0 -1
  103. package/dist-client/modeller/joint-edits.d.ts +0 -49
  104. package/dist-client/modeller/joint-edits.js +0 -223
  105. package/dist-client/modeller/joint-edits.js.map +0 -1
  106. package/dist-client/modeller/joint-gizmo.d.ts +0 -34
  107. package/dist-client/modeller/joint-gizmo.js +0 -158
  108. package/dist-client/modeller/joint-gizmo.js.map +0 -1
  109. package/dist-client/modeller/maturity.d.ts +0 -55
  110. package/dist-client/modeller/maturity.js +0 -212
  111. package/dist-client/modeller/maturity.js.map +0 -1
  112. package/dist-client/modeller/new-figure-entry.d.ts +0 -2
  113. package/dist-client/modeller/new-figure-entry.js +0 -5
  114. package/dist-client/modeller/new-figure-entry.js.map +0 -1
  115. package/dist-client/modeller/pose-defaults.d.ts +0 -9
  116. package/dist-client/modeller/pose-defaults.js +0 -35
  117. package/dist-client/modeller/pose-defaults.js.map +0 -1
  118. package/dist-client/pages/figure-modeller-page.d.ts +0 -419
  119. package/dist-client/pages/figure-modeller-page.js +0 -2060
  120. package/dist-client/pages/figure-modeller-page.js.map +0 -1
  121. package/test/angle-dial.test.ts +0 -78
  122. package/test/base-box.test.ts +0 -235
  123. package/test/joint-edits.test.ts +0 -165
  124. package/test/joint-gizmo.test.ts +0 -174
  125. package/test/joint-needle.test.ts +0 -162
  126. package/test/modeller-undo-redo.test.ts +0 -33
  127. package/test/new-figure-entry.test.ts +0 -15
  128. package/test/pose-defaults.test.ts +0 -71
  129. package/test/preview-board-depth.test.ts +0 -27
@@ -1,349 +0,0 @@
1
- import { __decorate, __metadata } from "tslib";
2
- import { css, html, LitElement } from 'lit';
3
- import { customElement, property } from 'lit/decorators.js';
4
- import { live } from 'lit/directives/live.js';
5
- import { i18next, localize } from '@operato/i18n';
6
- import { ScrollbarStyles } from '@operato/styles';
7
- /*
8
- 칸마다의 이름은 **형식의 능력 이름**이다. `string[]` 으로 두면 사다리에 오타를 적어도
9
- 타입이 통과하고, 그 도형은 발행까지 지난 뒤 씬에서 죽는다 — `hoist` 가 그렇게 나갔다
10
- (ADR-0051 ④). `as const` 로 덮지 않는 이유도 같다: 그건 좁히는 것이 아니라 가리는 것이다.
11
- */
12
- const RUNGS = Object.freeze({
13
- none: [],
14
- hold: ['Capacity', 'Holdable'],
15
- flow: ['FlowNode']
16
- });
17
- /** 선언된 이름 집합이 사다리 어느 칸인가. 어느 칸도 아니면 `null` — 지어내지 않는다. */
18
- function rungOf(names) {
19
- const has = new Set(names ?? []);
20
- if (has.size === 0)
21
- return 'none';
22
- for (const [rung, wanted] of Object.entries(RUNGS)) {
23
- if (wanted.length === has.size && wanted.every(name => has.has(name)))
24
- return rung;
25
- }
26
- return null;
27
- }
28
- let FigureCapabilities = class FigureCapabilities extends localize(i18next)(LitElement) {
29
- constructor() {
30
- super(...arguments);
31
- this.parts = [];
32
- }
33
- static { this.styles = [
34
- ScrollbarStyles,
35
- css `
36
- :host {
37
- display: flex;
38
- flex-direction: column;
39
- min-height: 0;
40
- overflow-y: auto;
41
- background-color: var(--md-sys-color-surface-container-lowest);
42
- color: var(--md-sys-color-on-surface);
43
- font: var(--label-font, inherit);
44
- font-size: 0.76rem;
45
- }
46
-
47
- section {
48
- display: flex;
49
- flex-direction: column;
50
- gap: var(--spacing-medium, 8px);
51
- padding: var(--spacing-large, 12px);
52
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
53
- }
54
-
55
- /* 다른 탭들과 같은 눈금이다 — 탭마다 다른 제목 규격을 쓰면 따로 논다 */
56
- h3 {
57
- margin: 0 0 2px 0;
58
- font-size: var(--ui-t-micro, 11px);
59
- font-weight: 700;
60
- letter-spacing: 0.04em;
61
- text-transform: uppercase;
62
- color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));
63
- }
64
-
65
- p[quiet] {
66
- margin: 0;
67
- font-size: 0.72rem;
68
- line-height: 1.5;
69
- color: var(--md-sys-color-on-surface-variant);
70
- }
71
-
72
- /* 사다리 — 고르는 것이 하나라 라디오다 */
73
- label[rung] {
74
- display: flex;
75
- align-items: flex-start;
76
- gap: var(--spacing-medium, 8px);
77
- padding: 6px 8px;
78
- border: 1px solid var(--md-sys-color-outline-variant);
79
- border-radius: 4px;
80
- cursor: pointer;
81
- }
82
- label[rung][on] {
83
- border-color: var(--md-sys-color-primary);
84
- background-color: var(--md-sys-color-surface-container);
85
- }
86
- label[rung]:hover:not([on]) {
87
- background-color: var(--md-sys-color-surface-container-low);
88
- }
89
- label[rung] span {
90
- display: flex;
91
- flex-direction: column;
92
- gap: 1px;
93
- }
94
- label[rung] strong {
95
- font-weight: 600;
96
- }
97
- label[rung] em {
98
- font-style: normal;
99
- font-size: 0.7rem;
100
- line-height: 1.4;
101
- color: var(--md-sys-color-on-surface-variant);
102
- }
103
-
104
- input[type='radio'] {
105
- flex: none;
106
- margin: 2px 0 0 0;
107
- accent-color: var(--md-sys-color-primary);
108
- cursor: pointer;
109
- }
110
-
111
- /* 실제로 씬에 붙는 이름. 고른 뒤에 보여 준다 */
112
- code[names] {
113
- font: var(--mono-font, ui-monospace, monospace);
114
- font-size: 0.7rem;
115
- color: var(--md-sys-color-on-surface-variant);
116
- }
117
-
118
- /* 지정된 부품 한 줄 — 읽는 것이지 고치는 것이 아니다. 눌러 그 부품으로 간다 */
119
- button[marked] {
120
- display: flex;
121
- align-items: baseline;
122
- gap: var(--spacing-medium, 8px);
123
- width: 100%;
124
- padding: 5px 8px;
125
- text-align: left;
126
- font: inherit;
127
- color: inherit;
128
- background: none;
129
- border: 1px solid var(--md-sys-color-outline-variant);
130
- border-radius: 4px;
131
- cursor: pointer;
132
- }
133
- button[marked]:hover {
134
- background-color: var(--md-sys-color-surface-container);
135
- }
136
- button[marked] b {
137
- flex: none;
138
- font-weight: 600;
139
- }
140
- button[marked] span {
141
- flex: none;
142
- font-size: 0.72rem;
143
- color: var(--md-sys-color-primary);
144
- }
145
- button[marked] em {
146
- flex: 1;
147
- min-width: 0;
148
- overflow: hidden;
149
- text-overflow: ellipsis;
150
- white-space: nowrap;
151
- text-align: right;
152
- font-style: normal;
153
- font-size: 0.7rem;
154
- color: var(--md-sys-color-on-surface-variant);
155
- }
156
-
157
- /* 부품마다 한 줄. 자리를 고른 것만 아래로 펼친다 */
158
- div[part] {
159
- display: flex;
160
- flex-direction: column;
161
- gap: 4px;
162
- padding: 6px 0;
163
- border-top: 1px solid var(--md-sys-color-outline-variant);
164
- }
165
- div[part]:first-of-type {
166
- border-top: none;
167
- }
168
- div[head] {
169
- display: flex;
170
- align-items: center;
171
- gap: var(--spacing-medium, 8px);
172
- }
173
- div[head] b {
174
- flex: 1;
175
- min-width: 0;
176
- overflow: hidden;
177
- text-overflow: ellipsis;
178
- white-space: nowrap;
179
- font-weight: 600;
180
- }
181
-
182
- select,
183
- input[type='text'],
184
- input[type='number'] {
185
- padding: 4px 6px;
186
- font: var(--input-field-font, inherit);
187
- font-size: 0.74rem;
188
- color: var(--md-sys-color-on-surface);
189
- background-color: var(--md-sys-color-surface);
190
- border: 1px solid var(--md-sys-color-outline-variant);
191
- border-radius: 4px;
192
- }
193
- /* 이름이 길어질 수 있는 쪽(문)까지 안 잘리는 폭이다 */
194
- div[head] select {
195
- flex: none;
196
- width: 8.5em;
197
- }
198
-
199
- div[detail] {
200
- display: grid;
201
- grid-template-columns: 4.5em 1fr;
202
- align-items: center;
203
- gap: 4px var(--spacing-medium, 8px);
204
- padding-left: var(--spacing-medium, 8px);
205
- }
206
- div[detail] > span {
207
- font-size: 0.72rem;
208
- color: var(--md-sys-color-on-surface-variant);
209
- }
210
- div[detail] input {
211
- width: 100%;
212
- box-sizing: border-box;
213
- }
214
- div[detail] em {
215
- grid-column: 2;
216
- font-style: normal;
217
- font-size: 0.7rem;
218
- color: var(--md-sys-color-on-surface-variant);
219
- }
220
- `
221
- ]; }
222
- render() {
223
- const rung = rungOf(this.draft?.capabilities);
224
- const names = this.draft?.capabilities ?? [];
225
- return html `
226
- <section>
227
- <h3>${i18next.t('figure.label.what-this-does')}</h3>
228
-
229
- ${this.rung('none', rung)} ${this.rung('hold', rung)} ${this.rung('flow', rung)}
230
- ${rung === null
231
- ? html `<p quiet>${i18next.t('figure.text.capability-hand-written')}</p>`
232
- : ''}
233
- ${names.length > 0 ? html `<code names>${names.join(' · ')}</code>` : ''}
234
- </section>
235
-
236
- ${this.renderAnchors(rung)}
237
- `;
238
- }
239
- /**
240
- * 자리와 문 — **여기서는 읽기만 한다.**
241
- *
242
- * 지정은 속성 탭에서 한다. 부품을 고치는 곳이 한 군데여야 하기 때문이다 — 목록을 여기 또
243
- * 두면 같은 부품을 두 화면에서 고치게 되고, 저작자는 어느 쪽이 이기는지 매번 생각해야 한다.
244
- *
245
- * 그래도 요약은 남긴다. 「어느 부품이 자리인가」는 부품을 하나씩 눌러 보며 알아낼 일이
246
- * 아니다 — 자리는 그려지지 않으므로 화면을 봐서는 영영 못 찾는다.
247
- */
248
- renderAnchors(rung) {
249
- if (rung === 'none') {
250
- return html `
251
- <section>
252
- <h3>${i18next.t('figure.label.slots-and-ports')}</h3>
253
- <p quiet>${i18next.t('figure.text.slots-need-capability')}</p>
254
- </section>
255
- `;
256
- }
257
- const marked = this.parts.filter(part => part.capability);
258
- return html `
259
- <section>
260
- <h3>${i18next.t('figure.label.slots-and-ports')}</h3>
261
- <p quiet>${i18next.t('figure.text.anchors-are-not-drawn')}</p>
262
-
263
- ${marked.length === 0
264
- ? html `<p quiet>${i18next.t('figure.text.designate-in-properties')}</p>`
265
- : marked.map(part => this.renderMarked(part))}
266
- </section>
267
- `;
268
- }
269
- /** 지정된 부품 한 줄. 누르면 그 부품이 골라진다 — 고치는 자리는 속성 탭이다. */
270
- renderMarked(part) {
271
- const spec = part.capability;
272
- return html `
273
- <button marked @click=${() => this.pick(part)}>
274
- <b>${part.name}</b>
275
- <span>${spec.roles.map(role => i18next.t('figure.label.role-' + role)).join(' · ')}</span>
276
- ${spec.accepts?.length ? html `<em>${spec.accepts.join(' · ')}</em>` : ''}
277
- </button>
278
- `;
279
- }
280
- /** 이 부품을 고르게 한다. 부품 목록에서 누른 것과 같은 알림이다. */
281
- pick(part) {
282
- const index = this.parts.indexOf(part);
283
- if (index < 0)
284
- return;
285
- this.dispatchEvent(new CustomEvent('select-part', { detail: { index }, bubbles: true, composed: true }));
286
- }
287
- /**
288
- * 사다리 한 칸.
289
- *
290
- * `checked` 를 `live()` 로 묶는다. 그냥 두면 Lit 이 **마지막으로 그린 값**과 견주므로,
291
- * 브라우저가 DOM 을 바꿨는데 모델은 안 바뀐 경우 되돌리지 않는다. 실제로 그랬다 — 고르는
292
- * 도중 도형이 다시 실렸더니 점은 「흐른다」에 있고 강조 상자는 「그리기만 한다」에 있었다.
293
- * 화면이 모델과 다른 말을 하는 상태다. `live()` 는 **지금 DOM 의 값**과 견준다.
294
- */
295
- rung(which, chosen) {
296
- return html `
297
- <label rung ?on=${chosen === which}>
298
- <input
299
- type="radio"
300
- name="rung"
301
- .checked=${live(chosen === which)}
302
- @change=${() => this.setRung(which)}
303
- />
304
- <span>
305
- <strong>${i18next.t(`figure.label.rung-${which}`)}</strong>
306
- <em>${i18next.t(`figure.text.rung-${which}-note`)}</em>
307
- </span>
308
- </label>
309
- `;
310
- }
311
- /**
312
- * 사다리를 내리면 자리도 함께 거둔다.
313
- *
314
- * 능력을 「그리기만 한다」로 되돌려 놓고 부품에 자리 표시가 남아 있으면, 형식은 자리를
315
- * 그대로 실어 보내는데 씬은 그것을 쓸 클래스가 없다. **선언은 있고 길이 없는** 상태가
316
- * 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.
317
- */
318
- setRung(which) {
319
- const draft = this.draft;
320
- if (!draft)
321
- return;
322
- this.tellDraft({ ...draft, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined });
323
- if (which === 'none') {
324
- this.parts.forEach((part, index) => {
325
- if (!part.capability)
326
- return;
327
- const { capability, ...rest } = part;
328
- this.dispatchEvent(new CustomEvent('update-part', { detail: { index, part: rest }, bubbles: true, composed: true }));
329
- });
330
- }
331
- }
332
- tellDraft(draft) {
333
- this.draft = draft;
334
- this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }));
335
- }
336
- };
337
- __decorate([
338
- property({ type: Object }),
339
- __metadata("design:type", Object)
340
- ], FigureCapabilities.prototype, "draft", void 0);
341
- __decorate([
342
- property({ type: Array }),
343
- __metadata("design:type", Array)
344
- ], FigureCapabilities.prototype, "parts", void 0);
345
- FigureCapabilities = __decorate([
346
- customElement('figure-capabilities')
347
- ], FigureCapabilities);
348
- export { FigureCapabilities };
349
- //# sourceMappingURL=figure-capabilities.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"figure-capabilities.js","sourceRoot":"","sources":["../../client/modeller/figure-capabilities.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { live } from 'lit/directives/live.js'\n\nimport { i18next, localize } from '@operato/i18n'\nimport { ScrollbarStyles } from '@operato/styles'\n\nimport type { FigureCapability } from '@hatiolab/figure-model'\n\nimport type { FigureDraft, PartModel } from './figure-source.js'\n\n/**\n * 이 도형이 **무엇을 하는가.**\n *\n * ## 왜 탭 하나가 필요한가\n *\n * 지금까지 저작한 것은 **생김새**뿐이었다. 컨베이어를 아무리 잘 그려도 도면에 놓으면\n * 상자 하나를 못 받는다 — 물건을 받고 담고 흘려보내는 것은 씬의 capability 가 하는 일이고,\n * 손으로 쓴 컴포넌트는 클래스 선언 한 줄로 그것을 졌다. 저작한 도형에는 그 줄을 쓸 자리가\n * 없어서, 여기서 고른 것이 그 줄이 된다.\n *\n * ## 사다리로 묻는다\n *\n * 능력의 이름(`Capacity` · `Holdable` · `Transferable` · `FlowNode`)을 그대로 늘어놓고\n * 고르라고 하면, 저작자는 넷의 관계를 먼저 배워야 한다. 실제로 물어야 하는 것은 하나다 —\n * **이 도형이 물건을 다루나, 다룬다면 지나가나.** 그래서 세 칸의 사다리로 묻고, 이름은\n * 고른 뒤에 보여 준다.\n *\n * 윗 칸이 아랫 칸을 포함한다(`FlowNode` 가 나머지 셋을 스스로 흡수한다). 그래서 사다리이지\n * 체크상자가 아니다.\n */\n\n/** 사다리 한 칸 — 화면이 묻는 말과, 그것이 되는 능력 이름. */\ntype Rung = 'none' | 'hold' | 'flow'\n\n/*\n 칸마다의 이름은 **형식의 능력 이름**이다. `string[]` 으로 두면 사다리에 오타를 적어도\n 타입이 통과하고, 그 도형은 발행까지 지난 뒤 씬에서 죽는다 — `hoist` 가 그렇게 나갔다\n (ADR-0051 ④). `as const` 로 덮지 않는 이유도 같다: 그건 좁히는 것이 아니라 가리는 것이다.\n*/\nconst RUNGS: Readonly<Record<Rung, readonly FigureCapability[]>> = Object.freeze({\n none: [],\n hold: ['Capacity', 'Holdable'],\n flow: ['FlowNode']\n})\n\n/** 선언된 이름 집합이 사다리 어느 칸인가. 어느 칸도 아니면 `null` — 지어내지 않는다. */\nfunction rungOf(names: readonly string[] | undefined): Rung | null {\n const has = new Set(names ?? [])\n if (has.size === 0) return 'none'\n\n for (const [rung, wanted] of Object.entries(RUNGS) as [Rung, readonly FigureCapability[]][]) {\n if (wanted.length === has.size && wanted.every(name => has.has(name))) return rung\n }\n return null\n}\n\n@customElement('figure-capabilities')\nexport class FigureCapabilities extends localize(i18next)(LitElement) {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow-y: auto;\n background-color: var(--md-sys-color-surface-container-lowest);\n color: var(--md-sys-color-on-surface);\n font: var(--label-font, inherit);\n font-size: 0.76rem;\n }\n\n section {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n padding: var(--spacing-large, 12px);\n border-bottom: 1px solid var(--md-sys-color-outline-variant);\n }\n\n /* 다른 탭들과 같은 눈금이다 — 탭마다 다른 제목 규격을 쓰면 따로 논다 */\n h3 {\n margin: 0 0 2px 0;\n font-size: var(--ui-t-micro, 11px);\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n color: var(--md-sys-color-on-surface-variant, var(--md-sys-color-on-surface));\n }\n\n p[quiet] {\n margin: 0;\n font-size: 0.72rem;\n line-height: 1.5;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 사다리 — 고르는 것이 하나라 라디오다 */\n label[rung] {\n display: flex;\n align-items: flex-start;\n gap: var(--spacing-medium, 8px);\n padding: 6px 8px;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n label[rung][on] {\n border-color: var(--md-sys-color-primary);\n background-color: var(--md-sys-color-surface-container);\n }\n label[rung]:hover:not([on]) {\n background-color: var(--md-sys-color-surface-container-low);\n }\n label[rung] span {\n display: flex;\n flex-direction: column;\n gap: 1px;\n }\n label[rung] strong {\n font-weight: 600;\n }\n label[rung] em {\n font-style: normal;\n font-size: 0.7rem;\n line-height: 1.4;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n input[type='radio'] {\n flex: none;\n margin: 2px 0 0 0;\n accent-color: var(--md-sys-color-primary);\n cursor: pointer;\n }\n\n /* 실제로 씬에 붙는 이름. 고른 뒤에 보여 준다 */\n code[names] {\n font: var(--mono-font, ui-monospace, monospace);\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 지정된 부품 한 줄 — 읽는 것이지 고치는 것이 아니다. 눌러 그 부품으로 간다 */\n button[marked] {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-medium, 8px);\n width: 100%;\n padding: 5px 8px;\n text-align: left;\n font: inherit;\n color: inherit;\n background: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n cursor: pointer;\n }\n button[marked]:hover {\n background-color: var(--md-sys-color-surface-container);\n }\n button[marked] b {\n flex: none;\n font-weight: 600;\n }\n button[marked] span {\n flex: none;\n font-size: 0.72rem;\n color: var(--md-sys-color-primary);\n }\n button[marked] em {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n text-align: right;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n /* 부품마다 한 줄. 자리를 고른 것만 아래로 펼친다 */\n div[part] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 6px 0;\n border-top: 1px solid var(--md-sys-color-outline-variant);\n }\n div[part]:first-of-type {\n border-top: none;\n }\n div[head] {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n div[head] b {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n }\n\n select,\n input[type='text'],\n input[type='number'] {\n padding: 4px 6px;\n font: var(--input-field-font, inherit);\n font-size: 0.74rem;\n color: var(--md-sys-color-on-surface);\n background-color: var(--md-sys-color-surface);\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: 4px;\n }\n /* 이름이 길어질 수 있는 쪽(문)까지 안 잘리는 폭이다 */\n div[head] select {\n flex: none;\n width: 8.5em;\n }\n\n div[detail] {\n display: grid;\n grid-template-columns: 4.5em 1fr;\n align-items: center;\n gap: 4px var(--spacing-medium, 8px);\n padding-left: var(--spacing-medium, 8px);\n }\n div[detail] > span {\n font-size: 0.72rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n div[detail] input {\n width: 100%;\n box-sizing: border-box;\n }\n div[detail] em {\n grid-column: 2;\n font-style: normal;\n font-size: 0.7rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n `\n ]\n\n @property({ type: Object }) draft?: FigureDraft\n @property({ type: Array }) parts: PartModel[] = []\n\n render() {\n const rung = rungOf(this.draft?.capabilities)\n const names = this.draft?.capabilities ?? []\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.what-this-does')}</h3>\n\n ${this.rung('none', rung)} ${this.rung('hold', rung)} ${this.rung('flow', rung)}\n ${rung === null\n ? html`<p quiet>${i18next.t('figure.text.capability-hand-written')}</p>`\n : ''}\n ${names.length > 0 ? html`<code names>${names.join(' · ')}</code>` : ''}\n </section>\n\n ${this.renderAnchors(rung)}\n `\n }\n\n /**\n * 자리와 문 — **여기서는 읽기만 한다.**\n *\n * 지정은 속성 탭에서 한다. 부품을 고치는 곳이 한 군데여야 하기 때문이다 — 목록을 여기 또\n * 두면 같은 부품을 두 화면에서 고치게 되고, 저작자는 어느 쪽이 이기는지 매번 생각해야 한다.\n *\n * 그래도 요약은 남긴다. 「어느 부품이 자리인가」는 부품을 하나씩 눌러 보며 알아낼 일이\n * 아니다 — 자리는 그려지지 않으므로 화면을 봐서는 영영 못 찾는다.\n */\n private renderAnchors(rung: Rung | null) {\n if (rung === 'none') {\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.slots-need-capability')}</p>\n </section>\n `\n }\n\n const marked = this.parts.filter(part => part.capability)\n\n return html`\n <section>\n <h3>${i18next.t('figure.label.slots-and-ports')}</h3>\n <p quiet>${i18next.t('figure.text.anchors-are-not-drawn')}</p>\n\n ${marked.length === 0\n ? html`<p quiet>${i18next.t('figure.text.designate-in-properties')}</p>`\n : marked.map(part => this.renderMarked(part))}\n </section>\n `\n }\n\n /** 지정된 부품 한 줄. 누르면 그 부품이 골라진다 — 고치는 자리는 속성 탭이다. */\n private renderMarked(part: PartModel) {\n const spec = part.capability!\n\n return html`\n <button marked @click=${() => this.pick(part)}>\n <b>${part.name}</b>\n <span>${spec.roles.map(role => i18next.t('figure.label.role-' + role)).join(' · ')}</span>\n ${spec.accepts?.length ? html`<em>${spec.accepts.join(' · ')}</em>` : ''}\n </button>\n `\n }\n\n /** 이 부품을 고르게 한다. 부품 목록에서 누른 것과 같은 알림이다. */\n private pick(part: PartModel) {\n const index = this.parts.indexOf(part)\n if (index < 0) return\n\n this.dispatchEvent(new CustomEvent('select-part', { detail: { index }, bubbles: true, composed: true }))\n }\n\n /**\n * 사다리 한 칸.\n *\n * `checked` 를 `live()` 로 묶는다. 그냥 두면 Lit 이 **마지막으로 그린 값**과 견주므로,\n * 브라우저가 DOM 을 바꿨는데 모델은 안 바뀐 경우 되돌리지 않는다. 실제로 그랬다 — 고르는\n * 도중 도형이 다시 실렸더니 점은 「흐른다」에 있고 강조 상자는 「그리기만 한다」에 있었다.\n * 화면이 모델과 다른 말을 하는 상태다. `live()` 는 **지금 DOM 의 값**과 견준다.\n */\n private rung(which: Rung, chosen: Rung | null) {\n return html`\n <label rung ?on=${chosen === which}>\n <input\n type=\"radio\"\n name=\"rung\"\n .checked=${live(chosen === which)}\n @change=${() => this.setRung(which)}\n />\n <span>\n <strong>${i18next.t(`figure.label.rung-${which}`)}</strong>\n <em>${i18next.t(`figure.text.rung-${which}-note`)}</em>\n </span>\n </label>\n `\n }\n\n /**\n * 사다리를 내리면 자리도 함께 거둔다.\n *\n * 능력을 「그리기만 한다」로 되돌려 놓고 부품에 자리 표시가 남아 있으면, 형식은 자리를\n * 그대로 실어 보내는데 씬은 그것을 쓸 클래스가 없다. **선언은 있고 길이 없는** 상태가\n * 되고, 그 도형은 멀쩡해 보이면서 아무것도 못 받는다.\n */\n private setRung(which: Rung) {\n const draft = this.draft\n if (!draft) return\n\n this.tellDraft({ ...draft, capabilities: RUNGS[which].length > 0 ? [...RUNGS[which]] : undefined })\n\n if (which === 'none') {\n this.parts.forEach((part, index) => {\n if (!part.capability) return\n const { capability, ...rest } = part\n this.dispatchEvent(\n new CustomEvent('update-part', { detail: { index, part: rest }, bubbles: true, composed: true })\n )\n })\n }\n }\n\n private tellDraft(draft: FigureDraft) {\n this.draft = draft\n this.dispatchEvent(new CustomEvent('update-draft', { detail: { draft }, bubbles: true, composed: true }))\n }\n}\n"]}