@things-factory/figure-ui 10.1.94 → 10.1.96

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 (38) hide show
  1. package/client/modeller/editor.ts +30 -11
  2. package/client/modeller/figure-fasten-drawing.ts +0 -2
  3. package/client/modeller/figure-links.ts +22 -25
  4. package/client/modeller/figure-material-panel.ts +18 -11
  5. package/client/modeller/figure-properties.ts +2 -2
  6. package/client/modeller/figure-section-panel.ts +12 -11
  7. package/client/modeller/links.ts +49 -23
  8. package/client/modeller/side-panel-styles.ts +67 -32
  9. package/dist-client/modeller/editor.d.ts +7 -2
  10. package/dist-client/modeller/editor.js +31 -10
  11. package/dist-client/modeller/editor.js.map +1 -1
  12. package/dist-client/modeller/figure-fasten-drawing.js +0 -2
  13. package/dist-client/modeller/figure-fasten-drawing.js.map +1 -1
  14. package/dist-client/modeller/figure-links.js +24 -25
  15. package/dist-client/modeller/figure-links.js.map +1 -1
  16. package/dist-client/modeller/figure-material-panel.js +18 -11
  17. package/dist-client/modeller/figure-material-panel.js.map +1 -1
  18. package/dist-client/modeller/figure-properties.js +2 -2
  19. package/dist-client/modeller/figure-properties.js.map +1 -1
  20. package/dist-client/modeller/figure-section-panel.d.ts +1 -0
  21. package/dist-client/modeller/figure-section-panel.js +12 -11
  22. package/dist-client/modeller/figure-section-panel.js.map +1 -1
  23. package/dist-client/modeller/links.d.ts +14 -4
  24. package/dist-client/modeller/links.js +51 -23
  25. package/dist-client/modeller/links.js.map +1 -1
  26. package/dist-client/modeller/side-panel-styles.d.ts +5 -1
  27. package/dist-client/modeller/side-panel-styles.js +67 -32
  28. package/dist-client/modeller/side-panel-styles.js.map +1 -1
  29. package/dist-client/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +2 -2
  31. package/test/command-preview.test.ts +3 -3
  32. package/test/first-minute.test.ts +16 -7
  33. package/test/section-panel.test.ts +2 -2
  34. package/translations/en.json +18 -10
  35. package/translations/ja.json +18 -10
  36. package/translations/ko.json +18 -10
  37. package/translations/ms.json +18 -10
  38. package/translations/zh.json +18 -10
@@ -10,7 +10,11 @@ import { css } from 'lit'
10
10
  *
11
11
  * The vocabulary:
12
12
  * section one titled group; the side is a stack of them
13
- * h3 a group's title -- small capitals
13
+ * h3 a group's title
14
+ *
15
+ * Three sizes and two weights (user 2026-10-01, the panel checked for balance: eight sizes, and titles smaller than
16
+ * what they titled): a title at the body token and semibold, everything else at the small token, and a hint at the
17
+ * micro one. Sections are 16 px above their title and 12 px below their last line.
14
18
  * div[inline-field] a short label and its one field on one line
15
19
  * div[field] a label above a full-width field
16
20
  * div[form] short labels beside their fields, the fields starting on one line
@@ -25,14 +29,14 @@ import { css } from 'lit'
25
29
  export const SidePanelStyles = css`
26
30
  :host {
27
31
  --figure-control-height: 30px;
28
- font-size: 0.76rem;
32
+ font-size: var(--ui-t-small, 11.5px);
29
33
  color: var(--md-sys-color-on-surface);
30
34
  }
31
35
  /* The side is a stack of sections, each a titled group. */
32
36
  section {
33
37
  display: grid;
34
- gap: 6px;
35
- padding: 8px 10px;
38
+ gap: 8px;
39
+ padding: 16px 12px 12px;
36
40
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
37
41
  }
38
42
  section > h3 {
@@ -48,18 +52,21 @@ export const SidePanelStyles = css`
48
52
  align-items: center;
49
53
  gap: 4px;
50
54
  margin: 14px 0 6px;
51
- font-size: var(--ui-t-micro, 11px);
52
- font-weight: 700;
53
- letter-spacing: 0.04em;
54
- text-transform: uppercase;
55
- color: var(--md-sys-color-on-surface-variant);
55
+ font-size: var(--ui-t-body, 13px);
56
+ font-weight: 600;
57
+ color: var(--md-sys-color-on-surface);
56
58
  }
57
59
  h3:first-child {
58
60
  margin-top: 0;
59
61
  }
62
+ /* Two weights only: a bold word in a line is the title's weight, not the browser's heavier one. */
63
+ b,
64
+ strong {
65
+ font-weight: 600;
66
+ }
60
67
  label,
61
68
  div[form] > span {
62
- font-size: 0.68rem;
69
+ font-size: inherit;
63
70
  color: var(--md-sys-color-on-surface-variant);
64
71
  }
65
72
  div[field] {
@@ -90,7 +97,7 @@ export const SidePanelStyles = css`
90
97
  }
91
98
  /* A value read, not edited, beside its label: the field's size and colour, not the page's. */
92
99
  div[inline-field] > span[value] {
93
- font-size: 0.76rem;
100
+ font-size: inherit;
94
101
  color: var(--md-sys-color-on-surface);
95
102
  }
96
103
  label span[unit] {
@@ -108,10 +115,9 @@ export const SidePanelStyles = css`
108
115
  gap: 4px;
109
116
  }
110
117
  div[tools] button {
111
- height: 28px;
118
+ height: var(--figure-control-height);
112
119
  padding: 0 10px;
113
120
  font: inherit;
114
- font-size: 0.78rem;
115
121
  color: var(--md-sys-color-on-surface);
116
122
  background: var(--md-sys-color-surface-container-lowest);
117
123
  border: 1px solid var(--md-sys-color-outline-variant);
@@ -146,16 +152,45 @@ export const SidePanelStyles = css`
146
152
  box-sizing: border-box;
147
153
  padding: 4px 7px;
148
154
  font: inherit;
149
- font-size: 0.76rem;
150
155
  color: var(--md-sys-color-on-surface);
151
156
  background-color: var(--md-sys-color-surface-container-lowest);
152
157
  border: 1px solid var(--md-sys-color-outline-variant);
153
158
  border-radius: 6px;
154
159
  }
160
+ /* The browser's own box and dot, in the panel's colour. */
155
161
  input[type='checkbox'],
156
162
  input[type='radio'] {
157
163
  width: auto;
158
164
  min-height: 0;
165
+ margin: 0;
166
+ accent-color: var(--md-sys-color-primary);
167
+ }
168
+ /* A fold: the panel's chevron, not the browser's triangle. */
169
+ summary {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 6px;
173
+ padding: 6px 0;
174
+ list-style: none;
175
+ cursor: pointer;
176
+ color: var(--md-sys-color-on-surface-variant);
177
+ }
178
+ summary::-webkit-details-marker {
179
+ display: none;
180
+ }
181
+ summary::before {
182
+ content: '';
183
+ flex: none;
184
+ width: 6px;
185
+ height: 6px;
186
+ margin: 0 2px;
187
+ border-right: 1.5px solid currentColor;
188
+ border-bottom: 1.5px solid currentColor;
189
+ transform: rotate(-45deg);
190
+ transition: transform 0.15s;
191
+ }
192
+ details[open] > summary::before {
193
+ transform: rotate(45deg);
159
194
  }
160
195
  /* A thin track and a small thumb: a slider is a line to drag along, not a block (user, 2026-09-24). */
161
196
  input[type='range'] {
@@ -239,7 +274,7 @@ export const SidePanelStyles = css`
239
274
  }
240
275
  div[cell] span[axis] {
241
276
  flex: none;
242
- font-size: 0.64rem;
277
+ font-size: var(--ui-t-micro, 11px);
243
278
  letter-spacing: 0.02em;
244
279
  color: var(--md-sys-color-on-surface-variant);
245
280
  }
@@ -272,7 +307,6 @@ export const SidePanelStyles = css`
272
307
  display: flex;
273
308
  align-items: center;
274
309
  gap: 6px;
275
- font-size: 0.76rem;
276
310
  color: var(--md-sys-color-on-surface);
277
311
  }
278
312
  /* The 「?」 beside a title: no border, it says there is more rather than asking to be pressed. */
@@ -299,41 +333,42 @@ export const SidePanelStyles = css`
299
333
  small {
300
334
  display: block;
301
335
  margin: 2px 0 4px;
302
- font-size: 0.68rem;
336
+ font-size: var(--ui-t-micro, 11px);
303
337
  line-height: 1.5;
304
338
  color: var(--md-sys-color-on-surface-variant);
305
339
  }
306
- p[hint] {
307
- padding-left: 6px;
308
- border-left: 2px solid var(--md-sys-color-outline-variant);
309
- }
340
+ /*
341
+ * An action under its fields: as wide as its words, not a band across the panel -- four full-width grey bars read as
342
+ * four walls and none of them as the one to press (user 2026-10-01). Every one carries its icon.
343
+ */
310
344
  button[act] {
311
345
  display: inline-flex;
312
346
  align-items: center;
313
347
  justify-content: center;
314
- gap: 4px;
315
- width: 100%;
348
+ justify-self: start;
349
+ gap: 6px;
350
+ width: auto;
351
+ max-width: 100%;
316
352
  margin-top: 4px;
317
- padding: 3px 8px;
318
- min-height: 28px;
353
+ padding: 0 12px;
354
+ min-height: var(--figure-control-height);
319
355
  font: inherit;
320
- font-size: 0.72rem;
321
- color: var(--md-sys-color-on-surface);
322
- background-color: var(--md-sys-color-surface-container);
356
+ color: var(--md-sys-color-primary);
357
+ background-color: var(--md-sys-color-surface-container-lowest);
323
358
  border: 1px solid var(--md-sys-color-outline-variant);
324
359
  border-radius: 6px;
325
360
  cursor: pointer;
326
361
  }
327
362
  button[act]:hover:not(:disabled) {
328
- background-color: var(--md-sys-color-surface-container-high);
363
+ background-color: var(--md-sys-color-primary-container);
329
364
  }
330
365
  button[act]:disabled {
331
366
  opacity: 0.5;
332
367
  cursor: default;
333
368
  }
334
369
  button[act] md-icon {
335
- font-size: 15px;
336
- width: 15px;
337
- height: 15px;
370
+ font-size: 16px;
371
+ width: 16px;
372
+ height: 16px;
338
373
  }
339
374
  `
@@ -309,8 +309,7 @@ export declare class FigureEditor extends LitElement {
309
309
  private pointBlockedOf;
310
310
  private renderStart;
311
311
  /**
312
- * The part the panel speaks of, held at its top: its kind, how its size answers, what holds it (designer 2026-09-27,
313
- * B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
312
+ * The part the panel speaks of, held at its top: its kind, and what its size follows when it follows something. No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
314
313
  * list's root is the way back to the model's settings.
315
314
  */
316
315
  private renderSubject;
@@ -328,6 +327,12 @@ export declare class FigureEditor extends LitElement {
328
327
  */
329
328
  private withSizeRow;
330
329
  private renderVector;
330
+ /**
331
+ * Why a row's numbers cannot be typed, said once under the rows by what decides them (user 2026-10-01: "set by a
332
+ * rule" named nothing): a fastened part's place and turn are its fastening's; other numbers are worked out from
333
+ * something else. A size that follows something is said at the panel's head, so not again here.
334
+ */
335
+ private renderHeldAxes;
331
336
  /**
332
337
  * A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
333
338
  * typed into -- they move nothing. Declaring the room again is offered; nothing is deleted or widened on the way.
@@ -386,13 +386,18 @@ let FigureEditor = class FigureEditor extends LitElement {
386
386
  z-index: 1;
387
387
  display: grid;
388
388
  gap: 4px;
389
- padding: 8px 10px;
389
+ padding: 10px 12px;
390
390
  background: var(--md-sys-color-surface-container-lowest);
391
391
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
392
392
  }
393
+ /* The panel's three sizes (side-panel-styles): the part's kind at the title's, its two lines at the hint's. */
393
394
  div[subject-summary] {
394
395
  display: grid;
395
- gap: 1px;
396
+ gap: 2px;
397
+ }
398
+ div[subject-summary] b {
399
+ font-size: var(--ui-t-body, 13px);
400
+ font-weight: 600;
396
401
  }
397
402
  div[subject-summary] b span {
398
403
  font-weight: 400;
@@ -1651,14 +1656,13 @@ let FigureEditor = class FigureEditor extends LitElement {
1651
1656
  if (this.detachSaid?.key === key)
1652
1657
  return this.detachSaid.words;
1653
1658
  const say = (k, params) => i18next.t(k, params);
1654
- const from = 'plane' in joint.body0 ? say('figure.label.mounting-plane') : partLabelOf(joint.body0.part);
1655
1659
  let words;
1656
1660
  try {
1657
1661
  const seen = this.session.previewCommand({ kind: 'detach', part: this.selected });
1658
1662
  const own = seen.moves.some(m => m.body === this.selected);
1659
1663
  words = own
1660
1664
  ? [say('figure.text.detach-moves'), ...previewWordsOf({ context: seen.context, revision: seen.revision, key, moves: seen.moves }, this.selected, say)]
1661
- : [say('figure.text.detach-stays', { from })];
1665
+ : /* It stays where it is: as expected, so nothing is said (user 2026-10-01). */ [];
1662
1666
  const children = (asset.joints ?? []).filter(j => 'part' in j.body0 && j.body0.part === this.selected).map(j => partLabelOf(j.body1.part));
1663
1667
  if (children.length)
1664
1668
  words.push(say('figure.text.detach-keeps-children', { parts: children.join(', ') }));
@@ -1892,14 +1896,15 @@ let FigureEditor = class FigureEditor extends LitElement {
1892
1896
  </div>`;
1893
1897
  }
1894
1898
  /**
1895
- * The part the panel speaks of, held at its top: its kind, how its size answers, what holds it (designer 2026-09-27,
1896
- * B). No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
1899
+ * The part the panel speaks of, held at its top: its kind, and what its size follows when it follows something. No path above it (user 2026-10-01): the parts list and the canvas already show which part is chosen, and the
1897
1900
  * list's root is the way back to the model's settings.
1898
1901
  */
1899
1902
  renderSubject() {
1900
1903
  let summary;
1901
1904
  try {
1902
- summary = this.selected ? partSummaryOf(parseFigureAsset(this.session.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params)) : undefined;
1905
+ summary = this.selected
1906
+ ? partSummaryOf(parseFigureAsset(this.session.source()), ownerOf(this.selected), (key, params) => i18next.t(key, params), i18next.language || 'ko')
1907
+ : undefined;
1903
1908
  }
1904
1909
  catch {
1905
1910
  summary = undefined;
@@ -1909,7 +1914,7 @@ let FigureEditor = class FigureEditor extends LitElement {
1909
1914
  return html `<div subject>
1910
1915
  <div subject-summary>
1911
1916
  ${summary.kind ? html `<b>${summary.kind}</b>` : nothing}
1912
- <small>${summary.size}</small><small>${summary.relation}</small>
1917
+ ${summary.size ? html `<small>${summary.size}</small>` : nothing}
1913
1918
  </div>
1914
1919
  </div>`;
1915
1920
  }
@@ -2150,6 +2155,7 @@ let FigureEditor = class FigureEditor extends LitElement {
2150
2155
  ? html `<section>
2151
2156
  <h3>${i18next.t(vectorTitle)}</h3>
2152
2157
  ${groups.vectors.map(g => this.renderVector(g, unavailable, held))}
2158
+ ${this.renderHeldAxes(groups.vectors)}
2153
2159
  ${tools ?? nothing}
2154
2160
  </section>`
2155
2161
  : nothing}
@@ -2205,7 +2211,22 @@ let FigureEditor = class FigureEditor extends LitElement {
2205
2211
  </div>`)}
2206
2212
  </div>
2207
2213
  </div>
2208
- ${ruled.length ? html `<p hint held-axes>${i18next.t('figure.text.axes-held-by-rule', { group: i18next.t('figure.label.group-' + group.name), axes: ruled.map(a => a.toUpperCase()).join(' · ') })}</p>` : nothing}`;
2214
+ `;
2215
+ }
2216
+ /**
2217
+ * Why a row's numbers cannot be typed, said once under the rows by what decides them (user 2026-10-01: "set by a
2218
+ * rule" named nothing): a fastened part's place and turn are its fastening's; other numbers are worked out from
2219
+ * something else. A size that follows something is said at the panel's head, so not again here.
2220
+ */
2221
+ renderHeldAxes(vectors) {
2222
+ const fastened = !!this.asset()?.joints?.some(j => j.body1.part === ownerOf(this.selected));
2223
+ const held = vectors.filter(g => g.kind === 'vector' && g.name !== 'size' && ['x', 'y', 'z'].some(axis => !g.cells[axis]));
2224
+ if (!held.length)
2225
+ return nothing;
2226
+ const placed = held.every(g => g.name === 'position' || g.name === 'rotation');
2227
+ if (fastened && placed)
2228
+ return html `<p hint held-axes>${i18next.t('figure.text.held-by-fastening')}</p>`;
2229
+ return html `<p hint held-axes>${i18next.t('figure.text.held-worked-out', { group: held.map(g => i18next.t('figure.label.group-' + g.name)).join(' · ') })}</p>`;
2209
2230
  }
2210
2231
  /**
2211
2232
  * A part's fields only the declared room still reads (ADR-0097, designer 2026-09-27): shown as what they are and not
@@ -2218,7 +2239,7 @@ let FigureEditor = class FigureEditor extends LitElement {
2218
2239
  <div form>
2219
2240
  ${fields.map(f => html `<span>${fieldWordsOf(f.id, key => i18next.t(key))} (${f.unit})</span><input aria-label=${f.id} type="number" .value=${String(f.value)} disabled />`)}
2220
2241
  </div>
2221
- <button act room-refit ?disabled=${unavailable} @click=${() => this.askRoomRefit()}>${i18next.t('figure.button.room-refit')}</button>
2242
+ <button act room-refit ?disabled=${unavailable} @click=${() => this.askRoomRefit()}><md-icon>fit_screen</md-icon>${i18next.t('figure.button.room-refit')}</button>
2222
2243
  </section>`;
2223
2244
  }
2224
2245
  /** The room as declared and as proposed, and what lies outside each as measured, before the author confirms. */