tosijs-ui 1.17.3 → 1.17.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.17.4
4
+
5
+ > Live examples' inline views keep their controls in the code panel's tab bar, and a site or
6
+ > page can set the view its examples open in. Nothing to change on upgrade. Asked by tjs-lang
7
+ > for its learn-to-program book (#252). `dist/iife.js` grows 0.6 kB (508.7 kB gzip).
8
+
9
+ ### Added
10
+
11
+ - `SiteConfig.exampleView` (`'preview'` | `'code'` | `'console'`) sets the view every live
12
+ example opens in, and `exampleView` in a page's metadata or frontmatter sets it for one page.
13
+ The nearest setting wins (fence, then page, then site), and `"preview"` opts out of a default
14
+ above it, so a big example on a code-first page takes `{"view": "preview"}`. A misspelt value
15
+ fails the build in the site config and is reported in page metadata. `setExampleView` (from
16
+ `tosijs-ui/live-example`) sets it from code.
17
+ - A refresh button in the code panel's tab bar, beside ⋮, in every view. Editing does not re-run
18
+ an example, and ⌘S / ⌘R were undiscoverable.
19
+
20
+ ### Changed
21
+
22
+ - In the inline views (`{"view": "code"}`, `{"view": "console"}`) the toolbar column beside the
23
+ preview is gone: it stood between the preview and its code. Maximize and the new-window editor
24
+ are in the code panel's tab bar. "view/edit code" (the code is already open) and the tests
25
+ check (inline tests always run) are not shown there. Maximized and plain examples are
26
+ unchanged.
27
+
28
+ ### Fixed
29
+
30
+ - `<tosi-tabs>` with more tabs than fit pushed its `after-tabs` controls past its clipped edge:
31
+ at phone width a live example's close button was pushed past the panel's clipped edge. The tab names now
32
+ scroll (swipe, trackpad, or the mouse wheel over them) and the controls stay where they are.
33
+ Selecting a tab scrolls it into view, and the underline follows the scroll, clipped to the
34
+ strip.
35
+
3
36
  ## 1.17.3
4
37
 
5
38
  > A doc site answers `/slug` as well as `/slug/`, and a missing page gets a real 404 page.
@@ -1,6 +1,6 @@
1
1
  import { type AccessibilityMode } from './doc-system/render.js';
2
2
  import { TestResults } from './live-example/test-harness.js';
3
- import { type ExamplePolicy } from './doc-system/example-policy.js';
3
+ import { type ExamplePolicy, type ExampleView } from './doc-system/example-policy.js';
4
4
  export declare const hasTestBlock: (text: string | undefined, policy?: ExamplePolicy) => boolean;
5
5
  export interface PageTestResults {
6
6
  passed: boolean;
@@ -45,6 +45,8 @@ export interface Doc {
45
45
  header?: boolean;
46
46
  /** `'full-width'` drops the reading measure; `'full-screen'` also puts the nav away. */
47
47
  layout?: 'full-width' | 'full-screen';
48
+ /** the view this page's live examples open in (see the site `Doc.exampleView`) */
49
+ exampleView?: ExampleView;
48
50
  testStatus?: 'passed' | 'failed' | 'pending';
49
51
  bakes?: Array<[string, {
50
52
  dialect: string;
@@ -110,7 +110,7 @@ import { LiveExample, testManager, pageTestCount, enableTests, disableTests, } f
110
110
  import { highlightBlocks } from './doc-system/highlight.js';
111
111
  import { examplePolicy } from './live-example/insert-examples.js';
112
112
  import { collectCodeTokens } from './doc-system/code-fences.js';
113
- import { isLiveFence } from './doc-system/example-policy.js';
113
+ import { isExampleView, isLiveFence, } from './doc-system/example-policy.js';
114
114
  import { tosiSidenav, TosiSidenav } from './side-nav.js';
115
115
  import { icons } from './icons.js';
116
116
  import { tosiPalette } from './palette.js';
@@ -957,6 +957,19 @@ export function createDocBrowser(options) {
957
957
  // render from doc text. Every navigation funnels through navigateTo() -> showDoc().
958
958
  const docContent = contentElement || div();
959
959
  docContent.classList.add('doc-content');
960
+ /*
961
+ A page's `exampleView` (#252), where its examples look for it: on the content they sit in. A
962
+ value the metadata block got wrong is reported, not ignored, as frontmatter's is.
963
+ */
964
+ const stampExampleView = (doc) => {
965
+ const view = doc.exampleView;
966
+ if (view !== undefined && !isExampleView(view))
967
+ console.error(`${doc.path || doc.filename}: unknown exampleView ${JSON.stringify(view)}`);
968
+ if (isExampleView(view))
969
+ docContent.setAttribute('data-example-view', view);
970
+ else
971
+ docContent.removeAttribute('data-example-view');
972
+ };
960
973
  Object.assign(docContent.style, {
961
974
  display: 'block',
962
975
  // Same var the pre-hydration `:not(:defined)` layout uses (doc-system-styles.ts).
@@ -1132,6 +1145,7 @@ export function createDocBrowser(options) {
1132
1145
  rewriteContentLinks();
1133
1146
  // Stamp each example with its source file (for the source↔doc map). doc.path
1134
1147
  // is the extracted file (.md, or a source file with doc comments).
1148
+ stampExampleView(doc);
1135
1149
  LiveExample.insertExamples(docContent, context, doc.path || undefined);
1136
1150
  focusableCodeBlocks(docContent);
1137
1151
  /*
@@ -1370,6 +1384,7 @@ export function createDocBrowser(options) {
1370
1384
  if (view === 'preview') {
1371
1385
  docContent.innerHTML = renderDocMarkdown(docMarkdownFromSource(editUI.editor.value, editUI.doc.path), { wikilinks, accessible: !!accessibility, title: editUI.doc.title });
1372
1386
  rewriteContentLinks();
1387
+ stampExampleView(editUI.doc);
1373
1388
  LiveExample.insertExamples(docContent, context, editUI.doc.path || undefined);
1374
1389
  focusableCodeBlocks(docContent);
1375
1390
  }
@@ -71,6 +71,15 @@ export declare const EXAMPLE_TABS: readonly ["js", "html", "css", "test", "conso
71
71
  export type ExampleTab = (typeof EXAMPLE_TABS)[number];
72
72
  /** Why `value` is not a list of example tabs, or undefined when it is one. */
73
73
  export declare function exampleTabsProblem(value: unknown): string | undefined;
74
+ /**
75
+ * The view a live example opens in (#252): `preview` (the default), `code` (the preview beside
76
+ * its code) or `console` (the console beside the code). Set per fence (`{"view": "code"}`), per
77
+ * page (`exampleView` in its metadata) or per site (`SiteConfig.exampleView`); the nearest wins,
78
+ * so `preview` opts one example or page out of a default.
79
+ */
80
+ export declare const EXAMPLE_VIEWS: readonly ["preview", "code", "console"];
81
+ export type ExampleView = (typeof EXAMPLE_VIEWS)[number];
82
+ export declare function isExampleView(value: unknown): value is ExampleView;
74
83
  /**
75
84
  * Will this fence become a live example?
76
85
  *
@@ -118,6 +118,16 @@ export function exampleTabsProblem(value) {
118
118
  .join(', ')} (the tabs are ${EXAMPLE_TABS.join(', ')})`
119
119
  : undefined;
120
120
  }
121
+ /**
122
+ * The view a live example opens in (#252): `preview` (the default), `code` (the preview beside
123
+ * its code) or `console` (the console beside the code). Set per fence (`{"view": "code"}`), per
124
+ * page (`exampleView` in its metadata) or per site (`SiteConfig.exampleView`); the nearest wins,
125
+ * so `preview` opts one example or page out of a default.
126
+ */
127
+ export const EXAMPLE_VIEWS = ['preview', 'code', 'console'];
128
+ export function isExampleView(value) {
129
+ return EXAMPLE_VIEWS.includes(value);
130
+ }
121
131
  /**
122
132
  * Will this fence become a live example?
123
133
  *
@@ -76,6 +76,11 @@ export interface Doc {
76
76
  * reach them from out here.
77
77
  */
78
78
  layout?: 'full-width' | 'full-screen';
79
+ /**
80
+ * The view this page's live examples open in (1.17.4, #252): `'code'`, `'console'`, or
81
+ * `'preview'` to opt out of the site's `exampleView`. A fence's `{"view": …}` still wins.
82
+ */
83
+ exampleView?: 'preview' | 'code' | 'console';
79
84
  headTitle?: string;
80
85
  description?: string;
81
86
  keywords?: string | string[];
@@ -160,6 +160,13 @@ export function parseFrontmatter(content) {
160
160
  else if (val)
161
161
  console.error(`unknown layout ${JSON.stringify(val)} in frontmatter`);
162
162
  }
163
+ else if (key === 'exampleview') {
164
+ // Same reasoning as layout: a misspelt view would silently do nothing.
165
+ if (val === 'preview' || val === 'code' || val === 'console')
166
+ data.exampleView = val;
167
+ else if (val)
168
+ console.error(`unknown exampleView ${JSON.stringify(val)} in frontmatter`);
169
+ }
163
170
  else if (key === 'header') {
164
171
  if (/^(false|no|0)$/i.test(val))
165
172
  data.header = false;
@@ -1,8 +1,9 @@
1
- import type { ExampleTab } from '../example-policy.js';
1
+ import type { ExampleTab, ExampleView } from '../example-policy.js';
2
2
  export interface PageGlobalsConfig {
3
3
  liveExamples?: 'auto' | 'opt-in' | 'no-code';
4
4
  exampleConsole?: boolean;
5
5
  exampleTabs?: ExampleTab[];
6
+ exampleView?: ExampleView;
6
7
  dialects?: string[];
7
8
  }
8
9
  /**
@@ -13,5 +14,6 @@ export interface PageGlobalsConfig {
13
14
  export declare function siteSwitchProblem(config: {
14
15
  liveExamples?: string;
15
16
  kind?: string;
17
+ exampleView?: string;
16
18
  }): string | null;
17
19
  export declare function pageGlobalsHead(config: PageGlobalsConfig): string;
@@ -6,7 +6,7 @@ bundle reads when it runs (so they must come BEFORE the bundle script — genera
6
6
  Each is emitted only when it differs from the default, so a default site's HTML is unchanged
7
7
  byte-for-byte when a new switch is added.
8
8
  */
9
- import { exampleTabsProblem, normalizeDialectNames } from '../example-policy.js';
9
+ import { EXAMPLE_VIEWS, exampleTabsProblem, normalizeDialectNames, } from '../example-policy.js';
10
10
  const LIVE_EXAMPLE_POLICIES = ['auto', 'opt-in', 'no-code'];
11
11
  const SITE_KINDS = ['documentation', 'book'];
12
12
  /**
@@ -19,7 +19,8 @@ export function siteSwitchProblem(config) {
19
19
  ? `SiteConfig.${name} is ${JSON.stringify(value)}; it must be one of ${allowed.map((a) => `'${a}'`).join(', ')}`
20
20
  : null;
21
21
  return (wrong('liveExamples', config.liveExamples, LIVE_EXAMPLE_POLICIES) ??
22
- wrong('kind', config.kind, SITE_KINDS));
22
+ wrong('kind', config.kind, SITE_KINDS) ??
23
+ wrong('exampleView', config.exampleView, [...EXAMPLE_VIEWS]));
23
24
  }
24
25
  export function pageGlobalsHead(config) {
25
26
  // A misspelt tab would hide a tab on every example with nothing to say why.
@@ -35,6 +36,10 @@ export function pageGlobalsHead(config) {
35
36
  config.exampleTabs
36
37
  ? `<script>globalThis.__TOSI_EXAMPLE_TABS=${JSON.stringify(config.exampleTabs)}</script>`
37
38
  : '',
39
+ // #252: the view examples open in
40
+ config.exampleView && config.exampleView !== 'preview'
41
+ ? `<script>globalThis.__TOSI_EXAMPLE_VIEW="${config.exampleView}"</script>`
42
+ : '',
38
43
  // 1.16.2: the example console
39
44
  config.exampleConsole === false
40
45
  ? `<script>globalThis.__TOSI_EXAMPLE_CONSOLE=false</script>`
@@ -8,7 +8,7 @@ import type { EditableDataOptions } from './data-store.js';
8
8
  import type { BookManifest } from '../book-manifest.js';
9
9
  import type { AccessibilityMode } from '../render.js';
10
10
  import type { AltTextHook } from './alt-text.js';
11
- import type { ExampleTab } from '../example-policy.js';
11
+ import type { ExampleTab, ExampleView } from '../example-policy.js';
12
12
  export type SiteHost = 'github-pages' | 'firebase' | 'static';
13
13
  /** Resolved paths handed to a `libraryBuild` override (see SiteConfig). */
14
14
  export interface LibraryBuildContext {
@@ -200,6 +200,14 @@ export interface SiteConfig {
200
200
  * `{"tabs": ["html"]}`. A hidden tab's block still runs; it just can't be edited.
201
201
  */
202
202
  exampleTabs?: ExampleTab[];
203
+ /**
204
+ * The view every live example opens in (1.17.4, #252): `'code'` puts the preview beside its
205
+ * code, `'console'` the console beside the code. Default `'preview'`, the example alone. For
206
+ * a book whose small examples should show their code: set it once here instead of
207
+ * `{"view": "code"}` on every fence. A page overrides it with `exampleView` in its metadata,
208
+ * and one example with the fence option `{"view": "preview"}`.
209
+ */
210
+ exampleView?: ExampleView;
203
211
  /**
204
212
  * Run script examples from same-origin modules (1.17.0, #245), for a site served under a
205
213
  * Content Security Policy without `'unsafe-eval'` (`script-src 'self'`).