@things-factory/figure-ui 10.1.45 → 10.1.47

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 (138) hide show
  1. package/client/modeller/angle-dial.ts +70 -0
  2. package/client/modeller/figure-settings.ts +328 -0
  3. package/client/modeller/figure-v3-capability.ts +137 -0
  4. package/client/modeller/figure-v3-clips.ts +253 -0
  5. package/client/modeller/figure-v3-controls.ts +212 -0
  6. package/client/modeller/figure-v3-links.ts +150 -79
  7. package/client/modeller/figure-v3-material.ts +108 -35
  8. package/client/modeller/figure-v3-parts.ts +96 -3
  9. package/client/modeller/figure-v3-preview.ts +326 -0
  10. package/client/modeller/figure-v3-properties.ts +29 -57
  11. package/client/modeller/figure-v3-verdict.ts +598 -60
  12. package/client/modeller/figure-view.ts +152 -0
  13. package/client/modeller/side-panel-styles.ts +326 -0
  14. package/client/modeller/v3-canvas.ts +221 -263
  15. package/client/modeller/v3-clips.ts +89 -0
  16. package/client/modeller/v3-controls.ts +54 -0
  17. package/client/modeller/v3-editor.ts +532 -223
  18. package/client/modeller/v3-fields.ts +62 -0
  19. package/client/modeller/v3-help.ts +41 -0
  20. package/client/modeller/v3-keys.ts +34 -0
  21. package/client/modeller/v3-links.ts +126 -10
  22. package/client/modeller/v3-live.ts +79 -0
  23. package/client/modeller/v3-maturity.ts +415 -0
  24. package/client/modeller/v3-palette.ts +45 -0
  25. package/client/modeller/v3-preview-model.ts +54 -0
  26. package/client/modeller/v3-stage-model.ts +159 -0
  27. package/client/pages/figure-list-page.ts +4 -4
  28. package/client/pages/figure-v3-modeller-page.ts +66 -134
  29. package/dist-client/modeller/angle-dial.d.ts +32 -0
  30. package/dist-client/modeller/angle-dial.js +41 -0
  31. package/dist-client/modeller/angle-dial.js.map +1 -0
  32. package/dist-client/modeller/figure-settings.d.ts +39 -0
  33. package/dist-client/modeller/figure-settings.js +317 -0
  34. package/dist-client/modeller/figure-settings.js.map +1 -0
  35. package/dist-client/modeller/figure-v3-capability.d.ts +22 -0
  36. package/dist-client/modeller/figure-v3-capability.js +141 -0
  37. package/dist-client/modeller/figure-v3-capability.js.map +1 -0
  38. package/dist-client/modeller/figure-v3-clips.d.ts +31 -0
  39. package/dist-client/modeller/figure-v3-clips.js +268 -0
  40. package/dist-client/modeller/figure-v3-clips.js.map +1 -0
  41. package/dist-client/modeller/figure-v3-controls.d.ts +36 -0
  42. package/dist-client/modeller/figure-v3-controls.js +226 -0
  43. package/dist-client/modeller/figure-v3-controls.js.map +1 -0
  44. package/dist-client/modeller/figure-v3-links.d.ts +10 -2
  45. package/dist-client/modeller/figure-v3-links.js +134 -79
  46. package/dist-client/modeller/figure-v3-links.js.map +1 -1
  47. package/dist-client/modeller/figure-v3-material.d.ts +5 -2
  48. package/dist-client/modeller/figure-v3-material.js +109 -35
  49. package/dist-client/modeller/figure-v3-material.js.map +1 -1
  50. package/dist-client/modeller/figure-v3-parts.d.ts +10 -0
  51. package/dist-client/modeller/figure-v3-parts.js +88 -3
  52. package/dist-client/modeller/figure-v3-parts.js.map +1 -1
  53. package/dist-client/modeller/figure-v3-preview.d.ts +34 -0
  54. package/dist-client/modeller/figure-v3-preview.js +322 -0
  55. package/dist-client/modeller/figure-v3-preview.js.map +1 -0
  56. package/dist-client/modeller/figure-v3-properties.d.ts +2 -2
  57. package/dist-client/modeller/figure-v3-properties.js +29 -57
  58. package/dist-client/modeller/figure-v3-properties.js.map +1 -1
  59. package/dist-client/modeller/figure-v3-verdict.d.ts +31 -17
  60. package/dist-client/modeller/figure-v3-verdict.js +579 -59
  61. package/dist-client/modeller/figure-v3-verdict.js.map +1 -1
  62. package/dist-client/modeller/figure-view.d.ts +118 -0
  63. package/dist-client/modeller/figure-view.js +78 -0
  64. package/dist-client/modeller/figure-view.js.map +1 -0
  65. package/dist-client/modeller/side-panel-styles.d.ts +23 -0
  66. package/dist-client/modeller/side-panel-styles.js +326 -0
  67. package/dist-client/modeller/side-panel-styles.js.map +1 -0
  68. package/dist-client/modeller/v3-canvas.d.ts +29 -37
  69. package/dist-client/modeller/v3-canvas.js +188 -260
  70. package/dist-client/modeller/v3-canvas.js.map +1 -1
  71. package/dist-client/modeller/v3-clips.d.ts +39 -0
  72. package/dist-client/modeller/v3-clips.js +58 -0
  73. package/dist-client/modeller/v3-clips.js.map +1 -0
  74. package/dist-client/modeller/v3-controls.d.ts +28 -0
  75. package/dist-client/modeller/v3-controls.js +28 -0
  76. package/dist-client/modeller/v3-controls.js.map +1 -0
  77. package/dist-client/modeller/v3-editor.d.ts +58 -1
  78. package/dist-client/modeller/v3-editor.js +556 -218
  79. package/dist-client/modeller/v3-editor.js.map +1 -1
  80. package/dist-client/modeller/v3-fields.d.ts +30 -0
  81. package/dist-client/modeller/v3-fields.js +46 -0
  82. package/dist-client/modeller/v3-fields.js.map +1 -0
  83. package/dist-client/modeller/v3-help.d.ts +18 -0
  84. package/dist-client/modeller/v3-help.js +37 -0
  85. package/dist-client/modeller/v3-help.js.map +1 -0
  86. package/dist-client/modeller/v3-keys.d.ts +14 -0
  87. package/dist-client/modeller/v3-keys.js +23 -0
  88. package/dist-client/modeller/v3-keys.js.map +1 -0
  89. package/dist-client/modeller/v3-links.d.ts +44 -2
  90. package/dist-client/modeller/v3-links.js +125 -8
  91. package/dist-client/modeller/v3-links.js.map +1 -1
  92. package/dist-client/modeller/v3-live.d.ts +31 -0
  93. package/dist-client/modeller/v3-live.js +58 -0
  94. package/dist-client/modeller/v3-live.js.map +1 -0
  95. package/dist-client/modeller/v3-maturity.d.ts +120 -0
  96. package/dist-client/modeller/v3-maturity.js +317 -0
  97. package/dist-client/modeller/v3-maturity.js.map +1 -0
  98. package/dist-client/modeller/v3-palette.d.ts +7 -0
  99. package/dist-client/modeller/v3-palette.js +42 -0
  100. package/dist-client/modeller/v3-palette.js.map +1 -0
  101. package/dist-client/modeller/v3-preview-model.d.ts +32 -0
  102. package/dist-client/modeller/v3-preview-model.js +48 -0
  103. package/dist-client/modeller/v3-preview-model.js.map +1 -0
  104. package/dist-client/modeller/v3-stage-model.d.ts +31 -0
  105. package/dist-client/modeller/v3-stage-model.js +121 -0
  106. package/dist-client/modeller/v3-stage-model.js.map +1 -0
  107. package/dist-client/pages/figure-list-page.js +4 -4
  108. package/dist-client/pages/figure-list-page.js.map +1 -1
  109. package/dist-client/pages/figure-v3-modeller-page.d.ts +2 -6
  110. package/dist-client/pages/figure-v3-modeller-page.js +64 -136
  111. package/dist-client/pages/figure-v3-modeller-page.js.map +1 -1
  112. package/dist-client/tsconfig.chk.tsbuildinfo +1 -0
  113. package/dist-client/tsconfig.scratch-fm.tsbuildinfo +1 -0
  114. package/dist-client/tsconfig.tsbuildinfo +1 -1
  115. package/package.json +6 -6
  116. package/test/v3-clips.test.ts +70 -0
  117. package/test/v3-controls.test.ts +68 -0
  118. package/test/v3-fields.test.ts +62 -0
  119. package/test/v3-gizmo.test.ts +3 -5
  120. package/test/v3-keys.test.ts +37 -0
  121. package/test/v3-layout.test.ts +78 -0
  122. package/test/v3-links.test.ts +118 -7
  123. package/test/v3-live.test.ts +99 -0
  124. package/test/v3-material.test.ts +6 -0
  125. package/test/v3-maturity.test.ts +268 -0
  126. package/test/v3-parts-wiring.test.ts +3 -1
  127. package/test/v3-preview.test.ts +42 -0
  128. package/test/v3-properties.test.ts +4 -2
  129. package/test/v3-scene-canvas.test.ts +85 -0
  130. package/test/v3-stage-model.test.ts +25 -0
  131. package/test/v3-theme.test.ts +16 -25
  132. package/test/v3-verdict.test.ts +12 -6
  133. package/test/v3-view.test.ts +20 -0
  134. package/translations/en.json +98 -5
  135. package/translations/ja.json +100 -7
  136. package/translations/ko.json +98 -5
  137. package/translations/ms.json +100 -7
  138. package/translations/zh.json +100 -7
@@ -1,6 +1,8 @@
1
+ var FigureV3Editor_1;
1
2
  import { __decorate, __metadata } from "tslib";
2
- import { LitElement, html, css } from 'lit';
3
+ import { LitElement, html, css, nothing } from 'lit';
3
4
  import { i18next } from '@operato/i18n';
5
+ import '@operato/layout';
4
6
  import { customElement, property, state } from 'lit/decorators.js';
5
7
  import { captureV3Thumbnail } from './v3-thumbnail.js';
6
8
  import { drawableV3Parts } from './v3-renderer.js';
@@ -8,33 +10,20 @@ import './v3-canvas.js';
8
10
  import './figure-v3-parts.js';
9
11
  import './figure-v3-material.js';
10
12
  import './figure-v3-links.js';
13
+ import './figure-v3-controls.js';
14
+ import './figure-v3-clips.js';
15
+ import './figure-v3-preview.js';
16
+ import './figure-settings.js';
17
+ import { DEFAULT_VIEW } from './figure-view.js';
18
+ import './figure-v3-capability.js';
11
19
  import { gizmoChanges, gizmoReachOf } from './v3-gizmo.js';
20
+ import { keyActionOf } from './v3-keys.js';
21
+ import { groupFields } from './v3-fields.js';
22
+ import { SidePanelStyles } from './side-panel-styles.js';
12
23
  import { alignChanges, boxOf, canMirror, mirrorChanges } from './v3-align.js';
13
- const names = {
14
- position: '위치',
15
- rotation: '회전',
16
- size: '크기',
17
- dimension: '치수',
18
- width: '너비',
19
- height: '높이',
20
- depth: '깊이',
21
- radius: '반지름',
22
- length: '길이',
23
- round: '모서리 반경',
24
- left: '왼쪽',
25
- right: '오른쪽',
26
- bottom: '아랫면',
27
- top: '윗면',
28
- openingLeft: '개구부 왼쪽',
29
- openingRight: '개구부 오른쪽',
30
- openingTop: '개구부 윗면'
31
- };
32
- function fieldLabel(id) {
33
- return id
34
- .split('.')
35
- .map(p => names[p] ?? p.toUpperCase())
36
- .join(' · ');
37
- }
24
+ import { paletteRecipe } from './v3-palette.js';
25
+ import { instanceRangeOf, rangeDiffers } from './v3-links.js';
26
+ import { isLive, livePartsOf, NOTHING_LIVE, scrubbed, scrubStep } from './v3-live.js';
38
27
  /** Product editor surface. The injected session is shared by page persistence and AI commands. */
39
28
  let FigureV3Editor = class FigureV3Editor extends LitElement {
40
29
  constructor() {
@@ -42,69 +31,193 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
42
31
  this.revision = 0;
43
32
  this.selected = '';
44
33
  this.error = '';
34
+ this.tab = 'properties';
35
+ /** What the middle shows: the parts being edited, or the figure placed on a board at eight sizes (V2's two modes). */
36
+ this.middle = 'edit';
37
+ /**
38
+ * How the workbench shows the figure -- V2's view settings, the board's own state names. The viewer's, kept in
39
+ * their browser, never in the figure.
40
+ */
41
+ this.view = FigureV3Editor_1.storedView();
45
42
  /**
46
43
  * Parts hidden on the workbench — the author's view while working, not the asset's visibility. Not saved, as in
47
44
  * the V2 modeller: a part hidden here is still in the figure, still checked, still drawn on a board.
48
45
  */
49
46
  this.hiddenParts = new Set();
47
+ /** A value in hand -- typed, held on a spinner, scrubbed, dragged -- drawn at once and recorded on release. */
48
+ this.live = NOTHING_LIVE;
50
49
  this.lastContext = null;
50
+ /** The modeller's keys (`v3-keys.ts`): undo, redo, duplicate, remove, back to the whole figure. */
51
+ this.onKey = (e) => {
52
+ const view = this.session?.read();
53
+ if (!view || view.loading || view.saving || !view.context || !this.isConnected)
54
+ return;
55
+ const action = keyActionOf({ key: e.key, ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey, altKey: e.altKey, target: e.composedPath()[0] }, this.selected);
56
+ if (!action)
57
+ return;
58
+ e.preventDefault();
59
+ if (action === 'undo')
60
+ view.canUndo && this.run(() => this.session.undo());
61
+ else if (action === 'redo')
62
+ view.canRedo && this.run(() => this.session.redo());
63
+ else if (action === 'duplicate')
64
+ this.duplicatePart(this.selected);
65
+ else if (action === 'remove')
66
+ this.removePart(this.selected);
67
+ else
68
+ this.selected = '';
69
+ };
51
70
  }
52
- static { this.styles = css `
71
+ static { FigureV3Editor_1 = this; }
72
+ static { this.styles = [SidePanelStyles, css `
53
73
  :host {
54
74
  display: flex;
55
75
  flex-direction: column;
56
76
  min-height: 0;
57
77
  height: 100%;
58
- /*
59
- The editor folds by the width it is given, not the window's. The rail (208 open, 56 closed) and the page's
60
- 320 side take their share first, so a window rule was wrong whenever either was there: at 945 with the rail
61
- open the editor had 417 and still drew its 660 of columns, over the side (UX ruling, 2026-09-24).
62
- */
78
+ /* The editor folds by the width it is given, not the window's (UX ruling, 2026-09-24). */
63
79
  container-type: inline-size;
64
80
  font: 13px sans-serif;
65
- color: var(--md-sys-color-on-surface, #22343f);
81
+ color: var(--md-sys-color-on-surface);
82
+ }
83
+ /* The bar over the work is ox-canvas-bar (ADR-0060 decision 8): the folds at its edges, undo, redo and
84
+ fit in the suite's places. What the editor puts in it - the mode switch, the status, save - is styled here. */
85
+ ox-canvas-bar button[mode] {
86
+ height: 28px;
87
+ padding: 0 10px;
88
+ border-radius: 999em;
89
+ border-color: transparent;
90
+ background: none;
91
+ }
92
+ ox-canvas-bar button[mode][aria-pressed='true'] {
93
+ border-color: var(--md-sys-color-primary);
66
94
  }
67
- header {
68
- display: flex;
69
- align-items: center;
70
- gap: 8px;
71
- padding: 10px;
72
- border-bottom: 1px solid var(--md-sys-color-outline-variant);
73
- flex-wrap: wrap;
95
+ figure-v3-preview {
96
+ min-width: 0;
97
+ min-height: 0;
98
+ }
99
+ ox-canvas-bar .status {
100
+ padding: 0 8px;
101
+ color: var(--md-sys-color-on-surface-variant);
102
+ }
103
+ ox-canvas-bar .status[dirty] {
104
+ color: var(--md-sys-color-primary);
105
+ font-weight: 600;
74
106
  }
75
- main {
107
+ ox-canvas-bar button[save] {
108
+ height: 32px;
109
+ padding: 0 14px;
110
+ border: none;
111
+ border-radius: 999em;
112
+ color: var(--md-sys-color-on-primary);
113
+ background: var(--md-sys-color-primary);
114
+ }
115
+ /* The three panes are ox-three-pane's (ADR-0060 decision 8): widths 200 | 1fr | 340, 160 | 300 under
116
+ 860px and one column with the middle first under 560px are the frame's, as are the dividers. */
117
+ ox-three-pane {
76
118
  flex: 1;
77
119
  min-height: 0;
78
- display: grid;
79
- grid-template-columns: 180px minmax(180px, 1fr) 300px;
80
120
  }
121
+ figure-v3-parts {
122
+ min-height: 0;
123
+ }
124
+ figure-v3-canvas {
125
+ min-width: 0;
126
+ min-height: 0;
127
+ }
128
+ /* The side: tabs over one panel at a time, each using the whole height (V2's side, and why it had tabs). */
81
129
  aside {
82
- padding: 12px;
83
- overflow: auto;
84
- background: var(--md-sys-color-surface, #fff);
130
+ display: flex;
131
+ flex-direction: column;
132
+ min-height: 0;
133
+ background: var(--md-sys-color-surface-container-lowest);
85
134
  }
86
- figure-v3-parts {
135
+ div[tabs] {
136
+ display: flex;
137
+ flex: none;
138
+ border-bottom: 1px solid var(--md-sys-color-outline-variant);
139
+ }
140
+ button[tab] {
141
+ flex: 1;
142
+ position: relative;
143
+ display: flex;
144
+ align-items: center;
145
+ justify-content: center;
146
+ height: 38px;
147
+ padding: 0;
148
+ border: none;
149
+ border-radius: 0;
150
+ border-bottom: 2px solid transparent;
151
+ background: none;
152
+ color: var(--md-sys-color-on-surface-variant);
153
+ }
154
+ button[tab]:hover:not([on]) {
155
+ background: var(--md-sys-color-surface-container);
156
+ }
157
+ button[tab][on] {
158
+ color: var(--md-sys-color-primary);
159
+ border-bottom-color: var(--md-sys-color-primary);
160
+ }
161
+ button[tab] md-icon {
162
+ font-size: 20px;
163
+ width: 20px;
164
+ height: 20px;
165
+ }
166
+ span[badge] {
167
+ position: absolute;
168
+ top: 3px;
169
+ right: 2px;
170
+ padding: 0 5px;
171
+ font-size: 0.62rem;
172
+ font-weight: 700;
173
+ line-height: 1.5;
174
+ color: var(--md-sys-color-on-primary-container);
175
+ background: var(--md-sys-color-primary-container);
176
+ border-radius: 999em;
177
+ pointer-events: none;
178
+ }
179
+ /* The sections inside carry the margins (side-panel-styles); the panel only scrolls. */
180
+ div[panel] {
181
+ flex: 1;
87
182
  min-height: 0;
183
+ overflow: auto;
184
+ padding: 0;
88
185
  }
89
- aside:last-child {
90
- border-left: 1px solid var(--md-sys-color-outline-variant);
186
+ div[panel][flush] {
187
+ padding: 0;
188
+ }
189
+ /* Hidden, not removed: a panel keeps what it holds (the verdict its last answer) while another tab is open. */
190
+ [off] {
191
+ display: none !important;
192
+ }
193
+ /* Which part the panel speaks of, and the way back to the whole figure. */
194
+ div[subject] {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 6px;
198
+ padding: 6px 10px 0;
91
199
  }
92
- h3 {
93
- font-size: var(--ui-t-body, 13px);
94
- margin: 22px 0 10px;
200
+ div[subject] button[chip] {
201
+ display: inline-flex;
202
+ align-items: center;
203
+ gap: 2px;
204
+ height: 26px;
205
+ padding: 0 10px 0 6px;
206
+ border-radius: 999em;
207
+ color: var(--md-sys-color-on-surface-variant);
95
208
  }
96
- h3:first-child {
97
- margin-top: 0;
209
+ div[subject] md-icon {
210
+ font-size: 16px;
211
+ width: 16px;
212
+ height: 16px;
98
213
  }
99
- button,
100
- input,
101
- select {
214
+ button {
102
215
  font: inherit;
103
216
  box-sizing: border-box;
104
- height: 34px;
217
+ height: 32px;
105
218
  border: 1px solid var(--md-sys-color-outline-variant);
106
219
  border-radius: 6px;
107
- background: var(--md-sys-color-surface, #fff);
220
+ background: var(--md-sys-color-surface);
108
221
  color: inherit;
109
222
  padding: 0 8px;
110
223
  min-width: 0;
@@ -121,94 +234,43 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
121
234
  color: var(--md-sys-color-on-secondary-container);
122
235
  border-color: var(--md-sys-color-primary);
123
236
  }
124
- label[name-field] {
125
- margin: 0 0 8px;
126
- }
127
- label[name-field] input {
128
- font-weight: 600;
129
- }
130
- div[tools] {
131
- display: flex;
132
- flex-wrap: wrap;
133
- gap: 4px;
134
- margin: 0 0 8px;
135
- }
136
- div[tools] button {
137
- display: inline-flex;
138
- align-items: center;
139
- justify-content: center;
140
- width: 34px;
141
- padding: 0;
142
- }
143
- div[tools] md-icon {
144
- font-size: 18px;
145
- width: 18px;
146
- height: 18px;
147
- }
148
- .parts {
149
- display: grid;
150
- gap: 6px;
151
- }
152
- .recipes {
153
- display: grid;
154
- grid-template-columns: 1fr 1fr;
155
- gap: 6px;
156
- }
157
- label {
158
- display: grid;
159
- grid-template-columns: 112px minmax(0, 1fr);
160
- align-items: center;
161
- gap: 8px;
162
- margin: 8px 0;
163
- }
164
- label span {
165
- overflow-wrap: anywhere;
166
- }
167
237
  .error {
168
238
  padding: 10px;
169
239
  background: var(--md-sys-color-error-container);
170
240
  color: var(--md-sys-color-on-error-container);
171
241
  white-space: pre-wrap;
172
242
  }
173
- .status {
174
- margin-left: auto;
175
- }
176
243
  .hint {
177
244
  color: var(--md-sys-color-on-surface-variant);
178
245
  line-height: 1.5;
179
246
  }
180
- /* 660 and over: parts | canvas | inputs. Below that the sum of the columns' least widths no longer fits. */
181
- @container (max-width: 659px) {
182
- main {
183
- grid-template-columns: 140px minmax(160px, 1fr);
184
- }
185
- aside:last-child {
186
- grid-column: 1/-1;
187
- max-height: 260px;
188
- border-left: none;
189
- border-top: 1px solid var(--md-sys-color-outline-variant);
190
- }
191
- }
192
- /* Under 420: one column. The canvas first — seeing what is being made comes before the lists — and main scrolls. */
193
- @container (max-width: 419px) {
194
- main {
195
- grid-template-columns: minmax(0, 1fr);
196
- grid-template-rows: minmax(240px, 45%);
197
- grid-auto-rows: auto;
198
- overflow-y: auto;
199
- }
200
- figure-v3-canvas {
201
- order: -1;
202
- }
203
- figure-v3-parts {
204
- border-right: none;
205
- border-top: 1px solid var(--md-sys-color-outline-variant);
206
- }
207
- aside:last-child {
208
- max-height: none;
209
- }
210
- }
211
- `; }
247
+ `]; }
248
+ static storedView() {
249
+ try {
250
+ const stored = JSON.parse(localStorage.getItem('figure-v3-view') ?? 'null');
251
+ return stored && typeof stored === 'object' ? { ...DEFAULT_VIEW, ...stored } : { ...DEFAULT_VIEW };
252
+ }
253
+ catch {
254
+ return { ...DEFAULT_VIEW };
255
+ }
256
+ }
257
+ setView(view) {
258
+ this.view = { ...DEFAULT_VIEW, ...view };
259
+ try {
260
+ localStorage.setItem('figure-v3-view', JSON.stringify(this.view));
261
+ }
262
+ catch {
263
+ /* A browser that keeps nothing still shows the change for this visit. */
264
+ }
265
+ }
266
+ connectedCallback() {
267
+ super.connectedCallback();
268
+ window.addEventListener('keydown', this.onKey);
269
+ }
270
+ disconnectedCallback() {
271
+ window.removeEventListener('keydown', this.onKey);
272
+ super.disconnectedCallback();
273
+ }
212
274
  willUpdate(_changed) {
213
275
  const view = this.session?.read();
214
276
  if (view?.context !== this.lastContext) {
@@ -229,6 +291,19 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
229
291
  catch (error) {
230
292
  this.error = error instanceof Error ? error.message : String(error);
231
293
  }
294
+ finally {
295
+ /* Committed or refused, the value in hand is gone: the figure as it stands is drawn again. */
296
+ this.live = NOTHING_LIVE;
297
+ }
298
+ }
299
+ /** What the canvas draws: the figure with the value in hand when there is one, and without the parts hidden on the workbench. */
300
+ drawnParts(geometry, asset) {
301
+ const parts = (isLive(this.live) && asset ? livePartsOf(asset, this.live) : null) ?? drawableV3Parts(geometry ?? []);
302
+ return parts.filter(body => !this.hiddenParts.has(body.id));
303
+ }
304
+ /** A value in hand: an input's value (design or pose) or a part edit. Nothing is recorded. */
305
+ hold(values, edit) {
306
+ this.live = { values: { ...this.live.values, ...values }, ...(edit ? { edit } : this.live.edit ? { edit: this.live.edit } : {}) };
232
307
  }
233
308
  changed() {
234
309
  this.requestUpdate();
@@ -265,28 +340,7 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
265
340
  let n = 1;
266
341
  while (ids.has(`${kind}-${n}`))
267
342
  n++;
268
- const common = { id: `${kind}-${n}`, position: { x: 0, y: 50, z: 0 } };
269
- const part = kind === 'box'
270
- ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100 } }
271
- : kind === 'cylinder'
272
- ? { ...common, kind, dimensions: { radius: 50, length: 100 } }
273
- : kind === 'rounded-box'
274
- ? { ...common, kind, dimensions: { width: 100, height: 100, depth: 100, round: 10 } }
275
- : {
276
- ...common,
277
- position: { x: 0, y: 0, z: 0 },
278
- kind: 'portal',
279
- dimensions: {
280
- left: -100,
281
- right: 100,
282
- bottom: 0,
283
- top: 200,
284
- openingLeft: -70,
285
- openingRight: 70,
286
- openingTop: 170,
287
- depth: 30
288
- }
289
- };
343
+ const part = paletteRecipe(kind, `${kind}-${n}`);
290
344
  this.session.add(part);
291
345
  this.selected = part.id;
292
346
  });
@@ -436,7 +490,7 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
436
490
  }
437
491
  }
438
492
  /** Size response, fastening and motion for the selected part (parity ④ ⑤ ⑥), as authoring commands. */
439
- renderLinks(unavailable) {
493
+ renderLinks(unavailable, section) {
440
494
  let asset;
441
495
  try {
442
496
  asset = JSON.parse(this.session.source());
@@ -453,12 +507,41 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
453
507
  }
454
508
  return html `<figure-v3-links
455
509
  .asset=${asset}
510
+ .section=${section}
456
511
  .partId=${this.selected}
457
512
  .dims=${dims}
458
513
  ?locked=${unavailable}
459
514
  @v3-command=${(e) => this.linkCommand(e.detail.action)}
515
+ @v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
460
516
  ></figure-v3-links>`;
461
517
  }
518
+ /** The figure's controls: try a value, name it, set its range and travel time, keep the pose (parity row ⑥). */
519
+ renderControls(view, unavailable) {
520
+ let asset;
521
+ try {
522
+ asset = JSON.parse(this.session.source());
523
+ }
524
+ catch {
525
+ return null;
526
+ }
527
+ const values = Object.fromEntries(view.fields.filter(f => f.role === 'state').map(f => [f.id, f.value]));
528
+ return html `<figure-v3-controls
529
+ .asset=${asset}
530
+ .values=${values}
531
+ ?locked=${unavailable}
532
+ @v3-state=${(e) => this.run(() => this.session.apply([{ kind: 'set-state', input: e.detail.input, unit: e.detail.unit, value: e.detail.value }]))}
533
+ @v3-state-live=${(e) => this.hold({ [e.detail.input]: e.detail.value })}
534
+ @v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
535
+ ></figure-v3-controls>
536
+ <figure-v3-clips
537
+ .asset=${asset}
538
+ .values=${values}
539
+ ?locked=${unavailable}
540
+ @v3-states=${(e) => this.run(() => this.session.apply(e.detail.changes))}
541
+ @v3-states-live=${(e) => this.hold(e.detail.values)}
542
+ @v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
543
+ ></figure-v3-clips>`;
544
+ }
462
545
  /** One command, one step of the history. A refusal a person can meet here is said in their words. */
463
546
  linkCommand(action) {
464
547
  try {
@@ -486,35 +569,170 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
486
569
  // Rejected edits must also restore the displayed numeric value.
487
570
  input.value = String(this.session.read().fields.find(i => i.id === field.id).value);
488
571
  }
572
+ /** Typing or a held spinner: each number on the way is drawn; the field's change records it. */
573
+ typing(field, event) {
574
+ const value = event.target.valueAsNumber;
575
+ if (Number.isFinite(value))
576
+ this.hold({ [field.id]: value });
577
+ }
578
+ /**
579
+ * Drag a field's label (or an axis letter) sideways to scrub its value, as vector tools do: the figure follows the
580
+ * pointer, and the release records one step. The field's number follows too, so the value in hand is always read.
581
+ */
582
+ scrub(field, down, unavailable) {
583
+ if (unavailable || down.button !== 0)
584
+ return;
585
+ const label = down.currentTarget;
586
+ const input = (label.closest('div[cell]')?.querySelector('input') ?? label.nextElementSibling);
587
+ if (!input)
588
+ return;
589
+ down.preventDefault();
590
+ label.setPointerCapture(down.pointerId);
591
+ const start = field.value;
592
+ let value = start;
593
+ const move = (e) => {
594
+ value = scrubbed(start, e.clientX - down.clientX, scrubStep(field.unit, start, e.shiftKey));
595
+ input.value = String(value);
596
+ this.hold({ [field.id]: value });
597
+ };
598
+ const up = (e) => {
599
+ label.removeEventListener('pointermove', move);
600
+ label.removeEventListener('pointerup', up);
601
+ label.removeEventListener('pointercancel', up);
602
+ /* A drag the system took away is not the author's value: the field goes back to where it was. */
603
+ if (e.type === 'pointercancel')
604
+ value = start;
605
+ if (value === start) {
606
+ input.value = String(start);
607
+ this.live = NOTHING_LIVE;
608
+ return;
609
+ }
610
+ input.value = String(value);
611
+ this.changeField(field, { target: input });
612
+ };
613
+ label.addEventListener('pointermove', move);
614
+ label.addEventListener('pointerup', up);
615
+ label.addEventListener('pointercancel', up);
616
+ }
617
+ /** The sizes a board may give the figure, and the way to measure them again after its parts changed. */
618
+ renderInstanceRange(asset, unavailable) {
619
+ if (!asset)
620
+ return nothing;
621
+ /* Another figure, or another step of this one, is measured again. */
622
+ const at = `${this.session?.read().context ?? ''}:${this.revision}`;
623
+ if (this.range?.at !== at) {
624
+ let of;
625
+ try {
626
+ of = instanceRangeOf(asset);
627
+ }
628
+ catch {
629
+ of = undefined;
630
+ }
631
+ this.range = { at, of };
632
+ }
633
+ const range = this.range.of;
634
+ if (!range)
635
+ return nothing;
636
+ const mm = (v) => Math.round(v).toLocaleString();
637
+ const differs = rangeDiffers(range.declared, range.measured);
638
+ return html `<section>
639
+ <h3>${i18next.t('figure.label.instance-range')}</h3>
640
+ ${['x', 'y', 'z'].map(axis => html `<div inline-field>
641
+ <label>${axis.toUpperCase()}</label><span>${mm(range.declared[axis].min)} – ${mm(range.declared[axis].max)} mm</span>
642
+ </div>`)}
643
+ ${differs
644
+ ? html `<p hint>
645
+ ${i18next.t('figure.text.instance-range-can-widen', {
646
+ ranges: ['x', 'y', 'z']
647
+ .filter(axis => rangeDiffers(range.declared, { ...range.declared, [axis]: range.measured[axis] }))
648
+ .map(axis => `${axis.toUpperCase()} ${mm(range.measured[axis].min)} – ${mm(range.measured[axis].max)}`)
649
+ .join(' · ')
650
+ })}
651
+ </p>
652
+ <button act ?disabled=${unavailable} @click=${() => this.linkCommand({ kind: 'declare-instance-size', range: range.measured, replace: true })}>
653
+ <md-icon>straighten</md-icon>${i18next.t('figure.button.remeasure-range')}
654
+ </button>`
655
+ : nothing}
656
+ </section>`;
657
+ }
658
+ /** Choose a part, from the list, the canvas or a place row. A figure-level tab gives way to the part's properties. */
659
+ choose(id) {
660
+ this.selected = id;
661
+ if (id && (this.tab === 'verdict' || this.tab === 'view' || this.tab === 'history'))
662
+ this.tab = 'properties';
663
+ }
664
+ asset() {
665
+ try {
666
+ return JSON.parse(this.session.source());
667
+ }
668
+ catch {
669
+ return undefined;
670
+ }
671
+ }
672
+ renderTab(tab, icon, label, badge) {
673
+ return html `<button tab ?on=${this.tab === tab} title=${label} aria-label=${label} @click=${() => (this.tab = tab)}>
674
+ <md-icon>${icon}</md-icon>${badge == null ? '' : html `<span badge>${badge.toLocaleString()}</span>`}
675
+ </button>`;
676
+ }
677
+ updated(changed) {
678
+ super.updated?.(changed);
679
+ const panes = this.renderRoot.querySelector('ox-three-pane') ?? undefined;
680
+ if (panes !== this.panes)
681
+ this.panes = panes;
682
+ }
683
+ /** Which part the panel speaks of, with the way back to the whole figure. */
684
+ renderSubject() {
685
+ if (!this.selected)
686
+ return null;
687
+ return html `<div subject>
688
+ <button chip title=${i18next.t('figure.button.whole-figure')} @click=${() => (this.selected = '')}>
689
+ <md-icon>arrow_back</md-icon>${i18next.t('figure.button.whole-figure')}
690
+ </button>
691
+ </div>`;
692
+ }
489
693
  render() {
490
694
  const view = this.session?.read();
491
695
  if (!view)
492
- return html `<p>모델을 여는 중입니다.</p>`;
696
+ return html `<p>${i18next.t('figure.text.opening-model')}</p>`;
493
697
  const unavailable = view.loading || !!view.error || view.saving || !view.context;
494
698
  const part = view.geometry.find(g => g.id === this.selected);
495
- // Shared/root parameters remain available even when a part is selected.
496
- const own = view.fields.filter(i => this.selected && i.id.startsWith(this.selected + '.'));
497
- const common = view.fields.filter(i => !view.geometry.some(g => i.id.startsWith(g.id + '.')));
498
- const fields = this.selected ? [...own, ...common] : view.fields;
499
- return html ` <header>
500
- <strong>V3 모델링</strong>
501
- <button ?disabled=${unavailable || !view.canUndo} @click=${() => this.run(() => this.session.undo())}>
502
- 실행 취소
503
- </button>
504
- <button ?disabled=${unavailable || !view.canRedo} @click=${() => this.run(() => this.session.redo())}>
505
- 다시 실행
506
- </button>
507
- <button @click=${() => this.renderRoot.querySelector('figure-v3-canvas')?.fit()}>
508
- 전체 보기
509
- </button>
510
- <span class="status" role="status"
511
- >${view.loading ? '불러오는 중' : view.saving ? '저장 중' : view.dirty || !view.id ? '저장 안 됨' : '저장됨'}</span
512
- >
513
- <button ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>저장</button>
514
- </header>
699
+ /* The controls (state inputs) have their own place in the motion tab; these are the design values. */
700
+ const design = view.fields.filter(i => i.role !== 'state');
701
+ const own = design.filter(i => this.selected && i.id.startsWith(this.selected + '.'));
702
+ const common = design.filter(i => !view.geometry.some(g => i.id.startsWith(g.id + '.')));
703
+ const status = view.loading
704
+ ? 'figure.text.loading-figure'
705
+ : view.saving
706
+ ? 'figure.text.saving-figure'
707
+ : view.dirty || !view.id
708
+ ? 'figure.text.unsaved-changes'
709
+ : 'figure.text.saved';
710
+ const asset = this.asset();
711
+ const tab = this.tab;
712
+ return html `<ox-canvas-bar
713
+ history
714
+ fit
715
+ undo-shortcut="Ctrl+Z"
716
+ redo-shortcut="Ctrl+Y"
717
+ ?can-undo=${!unavailable && view.canUndo}
718
+ ?can-redo=${!unavailable && view.canRedo}
719
+ .pane=${this.panes}
720
+ @canvas-undo=${() => this.run(() => this.session.undo())}
721
+ @canvas-redo=${() => this.run(() => this.session.redo())}
722
+ @canvas-fit=${() => this.renderRoot.querySelector('figure-v3-canvas')?.fit()}
723
+ >
724
+ ${['edit', 'preview'].map(mode => html `<button slot="mode" mode aria-pressed=${String(this.middle === mode)} title=${i18next.t('figure.text.mode-' + mode + '-explained')} @click=${() => (this.middle = mode)}>
725
+ ${i18next.t('figure.label.mode-' + mode)}
726
+ </button>`)}
727
+ <span slot="status" class="status" role="status" ?dirty=${status === 'figure.text.unsaved-changes'}>${i18next.t(status)}</span>
728
+ <button slot="commit" save ?disabled=${unavailable || (!view.dirty && !!view.id)} @click=${this.save}>${i18next.t('figure.button.save')}</button>
729
+ </ox-canvas-bar>
515
730
  ${this.error || view.error ? html `<div role="alert" class="error">${this.error || view.error}</div>` : null}
516
- <main>
731
+ <ox-three-pane>
517
732
  <figure-v3-parts
733
+ slot="start"
734
+ .budget=${this.budget}
735
+ @show-maturity=${() => (this.tab = 'verdict')}
518
736
  .parts=${view.geometry.map((g) => ({
519
737
  id: g.id,
520
738
  provider: g.shape?.provider ?? '',
@@ -524,7 +742,7 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
524
742
  .selected=${this.selected}
525
743
  ?locked=${unavailable}
526
744
  @add-part=${(e) => this.add(e.detail.kind)}
527
- @select-part=${(e) => (this.selected = e.detail.id)}
745
+ @select-part=${(e) => this.choose(e.detail.id)}
528
746
  @toggle-part-hidden=${(e) => this.toggleHiddenPart(e.detail.id)}
529
747
  @set-all-parts-hidden=${(e) => (this.hiddenParts = e.detail.hidden ? new Set(view.geometry.map(g => g.id)) : new Set())}
530
748
  @move-part-up=${(e) => this.movePart(e.detail.id, e.detail.to)}
@@ -532,47 +750,139 @@ let FigureV3Editor = class FigureV3Editor extends LitElement {
532
750
  @duplicate-part=${(e) => this.duplicatePart(e.detail.id)}
533
751
  @remove-part=${(e) => this.removePart(e.detail.id)}
534
752
  ></figure-v3-parts>
535
- <figure-v3-canvas
536
- .parts=${drawableV3Parts(view.geometry ?? []).filter(body => !this.hiddenParts.has(body.id))}
753
+ ${this.middle === 'preview'
754
+ ? html `<figure-v3-preview
755
+ .asset=${asset}
756
+ .context=${view.context ?? ''}
757
+ .view=${this.view}
758
+ @v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
759
+ ></figure-v3-preview>`
760
+ : html `<figure-v3-canvas
761
+ .view=${this.view}
762
+ .parts=${this.drawnParts(view.geometry, asset)}
537
763
  .palette=${view.palette ?? {}}
538
764
  .context=${view.context ?? ''}
539
765
  .selected=${this.selected}
540
766
  .reach=${unavailable ? { translate: false, rotate: false, scale: false } : this.reachOf(this.selected)}
541
767
  @part-transformed=${(e) => this.transformed(e.detail.id, e.detail.result)}
542
- @part-selected=${(e) => {
543
- this.selected = e.detail;
544
- }}
768
+ @part-selected=${(e) => this.choose(e.detail)}
545
769
  @render-error=${(e) => {
546
- this.error = e.detail.message;
547
- }}
548
- ></figure-v3-canvas>
549
- <aside>
550
- ${this.selected && part
551
- ? html `<label name-field
552
- ><span>${i18next.t('figure.label.name')}</span
553
- ><input
554
- aria-label=${i18next.t('figure.label.name')}
555
- .value=${this.selected}
556
- ?disabled=${unavailable}
557
- @change=${(e) => this.renamePart(e.target)}
558
- /></label>`
559
- : html `<h3>${this.selected || '모델 입력'}</h3>`}
560
- ${this.renderTools(unavailable)}
561
- ${fields.map(field => html `<label><span>${fieldLabel(field.id)} (${field.unit})${field.role === 'state' ? ' · 동작' : ''}</span><input aria-label=${field.id} type="number" step="any" .value=${String(field.value)} ?disabled=${unavailable} @change=${(e) => this.changeField(field, e)} /></label>`)}
562
- ${part
563
- ? html `${this.renderLinks(unavailable)}<figure-v3-material
564
- .body=${part}
565
- .palette=${view.palette ?? {}}
566
- ?locked=${unavailable}
567
- @v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
568
- ></figure-v3-material>`
569
- : null}
570
- ${view.bindings.length
571
- ? html `<h3>배치 연결점</h3>
572
- ${view.bindings.map(b => html `<p>${b.label} · ${b.role}</p>`)}`
573
- : null}
770
+ this.error = e.detail.message;
771
+ }}
772
+ ></figure-v3-canvas>`}
773
+ <aside slot="end">
774
+ <div tabs>
775
+ ${this.renderTab('properties', 'tune', i18next.t('figure.label.properties'))}
776
+ ${this.renderTab('capability', 'conveyor_belt', i18next.t('figure.label.tab-capability'))}
777
+ ${this.renderTab('motion', 'animation', i18next.t('figure.label.motion'))}
778
+ ${this.renderTab('verdict', 'insights', i18next.t('figure.label.maturity'), this.instances)}
779
+ ${this.renderTab('view', 'wb_sunny', i18next.t('figure.label.tab-environment'))}
780
+ ${this.renderTab('history', 'history', i18next.t('figure.label.history'))}
781
+ </div>
782
+ <slot name="notice"></slot>
783
+ <div panel ?off=${tab !== 'properties'}>
784
+ ${this.selected && part
785
+ ? html `${this.renderSubject()}
786
+ <section>
787
+ <h3>${i18next.t('figure.label.group-part')}</h3>
788
+ <div inline-field>
789
+ <label>${i18next.t('figure.label.name')}</label>
790
+ <input
791
+ name-field
792
+ aria-label=${i18next.t('figure.label.name')}
793
+ .value=${this.selected}
794
+ ?disabled=${unavailable}
795
+ @change=${(e) => this.renamePart(e.target)}
796
+ />
797
+ </div>
798
+ </section>
799
+ ${this.renderGroups(groupFields(design, this.selected), 'figure.label.group-placement-size', 'figure.label.group-shape', unavailable, this.renderTools(unavailable))}
800
+ ${this.renderLinks(unavailable, 'shape')}
801
+ <figure-v3-material
802
+ .body=${part}
803
+ .palette=${view.palette ?? {}}
804
+ ?locked=${unavailable}
805
+ @v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
806
+ @v3-part-live=${(e) => this.hold({}, e.detail.edit)}
807
+ ></figure-v3-material>`
808
+ : html `<slot name="figure"></slot>
809
+ ${this.renderGroups(groupFields(common), 'figure.label.group-figure-size', 'figure.label.design-inputs', unavailable)}
810
+ ${this.renderInstanceRange(asset, unavailable)}`}
811
+ </div>
812
+ <div panel ?off=${tab !== 'capability'}>
813
+ ${this.renderSubject()}
814
+ ${this.selected && part ? this.renderLinks(unavailable, 'places') : null}
815
+ <figure-v3-capability
816
+ .asset=${asset}
817
+ .selected=${this.selected}
818
+ ?locked=${unavailable}
819
+ @select-part=${(e) => this.choose(e.detail.id)}
820
+ @v3-part-edit=${(e) => this.run(() => this.session.edit(e.detail.edit))}
821
+ ></figure-v3-capability>
822
+ </div>
823
+ <div panel ?off=${tab !== 'motion'}>
824
+ ${this.renderSubject()}
825
+ ${this.selected && part ? this.renderLinks(unavailable, 'motion') : html `<section><p hint>${i18next.t('figure.text.choose-a-part-to-move')}</p></section>`}
826
+ ${this.renderControls(view, unavailable)}
827
+ </div>
828
+ <div panel flush ?off=${tab !== 'verdict'}><slot name="verdict"></slot></div>
829
+ <div panel flush ?off=${tab !== 'view'}>
830
+ <figure-settings .view=${this.view} @update-view=${(e) => this.setView(e.detail.view)}></figure-settings>
831
+ </div>
832
+ <div panel flush ?off=${tab !== 'history'}><slot name="history"></slot></div>
574
833
  </aside>
575
- </main>`;
834
+ </ox-three-pane>`;
835
+ }
836
+ /** Position, rotation and size as rows of three cells; the shape's own measures one to a line, under their title. */
837
+ renderGroups(groups, vectorTitle, singleTitle, unavailable, tools = null) {
838
+ return html `${groups.vectors.length || tools
839
+ ? html `<section>
840
+ <h3>${i18next.t(vectorTitle)}</h3>
841
+ ${groups.vectors.map(g => this.renderVector(g, unavailable))}
842
+ ${tools ? html `<div vector-row><label>${i18next.t('figure.label.tools')}</label>${tools}</div>` : nothing}
843
+ </section>`
844
+ : nothing}
845
+ ${groups.singles.length
846
+ ? html `<section>
847
+ <h3>${i18next.t(singleTitle)}</h3>
848
+ <div form>${groups.singles.map(g => (g.kind === 'single' ? this.renderSingle(g.name, g.field, unavailable) : nothing))}</div>
849
+ </section>`
850
+ : nothing}`;
851
+ }
852
+ renderVector(group, unavailable) {
853
+ if (group.kind !== 'vector')
854
+ return nothing;
855
+ const unit = group.cells.x.unit === 'deg' ? '°' : group.cells.x.unit;
856
+ return html `<div vector-row>
857
+ <label>${i18next.t('figure.label.group-' + group.name)}${group.name === 'rotation' ? '' : html ` <span unit>${unit}</span>`}</label>
858
+ <div triple>
859
+ ${['x', 'y', 'z'].map(axis => html `<div cell>
860
+ <span axis ?scrub=${!unavailable} @pointerdown=${(e) => this.scrub(group.cells[axis], e, unavailable)}>${axis.toUpperCase()}</span>
861
+ <input
862
+ aria-label=${group.cells[axis].id}
863
+ type="number"
864
+ step="any"
865
+ .value=${String(group.cells[axis].value)}
866
+ ?disabled=${unavailable}
867
+ @input=${(e) => this.typing(group.cells[axis], e)}
868
+ @change=${(e) => this.changeField(group.cells[axis], e)}
869
+ />
870
+ </div>`)}
871
+ </div>
872
+ </div>`;
873
+ }
874
+ renderSingle(name, field, unavailable) {
875
+ const words = name.split('.').map(part => i18next.t('figure.label.field-' + part, { defaultValue: part }));
876
+ return html `<span ?scrub=${!unavailable} @pointerdown=${(e) => this.scrub(field, e, unavailable)}>${words.join(' · ')} (${field.unit})</span
877
+ ><input
878
+ aria-label=${field.id}
879
+ type="number"
880
+ step="any"
881
+ .value=${String(field.value)}
882
+ ?disabled=${unavailable}
883
+ @input=${(e) => this.typing(field, e)}
884
+ @change=${(e) => this.changeField(field, e)}
885
+ />`;
576
886
  }
577
887
  };
578
888
  __decorate([
@@ -583,6 +893,14 @@ __decorate([
583
893
  property({ type: Number }),
584
894
  __metadata("design:type", Object)
585
895
  ], FigureV3Editor.prototype, "revision", void 0);
896
+ __decorate([
897
+ property({ type: Number }),
898
+ __metadata("design:type", Number)
899
+ ], FigureV3Editor.prototype, "instances", void 0);
900
+ __decorate([
901
+ property({ attribute: false }),
902
+ __metadata("design:type", Object)
903
+ ], FigureV3Editor.prototype, "budget", void 0);
586
904
  __decorate([
587
905
  state(),
588
906
  __metadata("design:type", Object)
@@ -591,11 +909,31 @@ __decorate([
591
909
  state(),
592
910
  __metadata("design:type", Object)
593
911
  ], FigureV3Editor.prototype, "error", void 0);
912
+ __decorate([
913
+ state(),
914
+ __metadata("design:type", String)
915
+ ], FigureV3Editor.prototype, "tab", void 0);
916
+ __decorate([
917
+ state(),
918
+ __metadata("design:type", Function)
919
+ ], FigureV3Editor.prototype, "panes", void 0);
920
+ __decorate([
921
+ state(),
922
+ __metadata("design:type", String)
923
+ ], FigureV3Editor.prototype, "middle", void 0);
924
+ __decorate([
925
+ state(),
926
+ __metadata("design:type", Object)
927
+ ], FigureV3Editor.prototype, "view", void 0);
594
928
  __decorate([
595
929
  state(),
596
930
  __metadata("design:type", Object)
597
931
  ], FigureV3Editor.prototype, "hiddenParts", void 0);
598
- FigureV3Editor = __decorate([
932
+ __decorate([
933
+ state(),
934
+ __metadata("design:type", Object)
935
+ ], FigureV3Editor.prototype, "live", void 0);
936
+ FigureV3Editor = FigureV3Editor_1 = __decorate([
599
937
  customElement('figure-v3-editor')
600
938
  ], FigureV3Editor);
601
939
  export { FigureV3Editor };