tosijs-ui 1.7.2 → 1.7.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/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ export * from './menu';
29
29
  export * from './month';
30
30
  export * from './notifications';
31
31
  export * from './password-strength';
32
+ export * from './pocket-bar';
32
33
  export * from './pop-float';
33
34
  export * from './rating';
34
35
  export * from './rich-text';
package/dist/index.js CHANGED
@@ -29,6 +29,7 @@ export * from './menu';
29
29
  export * from './month';
30
30
  export * from './notifications';
31
31
  export * from './password-strength';
32
+ export * from './pocket-bar';
32
33
  export * from './pop-float';
33
34
  export * from './rating';
34
35
  export * from './rich-text';
@@ -1,5 +1,19 @@
1
1
  import { Dialect, ExampleContext, TransformFn } from './types';
2
2
  export declare const AsyncFunction: Function;
3
+ /**
4
+ * Thrown by `rewriteImports` when an example has a static import the environment
5
+ * can't satisfy (a non-context package with no import-resolver). This is NOT a code
6
+ * defect — the code is fine, the doc environment just doesn't provide the dependency
7
+ * — so the build check treats it as a *warning* (the block is display-only) rather
8
+ * than a fatal error, and can tell it apart from a real syntax/transpile error.
9
+ */
10
+ export declare class UnsupportedImportError extends Error {
11
+ /** The offending `import …` statement. */
12
+ readonly statement: string;
13
+ constructor(message: string,
14
+ /** The offending `import …` statement. */
15
+ statement: string);
16
+ }
3
17
  /**
4
18
  * Sanitize a context module key into a JS identifier used as the binding name
5
19
  * in rewritten imports and as the AsyncFunction parameter. Must be applied
@@ -1,6 +1,23 @@
1
1
  export const AsyncFunction = (async () => {
2
2
  /* placeholder */
3
3
  }).constructor;
4
+ /**
5
+ * Thrown by `rewriteImports` when an example has a static import the environment
6
+ * can't satisfy (a non-context package with no import-resolver). This is NOT a code
7
+ * defect — the code is fine, the doc environment just doesn't provide the dependency
8
+ * — so the build check treats it as a *warning* (the block is display-only) rather
9
+ * than a fatal error, and can tell it apart from a real syntax/transpile error.
10
+ */
11
+ export class UnsupportedImportError extends Error {
12
+ statement;
13
+ constructor(message,
14
+ /** The offending `import …` statement. */
15
+ statement) {
16
+ super(message);
17
+ this.statement = statement;
18
+ this.name = 'UnsupportedImportError';
19
+ }
20
+ }
4
21
  /**
5
22
  * Sanitize a context module key into a JS identifier used as the binding name
6
23
  * in rewritten imports and as the AsyncFunction parameter. Must be applied
@@ -90,12 +107,13 @@ importPrefix = resolverPrefix()) {
90
107
  // Anything still a static import is unsupported — fail loudly with the line.
91
108
  const leftover = result.match(/^\s*import\s+['"{*\w][^\n]*/m);
92
109
  if (leftover) {
93
- throw new Error(`live example: unsupported import \`${leftover[0].trim()}\` — imports ` +
110
+ const statement = leftover[0].trim();
111
+ throw new UnsupportedImportError(`live example: unsupported import \`${statement}\` — imports ` +
94
112
  `from the example context (${contextKeys.join(', ')}) are supported in ` +
95
113
  `{ named }, * as ns, or default form` +
96
114
  (importPrefix
97
115
  ? `, and other packages resolve via the import-resolver.`
98
- : ` (enable importResolver to import other packages).`));
116
+ : ` (enable importResolver to import other packages).`), statement);
99
117
  }
100
118
  return result;
101
119
  }
@@ -27,7 +27,7 @@ export declare class LiveExample extends Component<ExampleParts> {
27
27
  boxShadow: string;
28
28
  overflow: string;
29
29
  };
30
- ':host.-locally-edited [part="exampleWidgets"]': {
30
+ ':host.-locally-edited > [part="example"] > [part="exampleWidgets"]': {
31
31
  outline: string;
32
32
  outlineOffset: string;
33
33
  borderRadius: string;
@@ -53,7 +53,7 @@ export declare class LiveExample extends Component<ExampleParts> {
53
53
  ':host.-vertical .layout-indicator': {
54
54
  transform: string;
55
55
  };
56
- ':host.-maximize .hide-if-maximized, :host:not(.-maximize) .show-if-maximized': {
56
+ ':host.-maximize > [part="example"] > [part="exampleWidgets"] .hide-if-maximized, :host:not(.-maximize) > [part="example"] > [part="exampleWidgets"] .show-if-maximized': {
57
57
  display: string;
58
58
  };
59
59
  ':host [part="example"]': {
@@ -89,17 +89,23 @@ export declare class LiveExample extends Component<ExampleParts> {
89
89
  };
90
90
  ':host [part="exampleWidgets"]': {
91
91
  position: string;
92
- left: string;
93
- bottom: string;
94
- '--widget-color': string;
95
- borderRadius: string;
96
- width: string;
97
- height: string;
98
- lineHeight: string;
92
+ top: string;
93
+ right: string;
99
94
  zIndex: string;
95
+ color: string;
96
+ '--widget-color': string;
97
+ '--tosi-pocket-handle-color': string;
98
+ };
99
+ ':host [part="exampleWidgets"] button': {
100
+ '--text-color': string;
100
101
  };
101
- ':host [part="exampleWidgets"] svg': {
102
- stroke: string;
102
+ ':host [part="exampleWidgets"] .tests-toggle input': {
103
+ display: string;
104
+ };
105
+ ':host [part="exampleWidgets"] .tests-toggle': {
106
+ opacity: string;
107
+ filter: string;
108
+ transition: string;
103
109
  };
104
110
  ':host .code-editors': {
105
111
  overflow: string;
@@ -134,33 +140,6 @@ export declare class LiveExample extends Component<ExampleParts> {
134
140
  ':host .sizer': {
135
141
  cursor: string;
136
142
  };
137
- ':host [part="testIndicator"]': {
138
- position: string;
139
- top: string;
140
- right: string;
141
- width: string;
142
- height: string;
143
- borderRadius: string;
144
- background: string;
145
- zIndex: string;
146
- display: string;
147
- };
148
- ':host.-has-tests [part="testIndicator"]': {
149
- display: string;
150
- opacity: string;
151
- };
152
- ':host.-test-running [part="testIndicator"]': {
153
- background: string;
154
- animation: string;
155
- };
156
- ':host.-test-passed [part="testIndicator"]': {
157
- background: string;
158
- animation: string;
159
- };
160
- ':host.-test-failed [part="testIndicator"]': {
161
- background: string;
162
- animation: string;
163
- };
164
143
  '@keyframes test-pulse': {
165
144
  '0%, 100%': {
166
145
  opacity: string;
@@ -169,21 +148,14 @@ export declare class LiveExample extends Component<ExampleParts> {
169
148
  opacity: string;
170
149
  };
171
150
  };
172
- '@keyframes test-fade': {
173
- '0%': {
174
- opacity: string;
175
- };
176
- '50%': {
177
- opacity: string;
178
- };
179
- '100%': {
180
- opacity: string;
181
- };
151
+ ':host.-test-running > [part="example"] > [part="exampleWidgets"]': {
152
+ '--widget-color': string;
153
+ animation: string;
182
154
  };
183
- ':host.-test-passed [part="exampleWidgets"]': {
155
+ ':host.-test-passed > [part="example"] > [part="exampleWidgets"]': {
184
156
  '--widget-color': string;
185
157
  };
186
- ':host.-test-failed [part="exampleWidgets"]': {
158
+ ':host.-test-failed > [part="example"] > [part="exampleWidgets"]': {
187
159
  '--widget-color': string;
188
160
  };
189
161
  ':host [part="testResults"]': {
@@ -291,7 +263,8 @@ export declare class LiveExample extends Component<ExampleParts> {
291
263
  get isMaximized(): boolean;
292
264
  flipLayout: () => void;
293
265
  saveToSource: () => Promise<void>;
294
- exampleMenu: () => void;
266
+ handleTestsToggle: (event: Event) => void;
267
+ updateExampleWidgets: () => void;
295
268
  handleShortcuts: (event: KeyboardEvent) => void;
296
269
  content: () => any[];
297
270
  private buildEditorPanel;
@@ -413,6 +413,7 @@ import { Component, elements, tosi } from 'tosijs';
413
413
  import { codeEditor, CodeEditor } from '../code-editor';
414
414
  import { tosiTabs } from '../tab-selector';
415
415
  import { icons } from '../icons';
416
+ import { tosiPocketBar } from '../pocket-bar';
416
417
  import { postNotification } from '../notifications';
417
418
  import { popMenu } from '../menu';
418
419
  import { loadTransform, loadTjsTestApi, rewriteImports, contextVarName, AsyncFunction, } from './code-transform';
@@ -423,7 +424,7 @@ import { rewriteExampleBlocks, groupExamples, findFencedBlocks, } from './save-t
423
424
  import { exampleEditKey, saveExampleEdit, loadExampleEdit, clearExampleEdit, hasExampleEdit, } from './example-store';
424
425
  import { liveExampleStyleSpec } from './styles';
425
426
  import { runTests } from './test-harness';
426
- const { div, tosiSlot, style, button, pre, span } = elements;
427
+ const { div, tosiSlot, style, button, pre, span, label, input } = elements;
427
428
  // Test mode: controlled by localStorage, defaults to enabled on localhost
428
429
  const TESTS_ENABLED_KEY = 'tosijs-ui-tests-enabled';
429
430
  const isLocalhost = typeof window !== 'undefined' &&
@@ -495,7 +496,11 @@ export class LiveExample extends Component {
495
496
  return m;
496
497
  if (m)
497
498
  console.warn(`<tosi-example>: unknown mode "${m}" — running inline`);
498
- return this.iframe ? 'iframe' : 'inline';
499
+ // STRICT boolean: only an explicit `true` means iframe. `iframe` is a
500
+ // presence-only boolean attribute aliasing mode; a loose `this.iframe ?` would
501
+ // treat a stray non-boolean value ('', 'false', an attribute string) as truthy
502
+ // and silently force EVERY example into an iframe. Default is inline.
503
+ return this.iframe === true ? 'iframe' : 'inline';
499
504
  }
500
505
  prefix = 'lx';
501
506
  storageKey = STORAGE_KEY;
@@ -874,43 +879,21 @@ export class LiveExample extends Component {
874
879
  window.alert('Save failed.');
875
880
  }
876
881
  };
877
- exampleMenu = () => {
878
- const testsOn = testManager.enabled.value;
879
- popMenu({
880
- target: this.parts.exampleWidgets,
881
- width: 'auto',
882
- menuItems: [
883
- {
884
- icon: 'edit2',
885
- caption: 'view/edit code',
886
- action: this.showCode,
887
- },
888
- {
889
- icon: 'edit',
890
- caption: 'view/edit code in a new window',
891
- action: this.openEditorWindow,
892
- },
893
- null,
894
- {
895
- icon: this.isMaximized ? 'minimize' : 'maximize',
896
- caption: this.isMaximized ? 'restore preview' : 'maximize preview',
897
- action: this.toggleMaximize,
898
- },
899
- null,
900
- {
901
- icon: testsOn ? 'check' : '',
902
- caption: 'Run tests',
903
- action: () => {
904
- if (testsOn) {
905
- disableTests();
906
- }
907
- else {
908
- enableTests();
909
- }
910
- },
911
- },
912
- ],
913
- });
882
+ handleTestsToggle = (event) => {
883
+ if (event.target.checked)
884
+ enableTests();
885
+ else
886
+ disableTests();
887
+ };
888
+ // The maximize icon + title toggle purely in CSS (.hide-if-maximized /
889
+ // .show-if-maximized); only the run-tests checkbox needs a JS sync so its state
890
+ // is correct on first paint and after the global flag changes. (Its greyed /
891
+ // monochrome look keys off the --tests-enabled CSS var, which is global.)
892
+ updateExampleWidgets = () => {
893
+ if (!this.hydrated)
894
+ return;
895
+ if (this.parts.testsCheckbox)
896
+ this.parts.testsCheckbox.checked = testManager.enabled.value;
914
897
  };
915
898
  handleShortcuts = (event) => {
916
899
  if (event.metaKey || event.ctrlKey) {
@@ -938,11 +921,14 @@ export class LiveExample extends Component {
938
921
  }
939
922
  };
940
923
  content = () => [
941
- div({ part: 'example' }, style({ part: 'style' }), div({ part: 'testIndicator', title: 'test status' }), pre({ part: 'testResults', hidden: true }), button({
942
- title: 'example menu',
943
- part: 'exampleWidgets',
944
- onClick: this.exampleMenu,
945
- }, icons.code())),
924
+ div({ part: 'example' }, style({ part: 'style' }), pre({ part: 'testResults', hidden: true }),
925
+ // The example toolbar: a pocket bar whose collapsed `<>` handle carries the
926
+ // test-status colour (via --widget-color). Pinned top-right, growing left, so
927
+ // it never covers the editor in side-by-side mode.
928
+ tosiPocketBar({ part: 'exampleWidgets', icon: 'code', direction: 'w' }, button({ title: 'view/edit code', onClick: this.showCode }, icons.edit2()), button({ title: 'view/edit code in a new window', onClick: this.openEditorWindow }, icons.edit()), button({ title: 'toggle preview size', onClick: this.toggleMaximize },
929
+ // Both icons render; the existing .hide-if-maximized / .show-if-maximized
930
+ // CSS shows the right one for the current state — no JS icon swap.
931
+ icons.maximize({ class: 'hide-if-maximized' }), icons.minimize({ class: 'show-if-maximized' })), label({ class: 'tests-toggle', title: 'run tests' }, input({ type: 'checkbox', part: 'testsCheckbox', onChange: this.handleTestsToggle }), icons.check()))),
946
932
  // Empty until first showCode. buildEditorPanel() fills it lazily so a reader
947
933
  // who never opens a panel never constructs a <tosi-code> (and never pulls the
948
934
  // CodeMirror chunk). See ensureEditors().
@@ -1046,6 +1032,10 @@ export class LiveExample extends Component {
1046
1032
  // does not fire reliably during page unload.
1047
1033
  this.beforeUnloadHandler = () => this.remoteSync?.sendClose();
1048
1034
  addEventListener('beforeunload', this.beforeUnloadHandler);
1035
+ // Reflect maximize state + tests-enabled on the pocket-bar controls. The
1036
+ // run-tests icon greys globally via the --tests-enabled CSS var (maintained on
1037
+ // <body>), so no per-instance observer is needed to keep examples in sync.
1038
+ this.updateExampleWidgets();
1049
1039
  }
1050
1040
  disconnectedCallback() {
1051
1041
  super.disconnectedCallback();
@@ -1505,12 +1495,13 @@ export class LiveExample extends Component {
1505
1495
  }
1506
1496
  };
1507
1497
  displayTestResults() {
1508
- const { testResults: resultsEl, testIndicator } = this.parts;
1498
+ const { testResults: resultsEl, exampleWidgets } = this.parts;
1509
1499
  const results = this.testResults;
1510
1500
  if (!results || results.tests.length === 0) {
1511
1501
  resultsEl.hidden = true;
1512
1502
  this.classList.remove('-test-passed', '-test-failed');
1513
- testIndicator.title = 'no tests';
1503
+ if (exampleWidgets)
1504
+ exampleWidgets.title = 'no tests';
1514
1505
  return;
1515
1506
  }
1516
1507
  resultsEl.innerHTML = '';
@@ -1526,11 +1517,12 @@ export class LiveExample extends Component {
1526
1517
  }
1527
1518
  this.classList.toggle('-test-passed', results.failed === 0);
1528
1519
  this.classList.toggle('-test-failed', results.failed > 0);
1529
- // Update indicator title
1530
- testIndicator.title =
1531
- results.failed === 0
1532
- ? `${results.passed} tests passed`
1533
- : `${results.failed}/${results.tests.length} tests failed`;
1520
+ // The `<>` handle colour shows pass/fail; its tooltip carries the detail.
1521
+ if (exampleWidgets)
1522
+ exampleWidgets.title =
1523
+ results.failed === 0
1524
+ ? `${results.passed} tests passed`
1525
+ : `${results.failed}/${results.tests.length} tests failed`;
1534
1526
  // Update visibility based on tab and failure status
1535
1527
  this.updateTestResultsVisibility();
1536
1528
  // Dispatch event for doc-browser to track results
@@ -1584,6 +1576,7 @@ export class LiveExample extends Component {
1584
1576
  }
1585
1577
  render() {
1586
1578
  super.render();
1579
+ this.updateExampleWidgets();
1587
1580
  if (this.remoteId !== '') {
1588
1581
  const data = localStorage.getItem(this.storageKey);
1589
1582
  if (data !== null) {
@@ -14,7 +14,7 @@ export declare const liveExampleStyleSpec: {
14
14
  boxShadow: string;
15
15
  overflow: string;
16
16
  };
17
- ':host.-locally-edited [part="exampleWidgets"]': {
17
+ ':host.-locally-edited > [part="example"] > [part="exampleWidgets"]': {
18
18
  outline: string;
19
19
  outlineOffset: string;
20
20
  borderRadius: string;
@@ -40,7 +40,7 @@ export declare const liveExampleStyleSpec: {
40
40
  ':host.-vertical .layout-indicator': {
41
41
  transform: string;
42
42
  };
43
- ':host.-maximize .hide-if-maximized, :host:not(.-maximize) .show-if-maximized': {
43
+ ':host.-maximize > [part="example"] > [part="exampleWidgets"] .hide-if-maximized, :host:not(.-maximize) > [part="example"] > [part="exampleWidgets"] .show-if-maximized': {
44
44
  display: string;
45
45
  };
46
46
  ':host [part="example"]': {
@@ -76,17 +76,23 @@ export declare const liveExampleStyleSpec: {
76
76
  };
77
77
  ':host [part="exampleWidgets"]': {
78
78
  position: string;
79
- left: string;
80
- bottom: string;
81
- '--widget-color': string;
82
- borderRadius: string;
83
- width: string;
84
- height: string;
85
- lineHeight: string;
79
+ top: string;
80
+ right: string;
86
81
  zIndex: string;
82
+ color: string;
83
+ '--widget-color': string;
84
+ '--tosi-pocket-handle-color': string;
85
+ };
86
+ ':host [part="exampleWidgets"] button': {
87
+ '--text-color': string;
87
88
  };
88
- ':host [part="exampleWidgets"] svg': {
89
- stroke: string;
89
+ ':host [part="exampleWidgets"] .tests-toggle input': {
90
+ display: string;
91
+ };
92
+ ':host [part="exampleWidgets"] .tests-toggle': {
93
+ opacity: string;
94
+ filter: string;
95
+ transition: string;
90
96
  };
91
97
  ':host .code-editors': {
92
98
  overflow: string;
@@ -121,33 +127,6 @@ export declare const liveExampleStyleSpec: {
121
127
  ':host .sizer': {
122
128
  cursor: string;
123
129
  };
124
- ':host [part="testIndicator"]': {
125
- position: string;
126
- top: string;
127
- right: string;
128
- width: string;
129
- height: string;
130
- borderRadius: string;
131
- background: string;
132
- zIndex: string;
133
- display: string;
134
- };
135
- ':host.-has-tests [part="testIndicator"]': {
136
- display: string;
137
- opacity: string;
138
- };
139
- ':host.-test-running [part="testIndicator"]': {
140
- background: string;
141
- animation: string;
142
- };
143
- ':host.-test-passed [part="testIndicator"]': {
144
- background: string;
145
- animation: string;
146
- };
147
- ':host.-test-failed [part="testIndicator"]': {
148
- background: string;
149
- animation: string;
150
- };
151
130
  '@keyframes test-pulse': {
152
131
  '0%, 100%': {
153
132
  opacity: string;
@@ -156,21 +135,14 @@ export declare const liveExampleStyleSpec: {
156
135
  opacity: string;
157
136
  };
158
137
  };
159
- '@keyframes test-fade': {
160
- '0%': {
161
- opacity: string;
162
- };
163
- '50%': {
164
- opacity: string;
165
- };
166
- '100%': {
167
- opacity: string;
168
- };
138
+ ':host.-test-running > [part="example"] > [part="exampleWidgets"]': {
139
+ '--widget-color': string;
140
+ animation: string;
169
141
  };
170
- ':host.-test-passed [part="exampleWidgets"]': {
142
+ ':host.-test-passed > [part="example"] > [part="exampleWidgets"]': {
171
143
  '--widget-color': string;
172
144
  };
173
- ':host.-test-failed [part="exampleWidgets"]': {
145
+ ':host.-test-failed > [part="example"] > [part="exampleWidgets"]': {
174
146
  '--widget-color': string;
175
147
  };
176
148
  ':host [part="testResults"]': {
@@ -19,10 +19,14 @@ export const liveExampleStyleSpec = {
19
19
  boxShadow: 'inset 0 0 0 2px #8883',
20
20
  overflow: 'hidden',
21
21
  },
22
- // Local-edit indicator: an accent outline on the always-visible `<>` example
23
- // button (bottom-left) when this example differs from its doc source — visible
24
- // whether or not the code editor is open (e.g. an auto-restored saved edit).
25
- ':host.-locally-edited [part="exampleWidgets"]': {
22
+ // Local-edit indicator: an accent outline on the always-visible `<>` handle when
23
+ // this example differs from its doc source. NB every `:host.STATE [part=…]` rule
24
+ // in this file uses `> [part="example"] > [part="exampleWidgets"]`, not a bare
25
+ // descendant: a light-DOM `:host` is a global `tosi-example …` selector, so a
26
+ // descendant combinator would leak a CONTAINER example's state class onto a
27
+ // NESTED example's widget (the self-hosting demo). The child chain pins each rule
28
+ // to the example's own direct toolbar.
29
+ ':host.-locally-edited > [part="example"] > [part="exampleWidgets"]': {
26
30
  outline: '2px solid var(--brand-color, #da1167)',
27
31
  outlineOffset: '1px',
28
32
  borderRadius: '4px',
@@ -48,7 +52,9 @@ export const liveExampleStyleSpec = {
48
52
  ':host.-vertical .layout-indicator': {
49
53
  transform: 'rotateZ(180deg)',
50
54
  },
51
- ':host.-maximize .hide-if-maximized, :host:not(.-maximize) .show-if-maximized': {
55
+ // Maximize/restore icon toggle — scoped to the example's own toolbar so a
56
+ // container example's -maximize doesn't hide a nested example's maximize icon.
57
+ ':host.-maximize > [part="example"] > [part="exampleWidgets"] .hide-if-maximized, :host:not(.-maximize) > [part="example"] > [part="exampleWidgets"] .show-if-maximized': {
52
58
  display: 'none',
53
59
  },
54
60
  ':host [part="example"]': {
@@ -86,23 +92,42 @@ export const liveExampleStyleSpec = {
86
92
  height: '100%',
87
93
  position: 'relative',
88
94
  },
95
+ // The example toolbar (a <tosi-pocket-bar>), pinned top-right so it never covers
96
+ // the editor in side-by-side mode. Only the collapsed `<>` handle carries the
97
+ // test-status colour — the action buttons stay neutral — via the pocket bar's
98
+ // --tosi-pocket-handle-color (the -test-* rules below drive --widget-color).
89
99
  ':host [part="exampleWidgets"]': {
90
100
  position: 'absolute',
91
- left: '5px',
92
- bottom: '5px',
93
- '--widget-color': 'var(--brand-color)',
94
- borderRadius: '5px',
95
- width: '44px',
96
- height: '44px',
97
- lineHeight: '44px',
101
+ top: '5px',
102
+ right: '5px',
98
103
  zIndex: '100',
104
+ // Action controls are neutral (text colour); only the handle carries status.
105
+ color: 'var(--text-color)',
106
+ '--widget-color': 'var(--brand-color)',
107
+ '--tosi-pocket-handle-color': 'var(--widget-color)',
108
+ },
109
+ // The doc site brand-colours bare <button>s by overriding --text-color ON the
110
+ // button (a link affordance). Revert it here so the toolbar's icon buttons are
111
+ // neutral, matching the check <label> (which the button rule never touched).
112
+ ':host [part="exampleWidgets"] button': {
113
+ '--text-color': 'inherit',
114
+ },
115
+ // Run-tests toggle: the native checkbox is hidden; its icon is full-colour when
116
+ // tests are on and greyed + monochrome when off. --tests-enabled (0|1, set on
117
+ // <body>) is global, so every example's toggle stays in sync with no JS.
118
+ ':host [part="exampleWidgets"] .tests-toggle input': {
119
+ display: 'none',
99
120
  },
100
- ':host [part="exampleWidgets"] svg': {
101
- stroke: 'var(--widget-color)',
121
+ ':host [part="exampleWidgets"] .tests-toggle': {
122
+ opacity: 'calc(0.4 + 0.6 * var(--tests-enabled, 0))',
123
+ filter: 'grayscale(calc(1 - var(--tests-enabled, 0)))',
124
+ transition: 'opacity 0.2s, filter 0.2s',
102
125
  },
103
126
  ':host .code-editors': {
104
127
  overflow: 'hidden',
105
- background: 'white',
128
+ // Match the editor surface (neutral off-white; off-black in dark mode) instead
129
+ // of a hardcoded white that wouldn't adapt.
130
+ background: 'var(--code-bg, #fdfdfd)',
106
131
  position: 'relative',
107
132
  top: '0',
108
133
  right: '0',
@@ -133,47 +158,21 @@ export const liveExampleStyleSpec = {
133
158
  ':host .sizer': {
134
159
  cursor: 'nwse-resize',
135
160
  },
136
- // Test status indicator (--tests-enabled CSS var pierces shadow DOM)
137
- ':host [part="testIndicator"]': {
138
- position: 'absolute',
139
- top: '8px',
140
- right: '8px',
141
- width: '12px',
142
- height: '12px',
143
- borderRadius: '50%',
144
- background: '#888',
145
- zIndex: '100',
146
- display: 'none',
147
- },
148
- ':host.-has-tests [part="testIndicator"]': {
149
- display: 'block',
150
- opacity: 'var(--tests-enabled, 1)',
151
- },
152
- ':host.-test-running [part="testIndicator"]': {
153
- background: '#fa0',
154
- animation: 'test-pulse 0.5s ease-in-out infinite',
155
- },
156
- ':host.-test-passed [part="testIndicator"]': {
157
- background: '#0a0',
158
- animation: 'test-fade 2s ease-out forwards',
159
- },
160
- ':host.-test-failed [part="testIndicator"]': {
161
- background: '#c00',
162
- animation: 'test-pulse 1s ease-in-out infinite',
163
- },
164
161
  '@keyframes test-pulse': {
165
162
  '0%, 100%': { opacity: '1' },
166
163
  '50%': { opacity: '0.4' },
167
164
  },
168
- '@keyframes test-fade': {
169
- '0%': { opacity: '1' },
170
- '50%': { opacity: '1' },
171
- '100%': { opacity: '0' },
165
+ // Test status colours the `<>` handle (via --widget-color → the pocket bar's
166
+ // --tosi-pocket-handle-color). The handle is translucent at rest, so the colour
167
+ // reads as a subtle tint until you hover.
168
+ ':host.-test-running > [part="example"] > [part="exampleWidgets"]': {
169
+ '--widget-color': '#fa0',
170
+ animation: 'test-pulse 0.75s ease-in-out infinite',
172
171
  },
173
- ':host.-test-passed [part="exampleWidgets"]': {
172
+ ':host.-test-passed > [part="example"] > [part="exampleWidgets"]': {
174
173
  '--widget-color': '#0a0',
175
174
  },
176
- ':host.-test-failed [part="exampleWidgets"]': {
175
+ ':host.-test-failed > [part="example"] > [part="exampleWidgets"]': {
177
176
  '--widget-color': '#f00',
178
177
  },
179
178
  ':host [part="testResults"]': {
@@ -9,14 +9,14 @@ export interface ExampleParts extends PartsMap {
9
9
  codeEditors: HTMLElement;
10
10
  undo: HTMLButtonElement;
11
11
  redo: HTMLButtonElement;
12
- exampleWidgets: HTMLButtonElement;
12
+ exampleWidgets: HTMLElement;
13
+ testsCheckbox: HTMLInputElement;
13
14
  editors: TosiTabs;
14
15
  code: HTMLElement;
15
16
  sources: HTMLElement;
16
17
  style: HTMLStyleElement;
17
18
  example: HTMLElement;
18
19
  testResults: HTMLElement;
19
- testIndicator: HTMLElement;
20
20
  js: CodeEditor;
21
21
  html: CodeEditor;
22
22
  css: CodeEditor;