duckfn-docs-kit 0.1.0
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/AGENTS.md +689 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/dom.d.ts +69 -0
- package/dist/home/DfkFeatures.d.ts +20 -0
- package/dist/home/DfkHero.d.ts +25 -0
- package/dist/home/DfkNextSteps.d.ts +16 -0
- package/dist/home/styles.d.ts +8 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +2 -0
- package/dist/register-DKLiYs-F.js +2324 -0
- package/dist/register.d.ts +10 -0
- package/dist/remark.d.ts +21 -0
- package/dist/remark.js +15 -0
- package/dist/runtimeConfig-Bokbb8VH.js +106 -0
- package/dist/sql/DfkSql.d.ts +7 -0
- package/dist/sql/PreviewTabs.d.ts +37 -0
- package/dist/sql/client.d.ts +1 -0
- package/dist/sql/client.js +4 -0
- package/dist/sql/editor.d.ts +16 -0
- package/dist/sql/extensions.d.ts +108 -0
- package/dist/sql/extensions.js +198 -0
- package/dist/sql/remark.d.ts +88 -0
- package/dist/sql/remark.js +69 -0
- package/dist/sql/renderers.d.ts +44 -0
- package/dist/sql/runtime.d.ts +105 -0
- package/dist/sql/runtimeConfig.d.ts +80 -0
- package/dist/sql/styles.d.ts +6 -0
- package/dist/toc-toggle/TocToggle.d.ts +46 -0
- package/dist/toc-toggle/TocToggle.js +69 -0
- package/dist/toc-toggle/client.d.ts +1 -0
- package/dist/toc-toggle/client.js +9 -0
- package/dist/toc-toggle/plugin.d.ts +36 -0
- package/dist/toc-toggle/plugin.js +13 -0
- package/dist/types.d.ts +42 -0
- package/package.json +73 -0
- package/src/dom.ts +109 -0
- package/src/home/DfkFeatures.ts +78 -0
- package/src/home/DfkHero.ts +128 -0
- package/src/home/DfkNextSteps.ts +73 -0
- package/src/home/home.css +520 -0
- package/src/home/styles.ts +28 -0
- package/src/index.ts +59 -0
- package/src/kit.css +19 -0
- package/src/register.ts +39 -0
- package/src/remark.ts +60 -0
- package/src/sql/DfkSql.css +226 -0
- package/src/sql/DfkSql.ts +620 -0
- package/src/sql/PreviewTabs.ts +169 -0
- package/src/sql/client.ts +16 -0
- package/src/sql/editor.ts +75 -0
- package/src/sql/extensions.ts +470 -0
- package/src/sql/remark.ts +213 -0
- package/src/sql/renderers.ts +916 -0
- package/src/sql/runtime.ts +348 -0
- package/src/sql/runtimeConfig.ts +249 -0
- package/src/sql/sql.css +397 -0
- package/src/sql/styles.ts +24 -0
- package/src/theme/tokens.css +75 -0
- package/src/toc-toggle/TocToggle.css +69 -0
- package/src/toc-toggle/TocToggle.ts +172 -0
- package/src/toc-toggle/client.ts +20 -0
- package/src/toc-toggle/plugin.ts +54 -0
- package/src/types.ts +47 -0
- package/src/vite-env.d.ts +8 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every renderer's result shell: a tab strip plus the panels behind it.
|
|
3
|
+
*
|
|
4
|
+
* The strip is one tab per preview row plus a `Table` tab that always comes
|
|
5
|
+
* **last**; a renderer with a single view passes no items at all, so a plain
|
|
6
|
+
* table result is a strip holding nothing but that trailing `Table` tab. Every
|
|
7
|
+
* result therefore has the same chrome — which is where the fullscreen toggle
|
|
8
|
+
* lives.
|
|
9
|
+
*
|
|
10
|
+
* Retained mode: every button and panel is built in the constructor and held in
|
|
11
|
+
* a field. Activating a tab mutates the nodes it owns (`hidden`, `classList`,
|
|
12
|
+
* `aria-selected`, `tabIndex`) — there is no rebuild, and a panel is filled the
|
|
13
|
+
* first time it is shown rather than up front.
|
|
14
|
+
*
|
|
15
|
+
* The table panel is mounted on first activation on purpose: VTable measures
|
|
16
|
+
* its container when it is constructed, and a `hidden` panel measures to zero.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {el} from '../dom';
|
|
20
|
+
|
|
21
|
+
/** One preview row: its tab label and how to fill its panel. */
|
|
22
|
+
export interface PreviewTabItem {
|
|
23
|
+
label: string;
|
|
24
|
+
mount(panel: HTMLElement): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** What `PreviewTabs` needs from the caller to own the trailing table tab. */
|
|
28
|
+
export interface PreviewTableHandle {
|
|
29
|
+
dispose(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Keeps per-instance element ids unique across every `<dfk-sql>` on a page. */
|
|
33
|
+
let sequence = 0;
|
|
34
|
+
|
|
35
|
+
export class PreviewTabs {
|
|
36
|
+
readonly #buttons: HTMLButtonElement[] = [];
|
|
37
|
+
readonly #panels: HTMLElement[] = [];
|
|
38
|
+
/** `null` marks the trailing table tab. */
|
|
39
|
+
readonly #items: (PreviewTabItem | null)[] = [];
|
|
40
|
+
readonly #mounted: boolean[] = [];
|
|
41
|
+
readonly #mountTable: (panel: HTMLElement) => Promise<PreviewTableHandle>;
|
|
42
|
+
|
|
43
|
+
#tableHandle: PreviewTableHandle | null = null;
|
|
44
|
+
#disposed = false;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param corner Node parked at the right end of the strip, outside the
|
|
48
|
+
* scrolling tab list. `<dfk-sql>` passes its fullscreen toggle: it owns that
|
|
49
|
+
* button's state, so it owns the node and only lends it here.
|
|
50
|
+
*/
|
|
51
|
+
constructor(
|
|
52
|
+
host: HTMLElement,
|
|
53
|
+
items: readonly PreviewTabItem[],
|
|
54
|
+
tableLabel: string,
|
|
55
|
+
mountTable: (panel: HTMLElement) => Promise<PreviewTableHandle>,
|
|
56
|
+
corner?: HTMLElement,
|
|
57
|
+
) {
|
|
58
|
+
this.#mountTable = mountTable;
|
|
59
|
+
const uid = `dfk-sql-tabs-${(sequence += 1)}`;
|
|
60
|
+
const bar = el('div', {class: 'dfk-sql-tabs'});
|
|
61
|
+
// Only the tab buttons belong to the tablist; the corner button must not be
|
|
62
|
+
// scrollable with them, hence the nested list.
|
|
63
|
+
const list = el('div', {
|
|
64
|
+
class: 'dfk-sql-tab-list',
|
|
65
|
+
attrs: {role: 'tablist'},
|
|
66
|
+
});
|
|
67
|
+
const panels = el('div', {class: 'dfk-sql-panels'});
|
|
68
|
+
|
|
69
|
+
const add = (label: string, item: PreviewTabItem | null): void => {
|
|
70
|
+
const index = this.#buttons.length;
|
|
71
|
+
const panel = el('div', {
|
|
72
|
+
class: 'dfk-sql-panel',
|
|
73
|
+
hidden: true,
|
|
74
|
+
attrs: {
|
|
75
|
+
role: 'tabpanel',
|
|
76
|
+
id: `${uid}-panel-${index}`,
|
|
77
|
+
'aria-labelledby': `${uid}-tab-${index}`,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const button = el('button', {
|
|
81
|
+
class: 'dfk-sql-tab',
|
|
82
|
+
type: 'button',
|
|
83
|
+
text: label,
|
|
84
|
+
tabIndex: index === 0 ? 0 : -1,
|
|
85
|
+
attrs: {
|
|
86
|
+
role: 'tab',
|
|
87
|
+
id: `${uid}-tab-${index}`,
|
|
88
|
+
'aria-controls': `${uid}-panel-${index}`,
|
|
89
|
+
'aria-selected': 'false',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
button.addEventListener('click', () => this.#select(index));
|
|
93
|
+
button.addEventListener('keydown', (event) => this.#onKeydown(event, index));
|
|
94
|
+
|
|
95
|
+
this.#buttons.push(button);
|
|
96
|
+
this.#panels.push(panel);
|
|
97
|
+
this.#items.push(item);
|
|
98
|
+
this.#mounted.push(false);
|
|
99
|
+
list.appendChild(button);
|
|
100
|
+
panels.appendChild(panel);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
for (const item of items) {
|
|
104
|
+
add(item.label, item);
|
|
105
|
+
}
|
|
106
|
+
add(tableLabel, null);
|
|
107
|
+
|
|
108
|
+
bar.appendChild(list);
|
|
109
|
+
if (corner) {
|
|
110
|
+
bar.appendChild(corner);
|
|
111
|
+
}
|
|
112
|
+
// The one-time installation of this widget's own subtree.
|
|
113
|
+
host.replaceChildren(bar, panels);
|
|
114
|
+
this.#select(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Releases the table (if it was ever shown) and empties every panel. */
|
|
118
|
+
dispose(): void {
|
|
119
|
+
this.#disposed = true;
|
|
120
|
+
this.#tableHandle?.dispose();
|
|
121
|
+
this.#tableHandle = null;
|
|
122
|
+
for (const panel of this.#panels) {
|
|
123
|
+
panel.replaceChildren();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Activation is pure mutation — no panel is rebuilt, none is discarded. */
|
|
128
|
+
#select(index: number): void {
|
|
129
|
+
for (let i = 0; i < this.#buttons.length; i += 1) {
|
|
130
|
+
const active = i === index;
|
|
131
|
+
const button = this.#buttons[i];
|
|
132
|
+
button.classList.toggle('dfk-sql-tab-active', active);
|
|
133
|
+
button.setAttribute('aria-selected', String(active));
|
|
134
|
+
button.tabIndex = active ? 0 : -1;
|
|
135
|
+
this.#panels[i].hidden = !active;
|
|
136
|
+
}
|
|
137
|
+
if (this.#mounted[index]) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
this.#mounted[index] = true;
|
|
141
|
+
const item = this.#items[index];
|
|
142
|
+
if (item) {
|
|
143
|
+
item.mount(this.#panels[index]);
|
|
144
|
+
} else {
|
|
145
|
+
void this.#mountTableInto(index);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async #mountTableInto(index: number): Promise<void> {
|
|
150
|
+
const handle = await this.#mountTable(this.#panels[index]);
|
|
151
|
+
if (this.#disposed) {
|
|
152
|
+
// Disposed while the mount was in flight: release what just arrived.
|
|
153
|
+
handle.dispose();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.#tableHandle = handle;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#onKeydown(event: KeyboardEvent, index: number): void {
|
|
160
|
+
const offset = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0;
|
|
161
|
+
if (offset === 0) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
const next = (index + offset + this.#buttons.length) % this.#buttons.length;
|
|
166
|
+
this.#select(next);
|
|
167
|
+
this.#buttons[next].focus();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kit's client bootstrap for pages that use the `dfk-*` elements,
|
|
3
|
+
* injected by the `dfkExtensions` Docusaurus plugin on every page (through
|
|
4
|
+
* `getClientModules()`) so a consuming site does not need a client-module file
|
|
5
|
+
* of its own just to register the custom elements. The home page keeps its own
|
|
6
|
+
* module-scope `registerDfkElements()` call (it imports the barrel anyway);
|
|
7
|
+
* registering twice is a no-op.
|
|
8
|
+
*
|
|
9
|
+
* Docusaurus evaluates this module in its Node prerender pass too, where there
|
|
10
|
+
* is nothing to register — hence the guard.
|
|
11
|
+
*/
|
|
12
|
+
import {registerDfkElements} from '../register';
|
|
13
|
+
|
|
14
|
+
if (typeof window !== 'undefined') {
|
|
15
|
+
registerDfkElements();
|
|
16
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CodeMirror 6 editor that *is* the code view of a runnable SQL block.
|
|
3
|
+
*
|
|
4
|
+
* Every CodeMirror module arrives through dynamic `import()` inside
|
|
5
|
+
* {@link mountSqlEditor}: a page full of SQL examples pays nothing for the
|
|
6
|
+
* editor on its critical path, and Docusaurus' Node prerender never evaluates
|
|
7
|
+
* any of it.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface SqlEditor {
|
|
11
|
+
getValue(): string;
|
|
12
|
+
setValue(value: string): void;
|
|
13
|
+
/** Soft-wraps long lines, or stops wrapping them (the block's wrap toggle). */
|
|
14
|
+
setWrap(wrapped: boolean): void;
|
|
15
|
+
destroy(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function mountSqlEditor(
|
|
19
|
+
container: HTMLElement,
|
|
20
|
+
value: string,
|
|
21
|
+
onChange: (value: string) => void,
|
|
22
|
+
): Promise<SqlEditor> {
|
|
23
|
+
const [
|
|
24
|
+
{basicSetup},
|
|
25
|
+
{sql},
|
|
26
|
+
{EditorView, keymap},
|
|
27
|
+
{defaultKeymap, historyKeymap},
|
|
28
|
+
{Compartment},
|
|
29
|
+
] = await Promise.all([
|
|
30
|
+
import('codemirror'),
|
|
31
|
+
import('@codemirror/lang-sql'),
|
|
32
|
+
import('@codemirror/view'),
|
|
33
|
+
import('@codemirror/commands'),
|
|
34
|
+
import('@codemirror/state'),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// Wrapping is toggled from the outside, and reconfiguring it must not disturb
|
|
38
|
+
// the document or the undo history — that is exactly what a compartment is
|
|
39
|
+
// for, so the extension is swapped in place rather than rebuilt.
|
|
40
|
+
const wrap = new Compartment();
|
|
41
|
+
|
|
42
|
+
const view = new EditorView({
|
|
43
|
+
doc: value,
|
|
44
|
+
extensions: [
|
|
45
|
+
basicSetup,
|
|
46
|
+
sql(),
|
|
47
|
+
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
48
|
+
wrap.of([]),
|
|
49
|
+
EditorView.updateListener.of((update) => {
|
|
50
|
+
if (update.docChanged) {
|
|
51
|
+
onChange(update.state.doc.toString());
|
|
52
|
+
}
|
|
53
|
+
}),
|
|
54
|
+
],
|
|
55
|
+
parent: container,
|
|
56
|
+
// `root` is left to CodeMirror's own `getRoot(container)`. The container sits
|
|
57
|
+
// in `<dfk-sql>`'s shadow root, so style-mod mounts the base theme into that
|
|
58
|
+
// same shadow root — exactly where the `.cm-*` rules are needed. Pinning it
|
|
59
|
+
// to `document` would put them outside the editor's tree instead, where a
|
|
60
|
+
// shadow boundary stops them.
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
getValue: () => view.state.doc.toString(),
|
|
65
|
+
setValue: (next: string) =>
|
|
66
|
+
view.dispatch({
|
|
67
|
+
changes: {from: 0, to: view.state.doc.length, insert: next},
|
|
68
|
+
}),
|
|
69
|
+
setWrap: (wrapped: boolean) =>
|
|
70
|
+
view.dispatch({
|
|
71
|
+
effects: wrap.reconfigure(wrapped ? EditorView.lineWrapping : []),
|
|
72
|
+
}),
|
|
73
|
+
destroy: () => view.destroy(),
|
|
74
|
+
};
|
|
75
|
+
}
|