tosijs-ui 1.16.31 → 1.16.32

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,98 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.16.32
4
+
5
+ Two new components, a doc search that ranks, and live examples whose toolbar no longer covers
6
+ what they draw. Nothing an adopter has to change: the toolbar and the search are doc-site
7
+ behaviour, and both components are new. One thing to know if you embed the doc browser in an
8
+ app: it now opens its search on Cmd/Ctrl-K. If your page owns that key, pass
9
+ `createDocBrowser({ searchShortcut: '' })` (or another key).
10
+
11
+ `dist/iife.js` grows by about 7 KB gzip (496.4 KB to 503.4 KB, +1.4%): the palette is 9.1 KB
12
+ gzip and the affix 5.0 KB. The ESM path pays only for what it imports.
13
+
14
+ ### New components
15
+
16
+ - **`<tosi-palette>`** (`tosiPalette`): a command palette. A compact search-field trigger
17
+ opens a centred modal of ranked results. Results come from `sources` (sync, or async with a
18
+ spinner and a `timeout`, 10s by default; each keystroke aborts the last search), are drawn
19
+ per `kind` by `renderers` (or a default row: icon, highlighted title, one-line description,
20
+ path), ranked by `score` and grouped by `group` (or by your own `rank`), with `quickLinks`
21
+ before anything is typed. Arrow keys, Enter and Escape drive it, `shortcut: 'mod+k'` opens it
22
+ from anywhere (a shortcut with an unknown modifier is ignored, with a console warning, rather
23
+ than binding the bare key), `multiline` makes the query a growing prompt, and choosing fires `action`.
24
+ `trigger` is `field` (the default, which shrinks to its icon below `compactWidth`, 160px),
25
+ `icon`, or `none` for a palette opened only by `open()` or its shortcut. Its colours come
26
+ from the theme's root `--tosi-text` / `--tosi-bg` / `--tosi-accent`, so a recoloured header
27
+ does not make its results invisible. The dialog sets its own line height, wrapping and font
28
+ size, so a header's do not reach it. The palette footer's Close is a button, for a phone,
29
+ which has no Escape key; on a touch screen the arrow/Enter cues and the Esc hint are hidden.
30
+ The ✕ that clears the query is hidden until something is typed.
31
+ - **`<tosi-affix>`** (`tosiAffix`): fixes a fixture (`slot="fixture"`) to an edge of its
32
+ content, in the FloatPosition vocabulary: the first letter is the edge, the second the
33
+ alignment, so `en` is the right edge, top-aligned. Outside (the default) it takes a grid cell
34
+ beside the content, so it never covers it and an ancestor's overflow cannot clip it; `inside`
35
+ puts it within the content against that edge. `fallback` (e.g. `"s inside"`) is used while
36
+ the fixture is longer than its edge. Setting `position`/`inside` sets where it is wanted,
37
+ reading them gives where it is, the `placed` attribute reflects it, and a `reposition` event
38
+ fires when it changes. It looks finished with no styling: a flex column on a side edge, a row
39
+ on top or bottom, the theme's inset background and spacing, outer corners rounded
40
+ (`--tosi-affix-bg/gap/padding/radius`, `::part(fixture)`).
41
+
42
+ ### Doc system
43
+
44
+ - **Search ranks pages, in the header.** The sidebar's nav filter (a substring test that hid
45
+ pages and ranked nothing) is replaced by a palette in the header bar: the site's search
46
+ field while there is room, its icon when the header is narrow, and Cmd/Ctrl-K on every page,
47
+ `{"header": false}` pages included. Pages are ranked by rough relevance (title, then
48
+ headings, keywords, description, opening paragraph, body), and a matching heading becomes a
49
+ deep link. A query word matches only the start of a word, camelCase split ("date" finds
50
+ `tosiDate`, not "update"), a whole word or its plural outranks a prefix, and a word used
51
+ often counts for more than one used once. A word matched in one heading no longer counts for
52
+ the next query word. The trigger looks like the search field it replaced (the site's
53
+ pill-shaped input), and the active row is a tint of the theme's accent. The ranking is
54
+ exported as `buildDocIndex` / `searchDocs` from `tosijs-ui/doc-system/doc-search`, with
55
+ section anchors taken from the renderer's own heading ids. `createDocBrowser({ searchShortcut })` sets the key
56
+ that opens the search, `''` for none.
57
+ - **A live example's toolbar sits beside the preview**, outside its outline, instead of a
58
+ pocket bar over the preview's top-right corner (which covered whatever the example drew
59
+ there, the crud example's New button for one). It moves inside along the bottom when the
60
+ column is taller than the preview, and when the example is maximized. The run-tests check
61
+ shows the test status; the owl handle is gone.
62
+ - Below 600px of header the link icons fold into the settings (⋮) menu, so an upright phone
63
+ has room for the title and the search.
64
+
65
+ ### Fixed
66
+
67
+ - `<tosi-crud>` has a Close button (and `close()`) that returns to the list. Under 720px (the
68
+ doc preview included) an open record replaces the list instead of squeezing into a 210px
69
+ column beside it, and the component fills a container with a height. A store that can only
70
+ list no longer shows New, Save and Delete where the page styles `button` with a `display`.
71
+ Without a schema, New starts from the fields the listed rows have instead of an empty form.
72
+ - Boolean columns from a schema asked for icons named "✓" and "✗", which do not exist; they
73
+ use `check` and `x`.
74
+ - `<tosi-table>` with no `columns` and data that arrived later showed no columns: it now
75
+ infers them once there is a row.
76
+ - `<tosi-schema-form>` with no schema kept the schema inferred from an empty value forever, and
77
+ threw "unobserve failed, listener not found" every time it left the page.
78
+
79
+ ### Dependencies
80
+
81
+ - The optional `happy-dom` peer is now `^21.0.4` (was `^20.7.0`), and tosijs-ui's own tests
82
+ run on 21.0.4. Every happy-dom from 17.0.0 to 21.0.3 is affected by
83
+ [GHSA-834v-8h66-3h79](https://github.com/advisories/GHSA-834v-8h66-3h79) (an unsanitized
84
+ import alias leads to host code execution), and there is no fixed 20.x. It matters only if
85
+ you use `tosijs-ui/test-setup` or build ePubs; then upgrade to 21.0.4. Its one breaking
86
+ change is that CSS and iframe loading are off by default, which neither relies on.
87
+
88
+ ### Tunnel
89
+
90
+ - A workspace no longer makes a request wait on the box for the revoked-device list. A
91
+ copy up to ten minutes old is used at once and refreshed behind, and when the list becomes
92
+ unreadable the dev server says so, and why. `tosijs-tunnel` refuses flag combinations it
93
+ used to ignore silently: `--share-ttl` with `--global` or without `--link --share`, and
94
+ `--label` anywhere but on a `--link --global` edit link.
95
+
3
96
  ## 1.16.31
4
97
 
5
98
  **Tunnel users: each browser needs one new `tosijs-tunnel --link --global`.** The edit cookie
@@ -422,6 +422,35 @@ export interface Argv {
422
422
  rest: string[]
423
423
  }
424
424
 
425
+ /**
426
+ * `tosijs-tunnel` flag combinations that would otherwise be silently ignored, each with what to
427
+ * do instead (1.16.31 review). A flag that changes nothing is a promise the bin does not keep.
428
+ */
429
+ export function tunnelFlagProblems(args: {
430
+ has: (name: string) => boolean
431
+ flag: (name: string) => string | undefined
432
+ }): string[] {
433
+ const problems: string[] = []
434
+ const link = args.has('link')
435
+ const share = args.has('share')
436
+ const global = args.has('global')
437
+ if (args.flag('share-ttl') !== undefined) {
438
+ if (global)
439
+ problems.push(
440
+ '--share-ttl does not apply to --global links: they always expire after 5 minutes. For a longer-lived view link, use --link --share --share-ttl=… without --global.'
441
+ )
442
+ else if (!(link && share))
443
+ problems.push('--share-ttl only applies to --link --share.')
444
+ }
445
+ if (args.flag('label') !== undefined) {
446
+ if (!(link && global) || share)
447
+ problems.push(
448
+ '--label names the device a --link --global EDIT link is for; it does nothing elsewhere.'
449
+ )
450
+ }
451
+ return problems
452
+ }
453
+
425
454
  /**
426
455
  * Parse `argv` against `spec`. Prints usage and exits on `--help` (0) or on an
427
456
  * unrecognised flag (1); returns a reader otherwise.
package/bin/tunnel.ts CHANGED
@@ -48,6 +48,7 @@ import {
48
48
  parseDeviceListing,
49
49
  DEVICE_LISTING_SEPARATOR,
50
50
  DEVICE_ID_PATTERN,
51
+ tunnelFlagProblems,
51
52
  } from './resolve-site-config.ts'
52
53
 
53
54
  const { has, flag } = parseArgv(process.argv.slice(2), {
@@ -81,6 +82,15 @@ const { has, flag } = parseArgv(process.argv.slice(2), {
81
82
  ` --port= --local-port= --remote-port= override the resolved ports`,
82
83
  })
83
84
 
85
+ {
86
+ const problems = tunnelFlagProblems({ has, flag })
87
+ if (problems.length) {
88
+ for (const problem of problems) console.error(`\n${problem}`)
89
+ console.error('')
90
+ process.exit(1)
91
+ }
92
+ }
93
+
84
94
  const siteConfig = await resolveSiteConfig()
85
95
 
86
96
  const preview = siteConfig.preview
@@ -0,0 +1,97 @@
1
+ import { Component, ElementCreator, PartsMap } from 'tosijs';
2
+ export type AffixPosition = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw' | 'en' | 'wn' | 'es' | 'ws';
3
+ export interface AffixPlacement {
4
+ position: AffixPosition;
5
+ inside: boolean;
6
+ }
7
+ /** `position` as written, or the default when it is not one. */
8
+ export declare function affixPosition(value: unknown): AffixPosition;
9
+ /** `"s inside"` → `{position: 's', inside: true}`; '' → null (no fallback). */
10
+ export declare function parsePlacement(value: unknown): AffixPlacement | null;
11
+ /** `{position: 's', inside: true}` → `"s inside"`. */
12
+ export declare const describePlacement: ({ position, inside }: AffixPlacement) => string;
13
+ type Track = 1 | 2 | 3;
14
+ type Align = 'start' | 'center' | 'end';
15
+ /**
16
+ * The grid cell and alignment for a placement. Outside, the fixture takes the cell on its
17
+ * edge, hugging the content; inside, it shares the content's cell, against that edge.
18
+ */
19
+ export declare function affixCell({ position, inside }: AffixPlacement): {
20
+ row: Track;
21
+ column: Track;
22
+ alignSelf: Align;
23
+ justifySelf: Align;
24
+ direction: string;
25
+ edge: "n" | "s" | "e" | "w";
26
+ };
27
+ interface AffixParts extends PartsMap {
28
+ content: HTMLSlotElement;
29
+ fixture: HTMLSlotElement;
30
+ }
31
+ export declare class TosiAffix extends Component<AffixParts> {
32
+ static initAttributes: Record<string, unknown>;
33
+ /** Where the fixture goes when the wanted place does not fit: `"s inside"`, or '' for nowhere. */
34
+ fallback: string;
35
+ static preferredTagName: string;
36
+ static shadowStyleSpec: {
37
+ ':host': {
38
+ display: string;
39
+ gridTemplateColumns: string;
40
+ gridTemplateRows: string;
41
+ '--tosi-affix-direction': string;
42
+ };
43
+ ':host([hidden])': {
44
+ display: string;
45
+ };
46
+ ':host > [part="content"]': {
47
+ display: string;
48
+ gridArea: string;
49
+ minWidth: string;
50
+ minHeight: string;
51
+ position: string;
52
+ };
53
+ ':host > [part="fixture"]': {
54
+ display: string;
55
+ flexDirection: string;
56
+ alignItems: string;
57
+ zIndex: string;
58
+ gap: string;
59
+ padding: string;
60
+ background: string;
61
+ color: string;
62
+ };
63
+ ':host > [part="fixture"][hidden]': {
64
+ display: string;
65
+ };
66
+ };
67
+ content: () => HTMLSlotElement[];
68
+ private _wanted;
69
+ private _placed;
70
+ private _wantedLength;
71
+ private _observer;
72
+ /** Where the fixture is (set: where it should be). */
73
+ get position(): AffixPosition;
74
+ set position(value: string);
75
+ /** Whether the fixture is inside the content (set: whether it should be). */
76
+ get inside(): boolean;
77
+ set inside(value: boolean | string);
78
+ /** Where the fixture should be, whatever fits. */
79
+ get wanted(): AffixPlacement;
80
+ /** An empty fixture slot is hidden, so it takes no room and draws no background. */
81
+ private syncEmpty;
82
+ handleSlotChange: () => void;
83
+ connectedCallback(): void;
84
+ private _observed;
85
+ observeContent: () => void;
86
+ disconnectedCallback(): void;
87
+ /** The fixture's and the content's lengths along `edge`, as laid out now. */
88
+ private lengths;
89
+ /** Choose where the fixture goes, lay it out there, and announce a change. */
90
+ place: () => void;
91
+ /** Lay the fixture out at `placement`, and announce it if that is a change. */
92
+ private layout;
93
+ private apply;
94
+ render(): void;
95
+ }
96
+ export declare const tosiAffix: ElementCreator<TosiAffix>;
97
+ export {};