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.
Files changed (65) hide show
  1. package/AGENTS.md +689 -0
  2. package/LICENSE +21 -0
  3. package/README.md +107 -0
  4. package/dist/dom.d.ts +69 -0
  5. package/dist/home/DfkFeatures.d.ts +20 -0
  6. package/dist/home/DfkHero.d.ts +25 -0
  7. package/dist/home/DfkNextSteps.d.ts +16 -0
  8. package/dist/home/styles.d.ts +8 -0
  9. package/dist/index.d.ts +50 -0
  10. package/dist/index.js +2 -0
  11. package/dist/register-DKLiYs-F.js +2324 -0
  12. package/dist/register.d.ts +10 -0
  13. package/dist/remark.d.ts +21 -0
  14. package/dist/remark.js +15 -0
  15. package/dist/runtimeConfig-Bokbb8VH.js +106 -0
  16. package/dist/sql/DfkSql.d.ts +7 -0
  17. package/dist/sql/PreviewTabs.d.ts +37 -0
  18. package/dist/sql/client.d.ts +1 -0
  19. package/dist/sql/client.js +4 -0
  20. package/dist/sql/editor.d.ts +16 -0
  21. package/dist/sql/extensions.d.ts +108 -0
  22. package/dist/sql/extensions.js +198 -0
  23. package/dist/sql/remark.d.ts +88 -0
  24. package/dist/sql/remark.js +69 -0
  25. package/dist/sql/renderers.d.ts +44 -0
  26. package/dist/sql/runtime.d.ts +105 -0
  27. package/dist/sql/runtimeConfig.d.ts +80 -0
  28. package/dist/sql/styles.d.ts +6 -0
  29. package/dist/toc-toggle/TocToggle.d.ts +46 -0
  30. package/dist/toc-toggle/TocToggle.js +69 -0
  31. package/dist/toc-toggle/client.d.ts +1 -0
  32. package/dist/toc-toggle/client.js +9 -0
  33. package/dist/toc-toggle/plugin.d.ts +36 -0
  34. package/dist/toc-toggle/plugin.js +13 -0
  35. package/dist/types.d.ts +42 -0
  36. package/package.json +73 -0
  37. package/src/dom.ts +109 -0
  38. package/src/home/DfkFeatures.ts +78 -0
  39. package/src/home/DfkHero.ts +128 -0
  40. package/src/home/DfkNextSteps.ts +73 -0
  41. package/src/home/home.css +520 -0
  42. package/src/home/styles.ts +28 -0
  43. package/src/index.ts +59 -0
  44. package/src/kit.css +19 -0
  45. package/src/register.ts +39 -0
  46. package/src/remark.ts +60 -0
  47. package/src/sql/DfkSql.css +226 -0
  48. package/src/sql/DfkSql.ts +620 -0
  49. package/src/sql/PreviewTabs.ts +169 -0
  50. package/src/sql/client.ts +16 -0
  51. package/src/sql/editor.ts +75 -0
  52. package/src/sql/extensions.ts +470 -0
  53. package/src/sql/remark.ts +213 -0
  54. package/src/sql/renderers.ts +916 -0
  55. package/src/sql/runtime.ts +348 -0
  56. package/src/sql/runtimeConfig.ts +249 -0
  57. package/src/sql/sql.css +397 -0
  58. package/src/sql/styles.ts +24 -0
  59. package/src/theme/tokens.css +75 -0
  60. package/src/toc-toggle/TocToggle.css +69 -0
  61. package/src/toc-toggle/TocToggle.ts +172 -0
  62. package/src/toc-toggle/client.ts +20 -0
  63. package/src/toc-toggle/plugin.ts +54 -0
  64. package/src/types.ts +47 -0
  65. package/src/vite-env.d.ts +8 -0
package/src/dom.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * DOM helpers shared by the custom elements.
3
+ *
4
+ * `HTMLElementBase` is the whole reason this module exists: the element classes
5
+ * `extend` it, but Docusaurus imports this package into Node during static
6
+ * prerendering, where the global `HTMLElement` does not exist and evaluating
7
+ * `class X extends HTMLElement` would throw. Falling back to an empty base
8
+ * class keeps module evaluation safe on the server; the real `HTMLElement` is
9
+ * picked up in the browser, where the elements are actually defined and run.
10
+ */
11
+
12
+ export const HTMLElementBase: typeof HTMLElement =
13
+ typeof HTMLElement !== 'undefined'
14
+ ? HTMLElement
15
+ : (class {} as unknown as typeof HTMLElement);
16
+
17
+ /**
18
+ * The tag's props that hold a value `el()` can assign as-is — `href`, `src`,
19
+ * `alt`, `width`, `hidden`, and so on.
20
+ *
21
+ * Methods and object-valued props are filtered out, so an options bag can never
22
+ * carry `appendChild`, `style`, `dataset` or `classList`. Getter-only props
23
+ * (`origin`, `clientWidth`) do pass the filter; assigning one throws in strict
24
+ * mode, which is loud enough to be caught the first time it runs.
25
+ */
26
+ type ValueProps<T> = {
27
+ [P in keyof T as T[P] extends Function
28
+ ? never
29
+ : T[P] extends object
30
+ ? never
31
+ : P]?: T[P];
32
+ };
33
+
34
+ /**
35
+ * Options for `el()`: the tag's own props, narrowed to that tag, plus two
36
+ * shorthands and the attribute escape hatch.
37
+ */
38
+ export type ElOptions<T extends Element> = ValueProps<T> & {
39
+ /** Shorthand for `className`. */
40
+ class?: string;
41
+ /** Shorthand for `textContent`. */
42
+ text?: string;
43
+ /**
44
+ * Attributes that have no matching prop: `aria-*`, `data-*`, and
45
+ * custom-element attributes. Everything else goes through a property, so a
46
+ * boolean or a number keeps its real type instead of being stringified.
47
+ */
48
+ attrs?: Record<string, string>;
49
+ };
50
+
51
+ /**
52
+ * `document.createElement` with per-tag typed options and an optional `init`
53
+ * callback.
54
+ *
55
+ * The one sanctioned way to build nodes in this package: it returns a live
56
+ * element (held in a class field by the caller), never an HTML string.
57
+ *
58
+ * The options are narrowed to the tag, so a typo, a wrong value type, a method
59
+ * name or an object-valued prop is a compile error:
60
+ *
61
+ * ```ts
62
+ * el('img', {class: 'dfk-logo', alt: '', width: 480});
63
+ * ```
64
+ *
65
+ * `init` describes a subtree in place, for structure that is never referenced
66
+ * again and therefore needs no field:
67
+ *
68
+ * ```ts
69
+ * this.root.append(
70
+ * el('span', {class: 'dfk-next-card-body'}, (body) => body.append(this.#title, this.#details)),
71
+ * this.#arrow,
72
+ * );
73
+ * ```
74
+ *
75
+ * Nodes touched later stay in fields. A field initializer must not read a
76
+ * `#field` declared below it (initializers run in declaration order, so that is
77
+ * a TDZ error) — pass `init` from the constructor, where every field is ready.
78
+ */
79
+ export function el<K extends keyof HTMLElementTagNameMap>(
80
+ tag: K,
81
+ // A function is the `init` callback, so options can be skipped:
82
+ // `el('span', (span) => (span.textContent = label))`.
83
+ options?:
84
+ | ElOptions<HTMLElementTagNameMap[K]>
85
+ | ((node: HTMLElementTagNameMap[K]) => void),
86
+ init?: (node: HTMLElementTagNameMap[K]) => void,
87
+ ): HTMLElementTagNameMap[K] {
88
+ const node = document.createElement(tag);
89
+ const build = typeof options === 'function' ? options : init;
90
+ if (typeof options !== 'function' && options) {
91
+ const {class: className, text, attrs, ...props} = options;
92
+ if (className !== undefined) {
93
+ node.className = className;
94
+ }
95
+ if (text !== undefined) {
96
+ node.textContent = text;
97
+ }
98
+ for (const [name, value] of Object.entries(props)) {
99
+ if (value !== undefined) {
100
+ (node as unknown as Record<string, unknown>)[name] = value;
101
+ }
102
+ }
103
+ for (const [name, value] of Object.entries(attrs ?? {})) {
104
+ node.setAttribute(name, value);
105
+ }
106
+ }
107
+ build?.(node);
108
+ return node;
109
+ }
@@ -0,0 +1,78 @@
1
+ import type {IconifyIconHTMLElement} from 'iconify-icon';
2
+ import {el, HTMLElementBase} from '../dom';
3
+ import {homeStyles} from './styles';
4
+ import type {FeatureItem} from '../types';
5
+
6
+ /**
7
+ * `<dfk-features>` — the "Why duckfn" grid of feature cards. Ported from the
8
+ * home page's `Features()`.
9
+ *
10
+ * Retained-mode: the grid is built once and each card (a {@link DfkFeatureCard})
11
+ * holds its own nodes. `setFeatures()` grows or shrinks the list to the new
12
+ * length and mutates the cards in place — the grid is never cleared and rebuilt.
13
+ *
14
+ * The tree lives in a shadow root (adopting the shared `homeStyles()` sheet);
15
+ * see `DfkHero` for why the theme crosses the boundary through custom
16
+ * properties.
17
+ */
18
+ export class DfkFeatures extends HTMLElementBase {
19
+ readonly #section = el('section', {class: 'dfk-section'});
20
+ readonly #inner = el('div', {class: 'dfk-section-inner'});
21
+ readonly #heading = el('h2', {class: 'dfk-section-title'});
22
+ readonly #grid = el('div', {class: 'dfk-feature-grid'});
23
+ readonly #cards: DfkFeatureCard[] = [];
24
+
25
+ constructor() {
26
+ super();
27
+ this.#inner.append(this.#heading, this.#grid);
28
+ this.#section.appendChild(this.#inner);
29
+ const shadow = this.attachShadow({mode: 'open'});
30
+ shadow.adoptedStyleSheets = [homeStyles()];
31
+ shadow.appendChild(this.#section);
32
+ }
33
+
34
+ setSectionTitle(text: string): void {
35
+ this.#heading.textContent = text;
36
+ }
37
+
38
+ setFeatures(items: readonly FeatureItem[]): void {
39
+ while (this.#cards.length > items.length) {
40
+ this.#cards.pop()?.root.remove();
41
+ }
42
+ while (this.#cards.length < items.length) {
43
+ const card = new DfkFeatureCard();
44
+ this.#cards.push(card);
45
+ this.#grid.appendChild(card.root);
46
+ }
47
+ items.forEach((item, index) => this.#cards[index].setFeature(item));
48
+ }
49
+ }
50
+
51
+ /** One feature card: an icon chip, a title and a description. Built once. */
52
+ class DfkFeatureCard {
53
+ readonly root = el('article', {class: 'dfk-feature-card'});
54
+ readonly #icon: IconifyIconHTMLElement = el('iconify-icon', {
55
+ class: 'dfk-feature-icon',
56
+ attrs: {'aria-hidden': 'true'},
57
+ });
58
+ readonly #title = el('h3', {class: 'dfk-feature-title'});
59
+ readonly #details = el('p', {class: 'dfk-feature-details'});
60
+
61
+ constructor() {
62
+ // The chip only wraps the icon and is never touched again, so it is
63
+ // described in place instead of being held in a field.
64
+ this.root.append(
65
+ el('span', {class: 'dfk-feature-icon-chip'}, (chip) =>
66
+ chip.appendChild(this.#icon),
67
+ ),
68
+ this.#title,
69
+ this.#details,
70
+ );
71
+ }
72
+
73
+ setFeature(item: FeatureItem): void {
74
+ this.#icon.setAttribute('icon', item.icon);
75
+ this.#title.textContent = item.title;
76
+ this.#details.textContent = item.details;
77
+ }
78
+ }
@@ -0,0 +1,128 @@
1
+ import type {IconifyIconHTMLElement} from 'iconify-icon';
2
+ import {el, HTMLElementBase} from '../dom';
3
+ import {homeStyles} from './styles';
4
+ import type {HeroAction, HeroBadge, HeroLink} from '../types';
5
+
6
+ /**
7
+ * `<dfk-hero>` — the landing hero: logo, title, tagline, the two call-to-action
8
+ * buttons and the badge row. Ported from the Docusaurus home page's `Hero()`.
9
+ *
10
+ * Retained-mode: every node is held in a field, the structure is assembled once
11
+ * in the constructor and the `set*` methods only mutate the nodes they own.
12
+ *
13
+ * The tree lives in a shadow root (adopting the shared `homeStyles()` sheet), so
14
+ * the site's global CSS cannot reach it; theme colours cross the boundary
15
+ * through the inherited `--duckfn-*` / `--ifm-*` custom properties.
16
+ */
17
+ export class DfkHero extends HTMLElementBase {
18
+ readonly #section = el('section', {class: 'dfk-hero'});
19
+ readonly #inner = el('div', {class: 'dfk-hero-inner'});
20
+ readonly #stage = el('span', {class: 'dfk-logo-stage'});
21
+ // The <h1> spells out the name, so the logo is decorative: alt="".
22
+ readonly #logo = el('img', {
23
+ class: 'dfk-logo',
24
+ alt: '',
25
+ width: 480,
26
+ height: 480,
27
+ });
28
+ readonly #title = el('h1', {class: 'dfk-title'});
29
+ readonly #tagline = el('p', {class: 'dfk-tagline'});
30
+ readonly #actions = el('div', {class: 'dfk-actions'});
31
+ readonly #primary = el('a', {class: 'dfk-button-primary'});
32
+ readonly #primaryLabel = el('span');
33
+ readonly #secondary = el('a', {class: 'dfk-button-secondary'});
34
+ readonly #secondaryIcon: IconifyIconHTMLElement = el('iconify-icon', {
35
+ class: 'dfk-button-icon',
36
+ attrs: {'aria-hidden': 'true'},
37
+ });
38
+ readonly #secondaryLabel = el('span');
39
+ readonly #badges = el('div', {class: 'dfk-badges'});
40
+ readonly #badgeLinks: HTMLAnchorElement[] = [];
41
+
42
+ constructor() {
43
+ super();
44
+ this.#stage.appendChild(this.#logo);
45
+ this.#primary.appendChild(this.#primaryLabel);
46
+ this.#secondary.append(this.#secondaryIcon, this.#secondaryLabel);
47
+ this.#actions.append(this.#primary, this.#secondary);
48
+ this.#inner.append(
49
+ this.#stage,
50
+ this.#title,
51
+ this.#tagline,
52
+ this.#actions,
53
+ this.#badges,
54
+ );
55
+ this.#section.appendChild(this.#inner);
56
+ // A custom element's constructor may call `attachShadow` (it only must not
57
+ // add children or attributes to `this`), so the whole tree lives in the
58
+ // shadow root from the first moment the element exists — the setters are
59
+ // safe to call before the element is even connected.
60
+ const shadow = this.attachShadow({mode: 'open'});
61
+ shadow.adoptedStyleSheets = [homeStyles()];
62
+ shadow.appendChild(this.#section);
63
+ }
64
+
65
+ setLogo(src: string): void {
66
+ this.#logo.src = src;
67
+ }
68
+
69
+ setTitle(text: string): void {
70
+ this.#title.textContent = text;
71
+ }
72
+
73
+ setTagline(text: string): void {
74
+ this.#tagline.textContent = text;
75
+ }
76
+
77
+ /** The "Get started" button: an internal link, so it stays in the same tab. */
78
+ setPrimaryAction(link: HeroLink): void {
79
+ this.#fillLink(this.#primary, this.#primaryLabel, link, false);
80
+ }
81
+
82
+ /** The GitHub button: always external, always carries the glyph. */
83
+ setSecondaryAction(action: HeroAction): void {
84
+ this.#fillLink(this.#secondary, this.#secondaryLabel, action, true);
85
+ this.#secondaryIcon.setAttribute('icon', action.icon);
86
+ }
87
+
88
+ setBadges(badges: readonly HeroBadge[]): void {
89
+ // Grow/shrink the row to the new length and reuse the links already there.
90
+ while (this.#badgeLinks.length > badges.length) {
91
+ this.#badgeLinks.pop()?.remove();
92
+ }
93
+ while (this.#badgeLinks.length < badges.length) {
94
+ const link = el('a', {
95
+ class: 'dfk-badge',
96
+ target: '_blank',
97
+ rel: 'noopener noreferrer',
98
+ });
99
+ link.appendChild(el('img', {class: 'dfk-badge-image'}));
100
+ this.#badgeLinks.push(link);
101
+ this.#badges.appendChild(link);
102
+ }
103
+ badges.forEach((badge, index) => {
104
+ const link = this.#badgeLinks[index];
105
+ link.href = badge.href;
106
+ const image = link.firstElementChild as HTMLImageElement;
107
+ image.src = badge.src;
108
+ image.alt = badge.alt;
109
+ });
110
+ }
111
+
112
+ #fillLink(
113
+ anchor: HTMLAnchorElement,
114
+ label: HTMLElement,
115
+ link: HeroLink,
116
+ external: boolean,
117
+ ): void {
118
+ anchor.href = link.href;
119
+ label.textContent = link.label;
120
+ if (external) {
121
+ anchor.target = '_blank';
122
+ anchor.rel = 'noopener noreferrer';
123
+ } else {
124
+ anchor.removeAttribute('target');
125
+ anchor.removeAttribute('rel');
126
+ }
127
+ }
128
+ }
@@ -0,0 +1,73 @@
1
+ import type {IconifyIconHTMLElement} from 'iconify-icon';
2
+ import {el, HTMLElementBase} from '../dom';
3
+ import {homeStyles} from './styles';
4
+ import type {NextStepItem} from '../types';
5
+
6
+ /**
7
+ * `<dfk-next-steps>` — the "Where to go next" row of link cards. Ported from
8
+ * the home page's `NextSteps()`.
9
+ *
10
+ * Same retained-mode shape as `DfkFeatures`: the grid is built once, each card
11
+ * holds its own nodes, `setSteps()` grows/shrinks the list and mutates in place.
12
+ * The tree lives in a shadow root like the other `dfk-*` elements.
13
+ */
14
+ export class DfkNextSteps extends HTMLElementBase {
15
+ readonly #section = el('section', {class: 'dfk-section'});
16
+ readonly #inner = el('div', {class: 'dfk-section-inner'});
17
+ readonly #heading = el('h2', {class: 'dfk-section-title'});
18
+ readonly #grid = el('div', {class: 'dfk-next-grid'});
19
+ readonly #cards: DfkNextStepCard[] = [];
20
+
21
+ constructor() {
22
+ super();
23
+ this.#inner.append(this.#heading, this.#grid);
24
+ this.#section.appendChild(this.#inner);
25
+ const shadow = this.attachShadow({mode: 'open'});
26
+ shadow.adoptedStyleSheets = [homeStyles()];
27
+ shadow.appendChild(this.#section);
28
+ }
29
+
30
+ setSectionTitle(text: string): void {
31
+ this.#heading.textContent = text;
32
+ }
33
+
34
+ setSteps(items: readonly NextStepItem[]): void {
35
+ while (this.#cards.length > items.length) {
36
+ this.#cards.pop()?.root.remove();
37
+ }
38
+ while (this.#cards.length < items.length) {
39
+ const card = new DfkNextStepCard();
40
+ this.#cards.push(card);
41
+ this.#grid.appendChild(card.root);
42
+ }
43
+ items.forEach((item, index) => this.#cards[index].setStep(item));
44
+ }
45
+ }
46
+
47
+ /** One "next step" card: the whole card is the link. Built once. */
48
+ class DfkNextStepCard {
49
+ readonly root = el('a', {class: 'dfk-next-card'});
50
+ readonly #title = el('span', {class: 'dfk-next-card-title'});
51
+ readonly #details = el('span', {class: 'dfk-next-card-details'});
52
+ readonly #arrow: IconifyIconHTMLElement = el('iconify-icon', {
53
+ class: 'dfk-next-card-arrow',
54
+ attrs: {icon: 'lucide:arrow-right', 'aria-hidden': 'true'},
55
+ });
56
+
57
+ constructor() {
58
+ // The body only groups the two text spans and is never touched again, so it
59
+ // is described in place instead of being held in a field.
60
+ this.root.append(
61
+ el('span', {class: 'dfk-next-card-body'}, (body) =>
62
+ body.append(this.#title, this.#details),
63
+ ),
64
+ this.#arrow,
65
+ );
66
+ }
67
+
68
+ setStep(item: NextStepItem): void {
69
+ this.root.href = item.href;
70
+ this.#title.textContent = item.title;
71
+ this.#details.textContent = item.details;
72
+ }
73
+ }