@kupcad/ui 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -154,7 +154,10 @@ webview and the desktop app to the language server.
154
154
  Edges, Axes, Grid and Plate toggles; Fit, Persp, and the Iso, Top, Front and Right views, the
155
155
  one looked from pressed, a turn by hand pressing none), the model's parts in its bottom half,
156
156
  each with its colour and copies (a click on one selects its copies, as a click in the viewer
157
- does: `bind:selected`). A bar above: a section through the model along an axis (a solid's: a drawing, which isn't cut, or no model offers none), screenshot, an
157
+ does: `bind:selected`). In a pane narrower than 36em (a phone's) the panel is under the model,
158
+ at most half the pane. The model fills what the rest leave, no less than
159
+ `--kc-model-min-height` (none by default), which a host whose pane is as tall as its content
160
+ sets: the model has no height of its own. A bar above: a section through the model along an axis (a solid's: a drawing, which isn't cut, or no model offers none), screenshot, an
158
161
  `actions` snippet (a host's own buttons, the Export menu), the Panel button, and a `toggles`
159
162
  snippet after it (a host's toggles of its own panes beside the pane, the Parameters button;
160
163
  `.kc-control` with `aria-expanded` gives one the Panel button's look). Below, a status
@@ -256,6 +259,10 @@ webview and the desktop app to the language server.
256
259
  `--kc-focus` its focus ring, `--kc-accent-subtle` what is chosen in a list. `tokens.test.js`
257
260
  holds every pair to WCAG's 4.5:1 for text and 3:1 for a control's edge, in both schemes, and
258
261
  every colour token to a mapping in `vscode.css`.
262
+ Each colour is `light-dark()` of its two values, so the scheme decides: the system's
263
+ (`color-scheme: light dark`), until the page chooses one with `data-theme="light"` or
264
+ `"dark"` on its root (Starlight's convention, so a site's switch and the docs' agree) or a
265
+ `color-scheme` of its own. The viewer's own colours and the editor's follow the same scheme.
259
266
  A host sets its own, as `vscode.css` maps VS Code's theme over them: the webview follows the
260
267
  editor's theme, never the brand.
261
268
  `--kc-viewer-*` theme the viewer (`themeFromTokens`, which reads each as the page draws it,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kupcad/ui",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "KupCAD's UI: a three.js viewer of a model the core made, for any host (@kupcad/ui/viewer), and the Svelte panels around it (the viewer's pane, parameters, console, a language server's preview), styled by --kc-* tokens.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -36,11 +36,11 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@kupcad/protocol": "0.1.1",
39
+ "@kupcad/protocol": "0.1.3",
40
40
  "three": "^0.186.1"
41
41
  },
42
42
  "devDependencies": {
43
- "@kupcad/wasm": "0.1.1",
43
+ "@kupcad/wasm": "0.1.3",
44
44
  "@playwright/test": "^1.64.1",
45
45
  "@sveltejs/vite-plugin-svelte": "^7.3.1",
46
46
  "@testing-library/svelte": "^5.4.2",
@@ -78,9 +78,12 @@
78
78
  </dialog>
79
79
 
80
80
  <style>
81
+ /* In the middle of the window by its own margin, not the browser's default one, which a page's
82
+ reset (Tailwind's) takes away */
81
83
  .kc-shortcuts {
82
84
  width: min(32em, calc(100vw - 32px));
83
85
  max-height: calc(100vh - 64px);
86
+ margin: auto;
84
87
  padding: 16px 20px;
85
88
  font-family: var(--kc-font);
86
89
  font-size: var(--kc-font-size);
@@ -4,8 +4,11 @@
4
4
  copies, volume and size, the face and its area). Beside it a panel, which the toolbar's Panel
5
5
  button hides: the view's controls (the render mode; edges, axes, the floor; fit, perspective,
6
6
  the views) over the model's parts (a click on one selecting its copies, as a click in the
7
- viewer does), half of it each. A bar above: a section through the model, a screenshot and a
8
- host's own actions (which take the controls' look by .kc-control), the Panel button, and a
7
+ viewer does), half of it each; in a pane narrower than 36em (a phone's), under the model, at
8
+ most half the pane. The model fills what the rest leave, and no less than
9
+ `--kc-model-min-height` (none by default): a host whose pane is as tall as its content sets it,
10
+ the model having no height of its own. A bar above: a section through the model, a screenshot
11
+ and a host's own actions (which take the controls' look by .kc-control), the Panel button, and a
9
12
  host's toggles of its own panes beside this one. Below: how its run went, and what it said
10
13
  went wrong, one with a line clicked given to the host (`onproblem`), and a button that lists
11
14
  the keyboard shortcuts (the host's own, then the viewer's). How the view looks (the render mode,
@@ -496,6 +499,7 @@
496
499
 
497
500
  <style>
498
501
  .kc-viewer-pane {
502
+ container-type: inline-size;
499
503
  display: flex;
500
504
  flex-direction: column;
501
505
  height: 100%;
@@ -530,6 +534,7 @@
530
534
  position: relative;
531
535
  flex: 1;
532
536
  min-width: 0;
537
+ min-height: var(--kc-model-min-height, 0);
533
538
  }
534
539
  .tooltip {
535
540
  position: absolute;
@@ -573,6 +578,26 @@
573
578
  .parts {
574
579
  border-top: 1px solid var(--kc-border);
575
580
  }
581
+ /* A narrow pane: the panel under the model, as wide, the controls then the parts at their own
582
+ heights; scrolled past half the pane */
583
+ @container (max-width: 36em) {
584
+ .body {
585
+ flex-direction: column;
586
+ }
587
+ .panel {
588
+ flex: none;
589
+ width: auto;
590
+ max-height: 50%;
591
+ overflow: auto;
592
+ border-left: none;
593
+ border-top: 1px solid var(--kc-border);
594
+ }
595
+ .controls,
596
+ .parts {
597
+ flex: none;
598
+ overflow: visible;
599
+ }
600
+ }
576
601
  .caption {
577
602
  margin: 0 0 2px;
578
603
  color: var(--kc-muted);
package/src/theme.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // The viewer's theme from the --kc-viewer-* tokens of the page around it: a host themes the
2
2
  // viewer as it themes the panels, and changes it while it shows (VS Code's theme switch). A
3
3
  // token left unset, or set to no colour the page reads, takes the viewer's own colour, LIGHT or
4
- // DARK as the page's colour scheme is.
4
+ // DARK as the page's colour scheme is: the one it chose (tokens.css's data-theme, a color-scheme
5
+ // of its own), else the system's.
5
6
 
6
7
  import { DARK, LIGHT } from './viewer/index.js';
7
8
 
@@ -25,8 +26,9 @@ const HEX_WITH_ALPHA = /^(#[\da-f]{3})[\da-f]$|^(#[\da-f]{6})[\da-f]{2}$/i;
25
26
  const RGBA = /^rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)$/;
26
27
  /** Two fills a colour the browser can't read leaves as they were: it reads as neither. */
27
28
  const UNREAD = /** @type {const} */ (['#000000', '#ffffff']);
28
- /** The attributes of the page's root and body a host themes it by (VS Code: a class, a style). */
29
- const THEMED_BY = ['class', 'style'];
29
+ /** The attributes of the page's root and body a host themes it by (VS Code: a class, a style; a
30
+ * site's switch: data-theme). */
31
+ const THEMED_BY = ['class', 'style', 'data-theme'];
30
32
 
31
33
  /** colorReader's, once made (null where the page has no 2D context). @type {ColorReader | null | undefined} */
32
34
  let reader;
@@ -43,8 +45,7 @@ let reader;
43
45
  */
44
46
  export function themeFromTokens(element) {
45
47
  const style = getComputedStyle(element);
46
- const dark = globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
47
- const fallback = dark ? DARK : LIGHT;
48
+ const fallback = isDark(style) ? DARK : LIGHT;
48
49
  /** @type {Theme} */
49
50
  const theme = { ...fallback };
50
51
  const context = colorReader();
@@ -54,6 +55,18 @@ export function themeFromTokens(element) {
54
55
  return theme;
55
56
  }
56
57
 
58
+ /**
59
+ * Whether an element is drawn dark: its colour scheme where the page chose one alone (`dark`,
60
+ * `only light`), else the system's (`light dark`, or none said).
61
+ * @param {CSSStyleDeclaration} style its computed style
62
+ */
63
+ export function isDark(style) {
64
+ const scheme = (style.colorScheme ?? '').split(/\s+/);
65
+ const light = scheme.includes('light');
66
+ if (scheme.includes('dark') !== light) return !light;
67
+ return globalThis.matchMedia?.('(prefers-color-scheme: dark)').matches ?? false;
68
+ }
69
+
57
70
  /**
58
71
  * `color` as the viewer can draw it: #rrggbb, the sRGB of a pixel `context` draws in it, so any
59
72
  * CSS colour the browser reads (three.js reads hex, rgb() and a name alone: a color-mix() or an
@@ -92,7 +105,7 @@ function colorReader() {
92
105
 
93
106
  /**
94
107
  * Gives `changed` the theme of `element`'s tokens now, and again whenever it changes: with the
95
- * page's colour scheme, or a class or style of its root or body.
108
+ * system's colour scheme, or a class, style or data-theme of the page's root or body.
96
109
  * @param {Element} element
97
110
  * @param {(theme: Theme) => void} changed
98
111
  * @returns {() => void} stops watching
package/src/tokens.css CHANGED
@@ -6,6 +6,11 @@
6
6
  brand's too. A control of a host's own takes the panels' look by the class .kc-control
7
7
  (controls.css); a range input by .kc-range, a checkbox by .kc-check.
8
8
 
9
+ Each colour is light-dark() of its light and its dark value, picked by the colour scheme, which
10
+ is the system's (`color-scheme: light dark`) until the page chooses: data-theme="light" or
11
+ "dark" on its root (the docs' convention, Starlight's), or a color-scheme of its own. The
12
+ viewer and the editor follow the same scheme.
13
+
9
14
  The accent is a fill where there is one of a kind (the primary button, a slider, a checked
10
15
  box); a toggle while on, or what is chosen in a list, is --kc-accent-subtle edged in it, as
11
16
  several are on at once. The text on the fill is --kc-accent-fg, the brand black: white on the
@@ -16,55 +21,41 @@
16
21
  @import './controls.css';
17
22
 
18
23
  :root {
24
+ color-scheme: light dark;
19
25
  --kc-font: system-ui, -apple-system, 'Segoe UI', sans-serif;
20
26
  --kc-font-size: 13px;
21
- --kc-bg: #ffffff;
22
- --kc-fg: #1a1b1c;
23
- --kc-muted: #5a5d65;
27
+ --kc-bg: light-dark(#ffffff, #1a1b1c);
28
+ --kc-fg: light-dark(#1a1b1c, #e3e7eb);
29
+ --kc-muted: light-dark(#5a5d65, #9a9da3);
24
30
  /* A divider between panes and rows */
25
- --kc-border: #d5dae0;
31
+ --kc-border: light-dark(#d5dae0, #333436);
26
32
  /* The edge of a field, a select or a checkbox, which tells it from the background */
27
- --kc-control-border: #8a9099;
28
- --kc-input-bg: #ffffff;
33
+ --kc-control-border: light-dark(#8a9099, #73767c);
34
+ --kc-input-bg: light-dark(#ffffff, #282829);
29
35
  /* A row or a button under the pointer */
30
- --kc-hover: rgba(26, 27, 28, 0.06);
36
+ --kc-hover: light-dark(rgba(26, 27, 28, 0.06), rgba(227, 231, 235, 0.07));
31
37
  --kc-accent: #ff5f14;
32
38
  --kc-accent-fg: #1a1b1c;
33
- --kc-accent-hover: #f0540c;
34
- --kc-accent-text: #c5480d;
39
+ --kc-accent-hover: light-dark(#f0540c, #ff7a3d);
40
+ --kc-accent-text: light-dark(#c5480d, #ff6a24);
35
41
  /* What is chosen in a list (the tree's open script), its text --kc-fg */
36
- --kc-accent-subtle: #ffe9de;
37
- --kc-focus: #c5480d;
42
+ --kc-accent-subtle: light-dark(#ffe9de, #43271b);
43
+ --kc-focus: light-dark(#c5480d, #ff6a24);
38
44
  /* A slider's track beyond its thumb */
39
- --kc-track: #d5dae0;
40
- --kc-error: #c8102e;
41
- --kc-warning: #8a5a00;
45
+ --kc-track: light-dark(#d5dae0, #3a3b3e);
46
+ --kc-error: light-dark(#c8102e, #ff6b81);
47
+ --kc-warning: light-dark(#8a5a00, #e3b341);
42
48
  /* A section's header: a group of parameters' */
43
- --kc-section-bg: #e3e7eb;
44
- --kc-section-fg: #1a1b1c;
49
+ --kc-section-bg: light-dark(#e3e7eb, #282829);
50
+ --kc-section-fg: light-dark(#1a1b1c, #e3e7eb);
45
51
  --kc-radius: 4px;
46
52
  --kc-gap: 8px;
47
53
  }
48
54
 
49
- @media (prefers-color-scheme: dark) {
50
- :root {
51
- --kc-bg: #1a1b1c;
52
- --kc-fg: #e3e7eb;
53
- --kc-muted: #9a9da3;
54
- --kc-border: #333436;
55
- --kc-control-border: #73767c;
56
- --kc-input-bg: #282829;
57
- --kc-hover: rgba(227, 231, 235, 0.07);
58
- --kc-accent: #ff5f14;
59
- --kc-accent-fg: #1a1b1c;
60
- --kc-accent-hover: #ff7a3d;
61
- --kc-accent-text: #ff6a24;
62
- --kc-accent-subtle: #43271b;
63
- --kc-focus: #ff6a24;
64
- --kc-track: #3a3b3e;
65
- --kc-error: #ff6b81;
66
- --kc-warning: #e3b341;
67
- --kc-section-bg: #282829;
68
- --kc-section-fg: #e3e7eb;
69
- }
55
+ /* The scheme the page chose: the system's no longer decides */
56
+ :root[data-theme='light'] {
57
+ color-scheme: light;
58
+ }
59
+ :root[data-theme='dark'] {
60
+ color-scheme: dark;
70
61
  }