tosijs-ui 1.7.0 → 1.7.2
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 +80 -0
- package/dist/babylon-3d.d.ts +1 -1
- package/dist/babylon-3d.js +1 -1
- package/dist/code-editor-cm.js +5 -1
- package/dist/code-editor.d.ts +1 -1
- package/dist/code-editor.js +1 -1
- package/dist/data-table.d.ts +3 -1
- package/dist/data-table.js +97 -5
- package/dist/doc-browser.js +6 -1
- package/dist/doc-system/doc-system-styles.js +11 -0
- package/dist/doc-system/site/dev-server.d.ts +11 -0
- package/dist/doc-system/site/dev-server.js +26 -3
- package/dist/doc-system/site/generate-site.d.ts +18 -0
- package/dist/doc-system/site/generate-site.js +42 -10
- package/dist/doc-system/site/site-config.d.ts +10 -3
- package/dist/hydrate/{ajs-language-h1x0ydap.js → ajs-language-qm5w38ae.js} +3 -3
- package/dist/hydrate/{ajs-language-h1x0ydap.js.map → ajs-language-qm5w38ae.js.map} +1 -1
- package/dist/hydrate/{code-editor-cm-2hgj9002.js → code-editor-cm-95n31m4f.js} +8 -8
- package/dist/hydrate/{code-editor-cm-2hgj9002.js.map → code-editor-cm-95n31m4f.js.map} +3 -3
- package/dist/hydrate/hydrate.js +78 -78
- package/dist/hydrate/hydrate.js.map +20 -19
- package/dist/hydrate/{index-nsmyq9mv.js → index-0ps8jzdn.js} +3 -3
- package/dist/hydrate/{index-nsmyq9mv.js.map → index-0ps8jzdn.js.map} +1 -1
- package/dist/hydrate/{index-iife-64z9yngn.js → index-iife-415dypjp.js} +3 -3
- package/dist/hydrate/{index-iife-64z9yngn.js.map → index-iife-415dypjp.js.map} +1 -1
- package/dist/hydrate/index-iife-kwhe0yt8.js +27 -0
- package/dist/hydrate/{index-iife-5wzyvyzm.js.map → index-iife-kwhe0yt8.js.map} +2 -2
- package/dist/hydrate/{index-iife-x2h4f6fc.js → index-iife-ype8sch3.js} +3 -3
- package/dist/hydrate/{index-iife-x2h4f6fc.js.map → index-iife-ype8sch3.js.map} +1 -1
- package/dist/hydrate/{tjs-import-resolver-akt3k27z.js → tjs-import-resolver-s4z0w296.js} +2 -2
- package/dist/iife.js +70 -70
- package/dist/iife.js.map +21 -20
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/live-example/component.d.ts +8 -1
- package/dist/live-example/component.js +8 -0
- package/dist/live-example/styles.d.ts +8 -1
- package/dist/live-example/styles.js +17 -1
- package/dist/live-theme.js +98 -0
- package/dist/side-nav.d.ts +1 -1
- package/dist/side-nav.js +4 -4
- package/dist/size-break.d.ts +1 -1
- package/dist/size-break.js +3 -3
- package/dist/tab-selector.d.ts +11 -2
- package/dist/tab-selector.js +19 -6
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/value-renderer.d.ts +26 -0
- package/dist/value-renderer.js +209 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/dist/bp-loader.d.ts +0 -1
- package/dist/bp-loader.js +0 -26
- package/dist/hydrate/index-iife-5wzyvyzm.js +0 -27
- /package/dist/hydrate/{tjs-import-resolver-akt3k27z.js.map → tjs-import-resolver-s4z0w296.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.7.2
|
|
4
|
+
|
|
5
|
+
- **Fix: doc sites are now mount-point-agnostic** (issue #25). The build baked `basePath` into
|
|
6
|
+
every functional URL (nav/content links, `scriptUrl`, `stylesUrl`, favicon, `docsUrl`), so a
|
|
7
|
+
build was locked to one mount — adding a custom domain to a GitHub project page flipped the
|
|
8
|
+
live site to the root while `docs/` still pointed at `/repo`, 404ing every asset into a bare,
|
|
9
|
+
unstyled shell with no warning. Functional URLs are now emitted **relative to each page**, so
|
|
10
|
+
one build works at `/repo`, a custom-domain root, or a moved mount with no rebuild. *Metadata*
|
|
11
|
+
URLs (`canonical`, `og:url`, `og:image`, `sitemap.xml`) stay absolute via `baseUrl`+`basePath`
|
|
12
|
+
— so `basePath` now only affects metadata, never the page's assets. (The hydrated SPA's own
|
|
13
|
+
nav/`pushState` and `__TJS_LOCAL_BASE` remain root-relative — tracked in #16.)
|
|
14
|
+
- **Fix: the haltija dev channel loaded once per background-test iframe.** The doc-browser's
|
|
15
|
+
background test runner loads every page-with-tests in a hidden iframe, each served the injected
|
|
16
|
+
dev-channel loader — so `dev.js` was imported N times into throwaway frames. The loader is now
|
|
17
|
+
gated to the top window (`self===top`).
|
|
18
|
+
- **Live examples get a rounded, padded card.** `.preview` gains `padding` (breathing room around
|
|
19
|
+
rendered content) and its first child's top margin is dropped; the inset border moves to the
|
|
20
|
+
example's `:host` with a `border-radius`, so the whole example (preview + editors) reads as one
|
|
21
|
+
bordered card. CodeMirror tooltips render fixed-positioned so autocomplete isn't clipped by the
|
|
22
|
+
card's `overflow: hidden`.
|
|
23
|
+
- **Removed the "blueprint loading" doc page.** It documented tosijs's own
|
|
24
|
+
`<xin-loader>`/`<xin-blueprint>` elements (no tosijs-ui component), so it belongs in tosijs's
|
|
25
|
+
docs. The generic `blueprint` icon stays.
|
|
26
|
+
|
|
27
|
+
## 1.7.1
|
|
28
|
+
|
|
29
|
+
- **Fix: components looked wrong in the doc site's dark mode** (e.g. `<tosi-table>` stayed
|
|
30
|
+
white). The doc-system theme drives the legacy `--background`/`--text-color` family and never
|
|
31
|
+
set the `--tosi-*` component palette, so components fell back to their baked-in light defaults
|
|
32
|
+
(`var(--tosi-bg, #fff)`). The doc-system now bridges `--tosi-accent`/`--tosi-bg`/`--tosi-text`/
|
|
33
|
+
`--tosi-bg-inset` onto its own colors **as references**, so every `--tosi-*` component follows
|
|
34
|
+
the site theme automatically — including dark mode (the referenced vars flip via
|
|
35
|
+
`invertLuminance`). The data-table pinned-row example now uses `var(--tosi-table-bg)` instead of
|
|
36
|
+
a hardwired `#eee`.
|
|
37
|
+
- **Data-table `type` example** demonstrating the new column types (currency, number, percent,
|
|
38
|
+
fixed, bytes, boolean) added to the docs.
|
|
39
|
+
|
|
40
|
+
- **Fix: "edit page source" showed HTML instead of source in a dev server without
|
|
41
|
+
`editableSources`.** The `/__docstore/source` endpoint was only routed when
|
|
42
|
+
`editableSources` was on; otherwise the request fell through to the SPA `index.html`
|
|
43
|
+
fallback and returned the rendered page at status 200 — so the client loaded HTML *as* the
|
|
44
|
+
source (and save read HTML). The endpoint is now handled unconditionally and answers a clean
|
|
45
|
+
`501` when editing is disabled, so `loadSource` falls back to the GitHub raw source (read-only
|
|
46
|
+
editing, matching the deployed site). Belt-and-suspenders: the client also rejects any
|
|
47
|
+
`text/html` response from that endpoint (guards SPA-rewrite hosts too). Documented the option
|
|
48
|
+
more fully in `doc-site-system.md`.
|
|
49
|
+
- **Local example save now gives feedback.** "Save changes (local)" writes a per-browser
|
|
50
|
+
scratchpad (not the file) — it used to do so silently, reading as a no-op. It now posts a
|
|
51
|
+
toast (`postNotification`): *"Saved to this browser only — use 'Save to source' to write the
|
|
52
|
+
file."*
|
|
53
|
+
|
|
54
|
+
- **`valueRenderer(type)` + declarative `<tosi-table>` column `type`.** A new exported helper
|
|
55
|
+
turns a compact type string into a reusable, locale-aware renderer with a sensible default
|
|
56
|
+
alignment: `number`, `currency`/`currency(USD)`, `fixed`/`fixed(n)` (default 2),
|
|
57
|
+
`percent`/`percent(n)` (of a fraction; default 0), `sci`, `eng`, `bytes`/`bytes(iec)` (SI ÷1000
|
|
58
|
+
or IEC ÷1024 units, right-aligned), and `boolean`/`boolean(t)`/`boolean(t,f)` (icons via the
|
|
59
|
+
`icons` proxy, centered; default `checkSquare`/`square`). Numeric cells also get a
|
|
60
|
+
`-negative` / `-zero` **state class** by value sign, so you get red negatives (etc.) from one
|
|
61
|
+
CSS rule — no custom cell renderer. Data-table columns take a `type`
|
|
62
|
+
(`ValueRendererType`) and format + align automatically — no hand-rolled `dataCell`; an explicit
|
|
63
|
+
`align`/`dataCell` still wins. Formatting follows `setLocale()`. Verified end-to-end (unit +
|
|
64
|
+
browser).
|
|
65
|
+
|
|
66
|
+
- **Deprecation cleanup: `on<Event>` component callbacks → `handle<Event>`.** tosijs reserves the
|
|
67
|
+
`on<Event>` prefix for elements-factory event-handler sugar and now warns when a component
|
|
68
|
+
*defines* such a property. Renamed the internal ones: `onResize` → `handleResize` (`size-break`,
|
|
69
|
+
`side-nav`, `code-editor`, `babylon-3d`, `tab-selector`) and `onScrollEnd` → `handleScrollEnd`
|
|
70
|
+
(`data-table`). Verified: those pages now load with zero `on<Event>` deprecation warnings.
|
|
71
|
+
(`<tosi-tabs>`'s public `onCloseTab` still uses the old name — a breaking rename deferred to a
|
|
72
|
+
deprecation-alias pass; and the `tosiValue()`/`tosiPath()` accessor deprecation is a separate,
|
|
73
|
+
larger cleanup — both tracked.)
|
|
74
|
+
|
|
75
|
+
- **CSS canary** — an inline doc-test on the `live-theme` page that smoke-tests the whole
|
|
76
|
+
styling chain in a real browser (`StyleSheet()` → the `vars` proxy → scaled-var `calc()` →
|
|
77
|
+
`createTheme`/`applyTheme` → cascade → `getComputedStyle`), plus a theme color-change
|
|
78
|
+
propagating to computed style, dark-mode luminance inversion, and `Color.inverseLuminance`.
|
|
79
|
+
Each link is silent when it breaks (happy-dom can't resolve `var()`/the cascade), so a red
|
|
80
|
+
now means a system-level CSS break, not a component quirk. Runs in the `doc-tests.pw.ts` CI
|
|
81
|
+
gate.
|
|
82
|
+
|
|
3
83
|
## 1.7.0
|
|
4
84
|
|
|
5
85
|
> ### ⚠️ BREAKING CHANGE — `<tosi-code>` moved from ACE to CodeMirror 6
|
package/dist/babylon-3d.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export declare class B3d extends WebComponent {
|
|
|
51
51
|
sceneCreated: B3dCallback;
|
|
52
52
|
update: B3dCallback;
|
|
53
53
|
private _update;
|
|
54
|
-
|
|
54
|
+
handleResize(): void;
|
|
55
55
|
loadScene: (path: string, file: string, processCallback?: MeshProcessCallback) => Promise<void>;
|
|
56
56
|
loadUI: (options: B3dUIOptions) => Promise<any>;
|
|
57
57
|
connectedCallback(): void;
|
package/dist/babylon-3d.js
CHANGED
package/dist/code-editor-cm.js
CHANGED
|
@@ -10,7 +10,7 @@ Languages here are the always-available set (js/ts/css/html/markdown). First-cla
|
|
|
10
10
|
tjs (highlighting + runtime-value autocomplete via tjs-lang's editors/codemirror) is
|
|
11
11
|
layered in separately and only when tjs-lang is installed.
|
|
12
12
|
*/
|
|
13
|
-
import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, } from '@codemirror/view';
|
|
13
|
+
import { EditorView, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, tooltips, } from '@codemirror/view';
|
|
14
14
|
import { EditorState, Compartment, Annotation, Transaction, } from '@codemirror/state';
|
|
15
15
|
import { indentWithTab, history, defaultKeymap, historyKeymap, undo, redo, undoDepth, redoDepth, } from '@codemirror/commands';
|
|
16
16
|
import { indentUnit, foldGutter, foldKeymap, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, } from '@codemirror/language';
|
|
@@ -129,6 +129,10 @@ const editorTheme = EditorView.theme({
|
|
|
129
129
|
function baseExtensions() {
|
|
130
130
|
return [
|
|
131
131
|
editorTheme,
|
|
132
|
+
// Render autocomplete / hover tooltips in a fixed layer so they escape the
|
|
133
|
+
// live-example's `:host { overflow: hidden }` (the rounded card clip) instead
|
|
134
|
+
// of being cut off at the editor's bottom edge in a short example.
|
|
135
|
+
tooltips({ position: 'fixed' }),
|
|
132
136
|
lineNumbers(),
|
|
133
137
|
highlightActiveLineGutter(),
|
|
134
138
|
highlightSpecialChars(),
|
package/dist/code-editor.d.ts
CHANGED
package/dist/code-editor.js
CHANGED
package/dist/data-table.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Component as WebComponent, ElementCreator } from 'tosijs';
|
|
2
2
|
import { SortCallback } from './make-sorter';
|
|
3
|
+
import { ValueRendererType } from './value-renderer';
|
|
3
4
|
export interface ColumnOptions {
|
|
4
5
|
name?: string;
|
|
5
6
|
prop: string;
|
|
6
7
|
width: number;
|
|
7
8
|
visible?: boolean;
|
|
8
9
|
align?: string;
|
|
10
|
+
type?: ValueRendererType;
|
|
9
11
|
pinned?: 'left' | 'right';
|
|
10
12
|
sort?: false | 'ascending' | 'descending';
|
|
11
13
|
headerCell?: (options: ColumnOptions) => HTMLElement;
|
|
@@ -201,7 +203,7 @@ export declare class TosiTable extends WebComponent {
|
|
|
201
203
|
private updateSelection;
|
|
202
204
|
private findCell;
|
|
203
205
|
private _pendingFocus;
|
|
204
|
-
private
|
|
206
|
+
private handleScrollEnd;
|
|
205
207
|
private focusCell;
|
|
206
208
|
private handleKeyNav;
|
|
207
209
|
connectedCallback(): void;
|
package/dist/data-table.js
CHANGED
|
@@ -194,6 +194,7 @@ export interface ColumnOptions {
|
|
|
194
194
|
width: number
|
|
195
195
|
visible?: boolean
|
|
196
196
|
align?: string
|
|
197
|
+
type?: string // valueRenderer type: 'currency(USD)', 'fixed(2)', 'bytes', 'boolean(check,x)', …
|
|
197
198
|
pinned?: 'left' | 'right'
|
|
198
199
|
sort?: false | 'ascending' | 'descending'
|
|
199
200
|
headerCell?: (options: ColumnOptions) => HTMLElement
|
|
@@ -201,6 +202,59 @@ export interface ColumnOptions {
|
|
|
201
202
|
}
|
|
202
203
|
```
|
|
203
204
|
|
|
205
|
+
## Column value types
|
|
206
|
+
|
|
207
|
+
Give a column a `type` and it's formatted (and aligned) automatically — no hand-rolled
|
|
208
|
+
`dataCell`. The `type` is a [`valueRenderer`](value-renderer) string:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
[
|
|
212
|
+
{ prop: 'name', width: 160 },
|
|
213
|
+
{ prop: 'price', width: 100, type: 'currency(USD)' }, // localized $, right-aligned
|
|
214
|
+
{ prop: 'weight', width: 100, type: 'fixed(3)' }, // 3 decimals, right-aligned
|
|
215
|
+
{ prop: 'size', width: 100, type: 'bytes' }, // 1.5 MB, right-aligned
|
|
216
|
+
{ prop: 'active', width: 60, type: 'boolean' }, // checkSquare / square, centered
|
|
217
|
+
]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Numeric types (`number`, `currency`, `fixed`, `percent`, `sci`, `eng`, `bytes`) right-align
|
|
221
|
+
by default; `boolean` centers and renders icons. An explicit `align` — or a `dataCell` —
|
|
222
|
+
always wins. Formatting follows the app locale (`setLocale()`). Numeric cells also get a
|
|
223
|
+
`-negative` or `-zero` state class by value sign, so the red "Change" values above come from
|
|
224
|
+
one CSS rule (`.-negative { color: #e44 }`), no cell renderer.
|
|
225
|
+
|
|
226
|
+
```js
|
|
227
|
+
import { tosiTable } from 'tosijs-ui'
|
|
228
|
+
|
|
229
|
+
const rows = [
|
|
230
|
+
{ item: 'Alpha Widget', price: 12.5, qty: 1240, change: 3.25, rate: 0.075, mass: 1.23456, size: 1_536_000, active: true, flagged: false },
|
|
231
|
+
{ item: 'Beta Gadget', price: 4.99, qty: 42, change: -1.5, rate: 0.2, mass: 0.5, size: 512, active: false, flagged: true },
|
|
232
|
+
{ item: 'Gamma Sprocket', price: 199, qty: 8, change: 0, rate: 1.5, mass: 12.005, size: 2_500_000_000, active: true, flagged: true },
|
|
233
|
+
{ item: 'Delta Cog', price: 0.75, qty: 99999, change: -0.12, rate: 0.004, mass: 0.001, size: 48_200, active: false, flagged: false },
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
const table = tosiTable({ style: { display: 'block', height: '240px' } })
|
|
237
|
+
table.value = {
|
|
238
|
+
array: rows,
|
|
239
|
+
columns: [
|
|
240
|
+
{ prop: 'item', name: 'Item', width: 150 },
|
|
241
|
+
{ prop: 'price', name: 'Price', width: 110, type: 'currency(USD)' },
|
|
242
|
+
{ prop: 'change', name: 'Change', width: 100, type: 'currency(USD)' }, // red negatives via CSS
|
|
243
|
+
{ prop: 'qty', name: 'Qty', width: 90, type: 'number' },
|
|
244
|
+
{ prop: 'rate', name: 'Rate', width: 80, type: 'percent(1)' },
|
|
245
|
+
{ prop: 'mass', name: 'Mass', width: 90, type: 'fixed(2)' },
|
|
246
|
+
{ prop: 'size', name: 'Size', width: 100, type: 'bytes' },
|
|
247
|
+
{ prop: 'active', name: 'Active', width: 80, type: 'boolean' },
|
|
248
|
+
{ prop: 'flagged', name: 'Flag', width: 80, type: 'boolean(check, x)' },
|
|
249
|
+
],
|
|
250
|
+
}
|
|
251
|
+
preview.append(table)
|
|
252
|
+
```
|
|
253
|
+
```css
|
|
254
|
+
.preview .-negative { color: #e44; }
|
|
255
|
+
.preview .-zero { opacity: 0.45; }
|
|
256
|
+
```
|
|
257
|
+
|
|
204
258
|
## Pinned Columns and Rows
|
|
205
259
|
|
|
206
260
|
Set `pinned: 'left'` or `pinned: 'right'` on individual columns to pin
|
|
@@ -314,11 +368,11 @@ preview.append(table)
|
|
|
314
368
|
width: 100%;
|
|
315
369
|
}
|
|
316
370
|
.preview tosi-table .pinned-bottom {
|
|
317
|
-
background: #
|
|
371
|
+
background: var(--tosi-table-bg, var(--tosi-bg, #fff));
|
|
318
372
|
font-weight: bold;
|
|
319
373
|
}
|
|
320
374
|
.preview .row-pinned .td {
|
|
321
|
-
background: #
|
|
375
|
+
background: var(--tosi-table-bg, var(--tosi-bg, #fff));
|
|
322
376
|
}
|
|
323
377
|
.preview .num-cell {
|
|
324
378
|
font-variant-numeric: tabular-nums;
|
|
@@ -513,6 +567,7 @@ As well as any column names you want localized.
|
|
|
513
567
|
import { Component as WebComponent, elements, vars, varDefault, tosiValue, getListItem, getListBinding, tosi, } from 'tosijs';
|
|
514
568
|
import { trackDrag } from './track-drag';
|
|
515
569
|
import { icons } from './icons';
|
|
570
|
+
import { valueRenderer } from './value-renderer';
|
|
516
571
|
import { popMenu } from './menu';
|
|
517
572
|
import * as dragAndDrop from './drag-and-drop';
|
|
518
573
|
import { tosiLocalized, localize } from './localize';
|
|
@@ -545,6 +600,20 @@ function defaultWidth(array, prop, charWidth) {
|
|
|
545
600
|
}
|
|
546
601
|
const { div, span, button } = elements;
|
|
547
602
|
const passThru = (array) => array;
|
|
603
|
+
// One ValueRenderer per typed column, cached: `Intl.*Format` construction is the
|
|
604
|
+
// expensive part and a column's renderer is stable for its lifetime. Returns null
|
|
605
|
+
// when the column has no `type` or supplies its own `dataCell` (which always wins).
|
|
606
|
+
const columnRenderers = new WeakMap();
|
|
607
|
+
function columnRenderer(col) {
|
|
608
|
+
if (!col.type || col.dataCell)
|
|
609
|
+
return null;
|
|
610
|
+
let renderer = columnRenderers.get(col);
|
|
611
|
+
if (!renderer) {
|
|
612
|
+
renderer = valueRenderer(col.type);
|
|
613
|
+
columnRenderers.set(col, renderer);
|
|
614
|
+
}
|
|
615
|
+
return renderer;
|
|
616
|
+
}
|
|
548
617
|
export class TosiTable extends WebComponent {
|
|
549
618
|
static preferredTagName = 'tosi-table';
|
|
550
619
|
// Layout: a single .scroll-area inside :host is the only scroll container
|
|
@@ -968,7 +1037,9 @@ export class TosiTable extends WebComponent {
|
|
|
968
1037
|
// position: sticky lives in `.col-pinned` (added by cellClasses), so only
|
|
969
1038
|
// the per-cell offsets need to be set inline here.
|
|
970
1039
|
const style = {
|
|
971
|
-
|
|
1040
|
+
// Explicit align wins; else the column type's default (numerics right,
|
|
1041
|
+
// booleans center); else left.
|
|
1042
|
+
justifyContent: col.align || columnRenderer(col)?.align || 'left',
|
|
972
1043
|
...extra,
|
|
973
1044
|
};
|
|
974
1045
|
if (si.left != null)
|
|
@@ -993,6 +1064,27 @@ export class TosiTable extends WebComponent {
|
|
|
993
1064
|
this.applyGridCellAttrs(cell, colIndex, si, style);
|
|
994
1065
|
return cell;
|
|
995
1066
|
}
|
|
1067
|
+
// A `type` column formats through its cached ValueRenderer. The binding's toDOM
|
|
1068
|
+
// runs per stamped row, so it stays locale-reactive and works for icon cells
|
|
1069
|
+
// (which replace children) as well as text.
|
|
1070
|
+
const renderer = columnRenderer(col);
|
|
1071
|
+
if (renderer) {
|
|
1072
|
+
return span({
|
|
1073
|
+
class: this.cellClasses('td', si),
|
|
1074
|
+
role: 'gridcell',
|
|
1075
|
+
tabindex: -1,
|
|
1076
|
+
ariaColindex: String(colIndex + 1),
|
|
1077
|
+
style,
|
|
1078
|
+
bind: {
|
|
1079
|
+
value: item[col.prop],
|
|
1080
|
+
binding: {
|
|
1081
|
+
toDOM(el, val) {
|
|
1082
|
+
renderer.toDOM(el, val);
|
|
1083
|
+
},
|
|
1084
|
+
},
|
|
1085
|
+
},
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
996
1088
|
return span({
|
|
997
1089
|
class: this.cellClasses('td', si),
|
|
998
1090
|
role: 'gridcell',
|
|
@@ -1287,7 +1379,7 @@ export class TosiTable extends WebComponent {
|
|
|
1287
1379
|
return cells?.[colIndex] ?? null;
|
|
1288
1380
|
}
|
|
1289
1381
|
_pendingFocus = null;
|
|
1290
|
-
|
|
1382
|
+
handleScrollEnd = () => {
|
|
1291
1383
|
if (!this._pendingFocus)
|
|
1292
1384
|
return;
|
|
1293
1385
|
const { row, col } = this._pendingFocus;
|
|
@@ -1659,7 +1751,7 @@ export class TosiTable extends WebComponent {
|
|
|
1659
1751
|
...visibleBinding,
|
|
1660
1752
|
this._tbodyBottom,
|
|
1661
1753
|
].filter(Boolean));
|
|
1662
|
-
this._scrollArea.addEventListener('scrollend', this.
|
|
1754
|
+
this._scrollArea.addEventListener('scrollend', this.handleScrollEnd);
|
|
1663
1755
|
this.append(this._scrollArea);
|
|
1664
1756
|
this.observePinnedRowMutations();
|
|
1665
1757
|
this.tagPinnedRows();
|
package/dist/doc-browser.js
CHANGED
|
@@ -726,7 +726,12 @@ export function createDocBrowser(options) {
|
|
|
726
726
|
const loadSource = async (p) => {
|
|
727
727
|
try {
|
|
728
728
|
const r = await fetch(`/__docstore/source?file=${encodeURIComponent(p)}`);
|
|
729
|
-
|
|
729
|
+
// Accept only a genuine source response. A misconfigured dev server (no
|
|
730
|
+
// editableSources) or a SPA-rewrite host answers an unknown path with
|
|
731
|
+
// index.html at status 200 — taking that would load the PAGE as the source.
|
|
732
|
+
// Reject HTML and fall through to the GitHub raw source instead.
|
|
733
|
+
const contentType = r.headers.get('content-type') || '';
|
|
734
|
+
if (r.ok && !/text\/html/i.test(contentType))
|
|
730
735
|
return await r.text();
|
|
731
736
|
}
|
|
732
737
|
catch {
|
|
@@ -62,6 +62,17 @@ export function docSystemStyleSpec(theme = {}) {
|
|
|
62
62
|
_fontSize: '16px',
|
|
63
63
|
_codeFontSize: '14px',
|
|
64
64
|
...colors,
|
|
65
|
+
// Bridge the tosijs component palette (`--tosi-*`) onto the doc-system's color
|
|
66
|
+
// family. Defined as REFERENCES, so a component reading `var(--tosi-bg)` follows
|
|
67
|
+
// the site theme automatically — including dark mode, where `invertLuminance()`
|
|
68
|
+
// flips the referenced `--background`/`--text-color`/… in `.darkmode`. Without
|
|
69
|
+
// this the doc-system never set `--tosi-*`, so components fell back to their
|
|
70
|
+
// baked-in light defaults (e.g. `var(--tosi-bg, #fff)` → white <tosi-table> in
|
|
71
|
+
// dark mode). Colors only — component metrics keep their own defaults.
|
|
72
|
+
_tosiAccent: vars.brandColor,
|
|
73
|
+
_tosiBg: vars.background,
|
|
74
|
+
_tosiText: vars.textColor,
|
|
75
|
+
_tosiBgInset: vars.insetBg,
|
|
65
76
|
_spacing: '10px',
|
|
66
77
|
_lineHeight: 'calc(var(--font-size) * 1.6)',
|
|
67
78
|
_h1Scale: '2',
|
|
@@ -28,6 +28,17 @@ export declare function resolveIdleMs(configHours: number | undefined, envHours:
|
|
|
28
28
|
* off. Only an explicit non-positive number disables the ceiling.
|
|
29
29
|
*/
|
|
30
30
|
export declare function resolveLimitMb(configMb: number | undefined, envMb: string | undefined): number;
|
|
31
|
+
/**
|
|
32
|
+
* The localhost-gated haltija dev-channel loader injected into served HTML at
|
|
33
|
+
* serve time (never bundled, never in the built output).
|
|
34
|
+
*
|
|
35
|
+
* `self===top` keeps it in the TOP window only. The doc-browser's background test
|
|
36
|
+
* runner loads every page-with-tests in a hidden iframe (`?_testMode=1`), each
|
|
37
|
+
* served this same HTML — without the guard, haltija's `dev.js` gets imported
|
|
38
|
+
* once per test page (N redundant loads in throwaway frames). An agent only ever
|
|
39
|
+
* drives the top page, so nested frames never need the channel.
|
|
40
|
+
*/
|
|
41
|
+
export declare function haltijaLoaderSnippet(httpsPort: number): string;
|
|
31
42
|
export declare function devServer(config: SiteConfig, opts?: {
|
|
32
43
|
test?: boolean;
|
|
33
44
|
build?: () => unknown | Promise<unknown>;
|
|
@@ -78,6 +78,20 @@ export function resolveLimitMb(configMb, envMb) {
|
|
|
78
78
|
const mb = Number.isFinite(raw) ? raw : DEFAULT_LIMIT_MB;
|
|
79
79
|
return mb > 0 ? mb : 0;
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The localhost-gated haltija dev-channel loader injected into served HTML at
|
|
83
|
+
* serve time (never bundled, never in the built output).
|
|
84
|
+
*
|
|
85
|
+
* `self===top` keeps it in the TOP window only. The doc-browser's background test
|
|
86
|
+
* runner loads every page-with-tests in a hidden iframe (`?_testMode=1`), each
|
|
87
|
+
* served this same HTML — without the guard, haltija's `dev.js` gets imported
|
|
88
|
+
* once per test page (N redundant loads in throwaway frames). An agent only ever
|
|
89
|
+
* drives the top page, so nested frames never need the channel.
|
|
90
|
+
*/
|
|
91
|
+
export function haltijaLoaderSnippet(httpsPort) {
|
|
92
|
+
return (`<script>self===top&&/^localhost$|^127\\./.test(location.hostname)` +
|
|
93
|
+
`&&import('https://localhost:${httpsPort}/dev.js')</script>`);
|
|
94
|
+
}
|
|
81
95
|
/**
|
|
82
96
|
* Reclaim the port we are about to bind, from the process LISTENING on it.
|
|
83
97
|
*
|
|
@@ -259,8 +273,7 @@ export async function devServer(config, opts = {}) {
|
|
|
259
273
|
(config.haltijaDev === true ||
|
|
260
274
|
process.env.HALTIJA_DEV === '1' ||
|
|
261
275
|
process.env.HALTIJA_DEV === 'true');
|
|
262
|
-
const HALTIJA_SNIPPET =
|
|
263
|
-
`&&import('https://localhost:${HALTIJA_HTTPS_PORT}/dev.js')</script>`;
|
|
276
|
+
const HALTIJA_SNIPPET = haltijaLoaderSnippet(HALTIJA_HTTPS_PORT);
|
|
264
277
|
let testReportResolve;
|
|
265
278
|
// Source read/write for in-browser "edit page source" (config.editableSources).
|
|
266
279
|
// Local dev only — your machine, your files — so the lone guard is correctness:
|
|
@@ -581,11 +594,21 @@ export async function devServer(config, opts = {}) {
|
|
|
581
594
|
// Source read/write for in-browser "edit page source" (opt-in, dev only).
|
|
582
595
|
// A write lands in the repo file; the chokidar watcher then rebuilds and
|
|
583
596
|
// the page refreshes — the build itself is the preview.
|
|
584
|
-
if (
|
|
597
|
+
if (reqPath === '/__docstore/source') {
|
|
598
|
+
// Handle this endpoint UNCONDITIONALLY so it never falls through to the SPA
|
|
599
|
+
// index.html fallback below. A 200-with-HTML there is silently corrupting:
|
|
600
|
+
// the client loads the PAGE as the "source" (edit-page-source shows HTML;
|
|
601
|
+
// save-to-source reads HTML). When editing isn't enabled, answer with a clean
|
|
602
|
+
// status so `loadSource` falls back to the GitHub raw source (read-only), which
|
|
603
|
+
// is what the deployed static site already does.
|
|
604
|
+
if (!config.editableSources) {
|
|
605
|
+
return new Response('editableSources is not enabled in this doc-site config (set editableSources: true to edit/save source in dev)', { status: 501, headers: { 'Content-Type': 'text/plain; charset=utf-8' } });
|
|
606
|
+
}
|
|
585
607
|
if (request.method === 'GET')
|
|
586
608
|
return handleReadSource(request);
|
|
587
609
|
if (request.method === 'POST')
|
|
588
610
|
return handleWriteSource(request);
|
|
611
|
+
return new Response('method not allowed', { status: 405 });
|
|
589
612
|
}
|
|
590
613
|
if (reqPath === '/')
|
|
591
614
|
reqPath = '/index.html';
|
|
@@ -56,4 +56,22 @@ export interface GenerateSiteConfig {
|
|
|
56
56
|
/** extra lines injected into every <head> (favicon, analytics, etc.) */
|
|
57
57
|
headExtra?: string;
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Depth of a page below the site root: 0 for the root index (served at the mount
|
|
61
|
+
* root), 1 for every `/slug/` page. The generator only ever emits a flat `/slug/`
|
|
62
|
+
* tree (see `pathForSlug`), so a non-root page is always exactly one directory deep.
|
|
63
|
+
*/
|
|
64
|
+
export declare function pageDepth(slug: string): number;
|
|
65
|
+
/**
|
|
66
|
+
* Rewrite a root-relative *functional* URL (asset ref, nav / content link) to be
|
|
67
|
+
* relative to a page at `depth`, so ONE build works at ANY mount point — a GitHub
|
|
68
|
+
* project page under `/repo`, a custom-domain root, or a moved mount — with no
|
|
69
|
+
* `basePath` rebuild. Relative URLs resolve against wherever the page is actually
|
|
70
|
+
* served, so `basePath` is deliberately NOT applied here: a page at `/repo/x/`
|
|
71
|
+
* gets `../styles.css` → `/repo/styles.css`, the same page at `/x/` gets it at
|
|
72
|
+
* `/styles.css`. External (`https://…`, `//…`) and already-relative refs pass
|
|
73
|
+
* through untouched. (Metadata URLs still use `withBase` — they need the absolute
|
|
74
|
+
* origin.) See issue #25; the runtime/SPA-navigation half is issue #16.
|
|
75
|
+
*/
|
|
76
|
+
export declare function relativeUrl(depth: number, p: string): string;
|
|
59
77
|
export declare function generateSite(config: GenerateSiteConfig): Promise<number>;
|
|
@@ -27,11 +27,11 @@ const escapeText = (s) => s.replace(/&/g, '&').replace(/</g, '<').replace
|
|
|
27
27
|
* <details> on the path to `currentFilename` opened. `currentFilename` gets
|
|
28
28
|
* aria-current. Crawlable + no-JS: every node is a real <a>.
|
|
29
29
|
*/
|
|
30
|
-
function navHtml(docs, slugMap, currentFilename,
|
|
30
|
+
function navHtml(docs, slugMap, currentFilename, depth) {
|
|
31
31
|
const roots = buildNavTree(docs, slugMap);
|
|
32
32
|
const open = navOpenPath(roots, currentFilename);
|
|
33
33
|
const renderNode = (node, indent) => {
|
|
34
|
-
const href =
|
|
34
|
+
const href = relativeUrl(depth, pathForSlug(node.slug));
|
|
35
35
|
const current = node.doc.filename === currentFilename;
|
|
36
36
|
// `doc-link` is what the HYDRATED nav emits, and the shared nav CSS is written
|
|
37
37
|
// against it. Emitting a bare <a> here forced a second, hand-copied rule set under
|
|
@@ -78,16 +78,48 @@ function absUrl(baseUrl, pathOrUrl) {
|
|
|
78
78
|
/**
|
|
79
79
|
* Prefix a root-relative path with basePath. No-op for '/', empty, protocol-
|
|
80
80
|
* relative (`//…`), or absolute (`https://…`) URLs.
|
|
81
|
+
*
|
|
82
|
+
* Used ONLY for *metadata* URLs now (canonical, og:url, og:image, sitemap) — they
|
|
83
|
+
* legitimately need the real absolute origin+path. *Functional* URLs go through
|
|
84
|
+
* `relativeUrl` instead, so the artifact is mount-agnostic. See `relativeUrl`.
|
|
81
85
|
*/
|
|
82
86
|
function withBase(basePath, p) {
|
|
83
87
|
if (!p || !basePath || basePath === '/' || /^(https?:)?\/\//.test(p))
|
|
84
88
|
return p;
|
|
85
89
|
return basePath.replace(/\/$/, '') + (p.startsWith('/') ? p : '/' + p);
|
|
86
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Depth of a page below the site root: 0 for the root index (served at the mount
|
|
93
|
+
* root), 1 for every `/slug/` page. The generator only ever emits a flat `/slug/`
|
|
94
|
+
* tree (see `pathForSlug`), so a non-root page is always exactly one directory deep.
|
|
95
|
+
*/
|
|
96
|
+
export function pageDepth(slug) {
|
|
97
|
+
return slug === '' ? 0 : 1;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Rewrite a root-relative *functional* URL (asset ref, nav / content link) to be
|
|
101
|
+
* relative to a page at `depth`, so ONE build works at ANY mount point — a GitHub
|
|
102
|
+
* project page under `/repo`, a custom-domain root, or a moved mount — with no
|
|
103
|
+
* `basePath` rebuild. Relative URLs resolve against wherever the page is actually
|
|
104
|
+
* served, so `basePath` is deliberately NOT applied here: a page at `/repo/x/`
|
|
105
|
+
* gets `../styles.css` → `/repo/styles.css`, the same page at `/x/` gets it at
|
|
106
|
+
* `/styles.css`. External (`https://…`, `//…`) and already-relative refs pass
|
|
107
|
+
* through untouched. (Metadata URLs still use `withBase` — they need the absolute
|
|
108
|
+
* origin.) See issue #25; the runtime/SPA-navigation half is issue #16.
|
|
109
|
+
*/
|
|
110
|
+
export function relativeUrl(depth, p) {
|
|
111
|
+
if (!p || /^(https?:)?\/\//.test(p) || !p.startsWith('/'))
|
|
112
|
+
return p;
|
|
113
|
+
const rel = '../'.repeat(depth) + p.slice(1);
|
|
114
|
+
return rel === '' ? './' : rel;
|
|
115
|
+
}
|
|
87
116
|
function pageHtml(doc, config, slugMap, configAttr) {
|
|
88
117
|
const { projectName = '', baseUrl = '', lang = 'en', favicon = '/favicon.svg', docsUrl = '/docs.json', scriptUrl = '/iife.js', hydrateUrl, stylesUrl = '/doc-system.css', localizedUrl = '/localized-strings.txt', basePath, headExtra = '', bakes, } = config;
|
|
118
|
+
// Functional URLs are emitted relative to THIS page's depth so the build is
|
|
119
|
+
// mount-agnostic (issue #25); metadata URLs below stay absolute via withBase.
|
|
120
|
+
const depth = pageDepth(slugMap[doc.filename] ?? '');
|
|
89
121
|
const localizedAttr = config.localizedStrings
|
|
90
|
-
? ` localized="${escapeAttr(
|
|
122
|
+
? ` localized="${escapeAttr(relativeUrl(depth, localizedUrl))}"`
|
|
91
123
|
: '';
|
|
92
124
|
// ONE rule, shared with the doc-browser's hydration path — see doc-title.ts. Writing
|
|
93
125
|
// it twice is what made the title change on hydration (issue #6).
|
|
@@ -103,9 +135,9 @@ function pageHtml(doc, config, slugMap, configAttr) {
|
|
|
103
135
|
// static HTML is correct for no-JS readers and crawlers (the doc-browser also
|
|
104
136
|
// does this client-side after hydration).
|
|
105
137
|
const body = rewriteDocLinks(renderDocMarkdown(doc.text, { bakes: bakes?.get(doc.filename) }), (filename) => slugMap[filename] !== undefined
|
|
106
|
-
?
|
|
138
|
+
? relativeUrl(depth, pathForSlug(slugMap[filename]))
|
|
107
139
|
: null);
|
|
108
|
-
const nav = navHtml(config.docs, slugMap, doc.filename,
|
|
140
|
+
const nav = navHtml(config.docs, slugMap, doc.filename, depth);
|
|
109
141
|
const navbar = linkListHtml('doc-navbar', config.navbarLinks);
|
|
110
142
|
const jsonLd = baseUrl
|
|
111
143
|
? jsonLdScript({
|
|
@@ -126,7 +158,7 @@ function pageHtml(doc, config, slugMap, configAttr) {
|
|
|
126
158
|
' <link rel="preconnect" href="https://fonts.googleapis.com" />',
|
|
127
159
|
' <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />',
|
|
128
160
|
// Burned-in theme: styles the page with no JS and with zero flash on hydration.
|
|
129
|
-
` <link rel="stylesheet" href="${escapeAttr(
|
|
161
|
+
` <link rel="stylesheet" href="${escapeAttr(relativeUrl(depth, stylesUrl))}" data-tosi-doc-system />`,
|
|
130
162
|
` <title>${escapeText(title)}</title>`,
|
|
131
163
|
description
|
|
132
164
|
? ` <meta name="description" content="${escapeAttr(description)}" />`
|
|
@@ -155,7 +187,7 @@ function pageHtml(doc, config, slugMap, configAttr) {
|
|
|
155
187
|
? ` <meta name="twitter:image" content="${escapeAttr(imageAbs)}" />`
|
|
156
188
|
: '',
|
|
157
189
|
jsonLd,
|
|
158
|
-
` <link rel="icon" href="${escapeAttr(
|
|
190
|
+
` <link rel="icon" href="${escapeAttr(relativeUrl(depth, favicon))}" />`,
|
|
159
191
|
headExtra,
|
|
160
192
|
]
|
|
161
193
|
.filter(Boolean)
|
|
@@ -189,7 +221,7 @@ function pageHtml(doc, config, slugMap, configAttr) {
|
|
|
189
221
|
${head}
|
|
190
222
|
</head>
|
|
191
223
|
<body>
|
|
192
|
-
<tosi-doc-system docs="${escapeAttr(
|
|
224
|
+
<tosi-doc-system docs="${escapeAttr(relativeUrl(depth, docsUrl))}" config="${configAttr}"${localizedAttr}>
|
|
193
225
|
<article class="doc-content">
|
|
194
226
|
${body}
|
|
195
227
|
</article>
|
|
@@ -197,8 +229,8 @@ ${nav}
|
|
|
197
229
|
${navbar}
|
|
198
230
|
</tosi-doc-system>
|
|
199
231
|
${hydrateUrl
|
|
200
|
-
? `<script type="module" src="${escapeAttr(
|
|
201
|
-
: `<script src="${escapeAttr(
|
|
232
|
+
? `<script type="module" src="${escapeAttr(relativeUrl(depth, hydrateUrl))}"></script>`
|
|
233
|
+
: `<script src="${escapeAttr(relativeUrl(depth, scriptUrl))}"></script>`}
|
|
202
234
|
</body>
|
|
203
235
|
</html>
|
|
204
236
|
`;
|
|
@@ -88,9 +88,16 @@ export interface SiteConfig {
|
|
|
88
88
|
domain?: string;
|
|
89
89
|
/**
|
|
90
90
|
* URL prefix the site is served under, default '/'. Set to '/<repo>' for a
|
|
91
|
-
* GitHub project page without a custom domain
|
|
92
|
-
*
|
|
93
|
-
*
|
|
91
|
+
* GitHub project page without a custom domain.
|
|
92
|
+
*
|
|
93
|
+
* As of the mount-agnostic build (issue #25), `basePath` only affects
|
|
94
|
+
* *metadata* URLs — `canonical`, `og:url`, `og:image`, and `sitemap.xml` —
|
|
95
|
+
* which need the real absolute served path for SEO. *Functional* URLs (nav /
|
|
96
|
+
* content links, `scriptUrl`, `stylesUrl`, favicon, `docsUrl`) are emitted
|
|
97
|
+
* **relative to each page**, so one build works at `/repo`, at a custom-domain
|
|
98
|
+
* root, or a moved mount with no rebuild. You still want `basePath` correct so
|
|
99
|
+
* search engines see canonical URLs at the real path; getting it wrong now only
|
|
100
|
+
* mis-states metadata, it no longer 404s the page's assets.
|
|
94
101
|
*/
|
|
95
102
|
basePath?: string;
|
|
96
103
|
/**
|