@things-factory/figure-ui 10.1.43 → 10.1.45

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 (128) hide show
  1. package/client/modeller/figure-v3-links.ts +303 -0
  2. package/client/modeller/modeller-route.ts +6 -21
  3. package/client/modeller/v3-editor.ts +44 -3
  4. package/client/modeller/v3-gizmo.ts +13 -2
  5. package/client/modeller/v3-links.ts +138 -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 +42 -0
  9. package/dist-client/modeller/figure-v3-links.js +306 -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 +46 -2
  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 +54 -0
  20. package/dist-client/modeller/v3-links.js +110 -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.tsbuildinfo +1 -1
  29. package/package.json +4 -4
  30. package/test/ai-proposal-contract.test.ts +0 -88
  31. package/test/figure-source.test.ts +0 -102
  32. package/test/i18n-prefix-guard.test.ts +0 -24
  33. package/test/modeller-route.test.ts +14 -25
  34. package/test/part-edits.test.ts +0 -7
  35. package/test/scene-budget.test.ts +0 -7
  36. package/test/v3-gizmo.test.ts +10 -0
  37. package/test/v3-links.test.ts +123 -0
  38. package/things-factory.config.js +2 -5
  39. package/translations/en.json +6 -0
  40. package/translations/ja.json +11 -1
  41. package/translations/ko.json +6 -0
  42. package/translations/ms.json +11 -1
  43. package/translations/zh.json +11 -1
  44. package/client/modeller/angle-dial.ts +0 -70
  45. package/client/modeller/base-box.ts +0 -175
  46. package/client/modeller/figure-animations.ts +0 -1249
  47. package/client/modeller/figure-canvas.ts +0 -567
  48. package/client/modeller/figure-capabilities.ts +0 -379
  49. package/client/modeller/figure-inspector.ts +0 -2208
  50. package/client/modeller/figure-parts.ts +0 -479
  51. package/client/modeller/figure-preview.ts +0 -598
  52. package/client/modeller/figure-report.ts +0 -966
  53. package/client/modeller/figure-settings.ts +0 -328
  54. package/client/modeller/figure-side.ts +0 -272
  55. package/client/modeller/figure-thumbnail.ts +0 -123
  56. package/client/modeller/figure-view.ts +0 -152
  57. package/client/modeller/joint-edits.ts +0 -262
  58. package/client/modeller/joint-gizmo.ts +0 -180
  59. package/client/modeller/maturity.ts +0 -288
  60. package/client/modeller/new-figure-entry.ts +0 -4
  61. package/client/modeller/pose-defaults.ts +0 -67
  62. package/client/pages/figure-modeller-page.ts +0 -2082
  63. package/dist-client/modeller/angle-dial.d.ts +0 -32
  64. package/dist-client/modeller/angle-dial.js +0 -41
  65. package/dist-client/modeller/angle-dial.js.map +0 -1
  66. package/dist-client/modeller/base-box.d.ts +0 -71
  67. package/dist-client/modeller/base-box.js +0 -110
  68. package/dist-client/modeller/base-box.js.map +0 -1
  69. package/dist-client/modeller/figure-animations.d.ts +0 -159
  70. package/dist-client/modeller/figure-animations.js +0 -1132
  71. package/dist-client/modeller/figure-animations.js.map +0 -1
  72. package/dist-client/modeller/figure-canvas.d.ts +0 -150
  73. package/dist-client/modeller/figure-canvas.js +0 -506
  74. package/dist-client/modeller/figure-canvas.js.map +0 -1
  75. package/dist-client/modeller/figure-capabilities.d.ts +0 -42
  76. package/dist-client/modeller/figure-capabilities.js +0 -349
  77. package/dist-client/modeller/figure-capabilities.js.map +0 -1
  78. package/dist-client/modeller/figure-inspector.d.ts +0 -329
  79. package/dist-client/modeller/figure-inspector.js +0 -2095
  80. package/dist-client/modeller/figure-inspector.js.map +0 -1
  81. package/dist-client/modeller/figure-parts.d.ts +0 -64
  82. package/dist-client/modeller/figure-parts.js +0 -478
  83. package/dist-client/modeller/figure-parts.js.map +0 -1
  84. package/dist-client/modeller/figure-preview.d.ts +0 -138
  85. package/dist-client/modeller/figure-preview.js +0 -549
  86. package/dist-client/modeller/figure-preview.js.map +0 -1
  87. package/dist-client/modeller/figure-report.d.ts +0 -165
  88. package/dist-client/modeller/figure-report.js +0 -952
  89. package/dist-client/modeller/figure-report.js.map +0 -1
  90. package/dist-client/modeller/figure-settings.d.ts +0 -39
  91. package/dist-client/modeller/figure-settings.js +0 -317
  92. package/dist-client/modeller/figure-settings.js.map +0 -1
  93. package/dist-client/modeller/figure-side.d.ts +0 -78
  94. package/dist-client/modeller/figure-side.js +0 -317
  95. package/dist-client/modeller/figure-side.js.map +0 -1
  96. package/dist-client/modeller/figure-thumbnail.d.ts +0 -3
  97. package/dist-client/modeller/figure-thumbnail.js +0 -95
  98. package/dist-client/modeller/figure-thumbnail.js.map +0 -1
  99. package/dist-client/modeller/figure-view.d.ts +0 -118
  100. package/dist-client/modeller/figure-view.js +0 -78
  101. package/dist-client/modeller/figure-view.js.map +0 -1
  102. package/dist-client/modeller/joint-edits.d.ts +0 -49
  103. package/dist-client/modeller/joint-edits.js +0 -223
  104. package/dist-client/modeller/joint-edits.js.map +0 -1
  105. package/dist-client/modeller/joint-gizmo.d.ts +0 -34
  106. package/dist-client/modeller/joint-gizmo.js +0 -158
  107. package/dist-client/modeller/joint-gizmo.js.map +0 -1
  108. package/dist-client/modeller/maturity.d.ts +0 -55
  109. package/dist-client/modeller/maturity.js +0 -212
  110. package/dist-client/modeller/maturity.js.map +0 -1
  111. package/dist-client/modeller/new-figure-entry.d.ts +0 -2
  112. package/dist-client/modeller/new-figure-entry.js +0 -5
  113. package/dist-client/modeller/new-figure-entry.js.map +0 -1
  114. package/dist-client/modeller/pose-defaults.d.ts +0 -9
  115. package/dist-client/modeller/pose-defaults.js +0 -35
  116. package/dist-client/modeller/pose-defaults.js.map +0 -1
  117. package/dist-client/pages/figure-modeller-page.d.ts +0 -419
  118. package/dist-client/pages/figure-modeller-page.js +0 -2060
  119. package/dist-client/pages/figure-modeller-page.js.map +0 -1
  120. package/test/angle-dial.test.ts +0 -78
  121. package/test/base-box.test.ts +0 -235
  122. package/test/joint-edits.test.ts +0 -165
  123. package/test/joint-gizmo.test.ts +0 -174
  124. package/test/joint-needle.test.ts +0 -162
  125. package/test/modeller-undo-redo.test.ts +0 -33
  126. package/test/new-figure-entry.test.ts +0 -15
  127. package/test/pose-defaults.test.ts +0 -71
  128. 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"]}