@barefootjs/client 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/dist/build.d.ts +56 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +76 -0
- package/dist/context.d.ts +25 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/csr-adapter.d.ts +26 -0
- package/dist/csr-adapter.d.ts.map +1 -0
- package/dist/forward-props.d.ts +17 -0
- package/dist/forward-props.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +154 -0
- package/dist/reactive.d.ts +150 -0
- package/dist/reactive.d.ts.map +1 -0
- package/dist/reactive.js +215 -0
- package/dist/runtime/apply-rest-attrs.d.ts +16 -0
- package/dist/runtime/apply-rest-attrs.d.ts.map +1 -0
- package/dist/runtime/branch-slot.d.ts +22 -0
- package/dist/runtime/branch-slot.d.ts.map +1 -0
- package/dist/runtime/client-marker.d.ts +21 -0
- package/dist/runtime/client-marker.d.ts.map +1 -0
- package/dist/runtime/component.d.ts +99 -0
- package/dist/runtime/component.d.ts.map +1 -0
- package/dist/runtime/context.d.ts +40 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/hydrate.d.ts +100 -0
- package/dist/runtime/hydrate.d.ts.map +1 -0
- package/dist/runtime/hydration-state.d.ts +13 -0
- package/dist/runtime/hydration-state.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +27 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +2093 -0
- package/dist/runtime/insert.d.ts +75 -0
- package/dist/runtime/insert.d.ts.map +1 -0
- package/dist/runtime/list.d.ts +21 -0
- package/dist/runtime/list.d.ts.map +1 -0
- package/dist/runtime/map-array.d.ts +32 -0
- package/dist/runtime/map-array.d.ts.map +1 -0
- package/dist/runtime/portal.d.ts +96 -0
- package/dist/runtime/portal.d.ts.map +1 -0
- package/dist/runtime/qsa-item.d.ts +52 -0
- package/dist/runtime/qsa-item.d.ts.map +1 -0
- package/dist/runtime/query.d.ts +86 -0
- package/dist/runtime/query.d.ts.map +1 -0
- package/dist/runtime/reconcile-elements.d.ts +44 -0
- package/dist/runtime/reconcile-elements.d.ts.map +1 -0
- package/dist/runtime/registry.d.ts +53 -0
- package/dist/runtime/registry.d.ts.map +1 -0
- package/dist/runtime/render.d.ts +35 -0
- package/dist/runtime/render.d.ts.map +1 -0
- package/dist/runtime/scope.d.ts +28 -0
- package/dist/runtime/scope.d.ts.map +1 -0
- package/dist/runtime/slot-resolver.d.ts +36 -0
- package/dist/runtime/slot-resolver.d.ts.map +1 -0
- package/dist/runtime/spread-attrs.d.ts +19 -0
- package/dist/runtime/spread-attrs.d.ts.map +1 -0
- package/dist/runtime/standalone.js +2278 -0
- package/dist/runtime/streaming.d.ts +36 -0
- package/dist/runtime/streaming.d.ts.map +1 -0
- package/dist/runtime/style.d.ts +17 -0
- package/dist/runtime/style.d.ts.map +1 -0
- package/dist/runtime/template.d.ts +39 -0
- package/dist/runtime/template.d.ts.map +1 -0
- package/dist/runtime/types.d.ts +26 -0
- package/dist/runtime/types.d.ts.map +1 -0
- package/dist/shims.d.ts +21 -0
- package/dist/shims.d.ts.map +1 -0
- package/dist/slot.d.ts +14 -0
- package/dist/slot.d.ts.map +1 -0
- package/dist/split-props.d.ts +26 -0
- package/dist/split-props.d.ts.map +1 -0
- package/dist/unwrap.d.ts +16 -0
- package/dist/unwrap.d.ts.map +1 -0
- package/package.json +71 -0
- package/src/build.ts +92 -0
- package/src/context.ts +33 -0
- package/src/csr-adapter.ts +134 -0
- package/src/forward-props.ts +43 -0
- package/src/index.ts +42 -0
- package/src/reactive.ts +411 -0
- package/src/runtime/apply-rest-attrs.ts +109 -0
- package/src/runtime/branch-slot.ts +32 -0
- package/src/runtime/client-marker.ts +46 -0
- package/src/runtime/component.ts +501 -0
- package/src/runtime/context.ts +111 -0
- package/src/runtime/hydrate.ts +311 -0
- package/src/runtime/hydration-state.ts +13 -0
- package/src/runtime/index.ts +96 -0
- package/src/runtime/insert.ts +407 -0
- package/src/runtime/list.ts +47 -0
- package/src/runtime/map-array.ts +381 -0
- package/src/runtime/portal.ts +174 -0
- package/src/runtime/qsa-item.ts +128 -0
- package/src/runtime/query.ts +632 -0
- package/src/runtime/reconcile-elements.ts +391 -0
- package/src/runtime/registry.ts +160 -0
- package/src/runtime/render.ts +105 -0
- package/src/runtime/scope.ts +46 -0
- package/src/runtime/slot-resolver.ts +66 -0
- package/src/runtime/spread-attrs.ts +88 -0
- package/src/runtime/streaming.ts +65 -0
- package/src/runtime/style.ts +27 -0
- package/src/runtime/template.ts +53 -0
- package/src/runtime/types.ts +27 -0
- package/src/shims.ts +54 -0
- package/src/slot.ts +23 -0
- package/src/split-props.ts +86 -0
- package/src/unwrap.ts +18 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared slot-relationship resolver: identifies the SSR scope of a child
|
|
3
|
+
* component mounted at slot `<sN>` inside its parent.
|
|
4
|
+
*
|
|
5
|
+
* Both single-root (`registry.ts::upsertChild`) and multi-root loop-body
|
|
6
|
+
* (`qsa-item.ts::upsertChildItem`) paths consume this. They differ only
|
|
7
|
+
* in whether they search a single `parent` element or walk a sequence of
|
|
8
|
+
* loop-item root elements; the lookup logic is identical.
|
|
9
|
+
*
|
|
10
|
+
* See `spec/compiler.md` "Slot identity" for the marker contract.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { BF_SCOPE, BF_HOST, BF_AT } from '@barefootjs/shared'
|
|
14
|
+
import { cssEscape } from './query'
|
|
15
|
+
|
|
16
|
+
/** Resolve the host scope id for a slot lookup. Prefers the explicit
|
|
17
|
+
* `anchorScope` because the immediate `parent` element may be a freshly-
|
|
18
|
+
* created detached fragment whose `closest()` returns null. */
|
|
19
|
+
export function parentScopeOf(parent: Element, anchorScope?: Element | null): string {
|
|
20
|
+
const ancestor = anchorScope ?? parent.closest(`[${BF_SCOPE}]`)
|
|
21
|
+
if (!ancestor) return ''
|
|
22
|
+
return ancestor.getAttribute(BF_SCOPE) ?? ''
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Build the (host, slot) metadata for a fresh component about to be
|
|
26
|
+
* mounted at `slotId`. `createComponent` stamps these onto the new
|
|
27
|
+
* element so subsequent `upsertChild` lookups can find it. `parent`
|
|
28
|
+
* defaults to the empty string when no surrounding scope is resolvable
|
|
29
|
+
* (top-level CSR mount). */
|
|
30
|
+
export function buildSlotInfo(
|
|
31
|
+
parent: Element,
|
|
32
|
+
slotId: string,
|
|
33
|
+
anchorScope?: Element | null,
|
|
34
|
+
): { parent: string; mount: string } {
|
|
35
|
+
return { parent: parentScopeOf(parent, anchorScope), mount: slotId }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Find the SSR scope element for a child component at `slotId` inside
|
|
40
|
+
* `parent`. Primary lookup is `(BF_HOST, BF_AT)`; the suffix fallback
|
|
41
|
+
* covers `renderChild` paths that emit a parent-anchored `bf-s` without
|
|
42
|
+
* stamping host metadata.
|
|
43
|
+
*
|
|
44
|
+
* `selfMatch` lets the multi-root loop-body caller include the root
|
|
45
|
+
* element itself in the search (the loop-item primary may be the scope
|
|
46
|
+
* element, not just a parent of it).
|
|
47
|
+
*/
|
|
48
|
+
export function findSsrScopeBySlotIn(
|
|
49
|
+
parent: Element,
|
|
50
|
+
slotId: string,
|
|
51
|
+
anchorScope: Element | null | undefined,
|
|
52
|
+
selfMatch: boolean,
|
|
53
|
+
): HTMLElement | null {
|
|
54
|
+
const parentBfs = parentScopeOf(parent, anchorScope)
|
|
55
|
+
|
|
56
|
+
if (parentBfs) {
|
|
57
|
+
const selector = `[${BF_HOST}="${cssEscape(parentBfs)}"][${BF_AT}="${slotId}"]`
|
|
58
|
+
if (selfMatch && parent.matches(selector)) return parent as HTMLElement
|
|
59
|
+
const direct = parent.querySelector(selector) as HTMLElement | null
|
|
60
|
+
if (direct) return direct
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const suffixSelector = `[${BF_SCOPE}$="_${slotId}"]`
|
|
64
|
+
if (selfMatch && parent.matches(suffixSelector)) return parent as HTMLElement
|
|
65
|
+
return parent.querySelector(suffixSelector) as HTMLElement | null
|
|
66
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarefootJS - Spread Attributes Helper (Template Mode)
|
|
3
|
+
*
|
|
4
|
+
* Converts an object to an HTML attribute string for use inside template literals.
|
|
5
|
+
* Unlike applyRestAttrs (which manipulates DOM elements reactively), this produces
|
|
6
|
+
* a static string for server/template rendering of computed local spreads.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { styleToCss } from './style'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SVG attributes that are case-sensitive and MUST stay in camelCase.
|
|
13
|
+
*
|
|
14
|
+
* The default JSX-prop → HTML-attribute rewrite lower-cases camelCase
|
|
15
|
+
* (`fooBar` → `foo-bar`), which is correct for HTML attrs and for the
|
|
16
|
+
* CSS-style SVG presentation attrs (`strokeWidth` → `stroke-width`).
|
|
17
|
+
* The XML-namespaced SVG attrs below, though, are case-sensitive in
|
|
18
|
+
* the spec: `viewBox` lower-cased to `view-box` makes the browser
|
|
19
|
+
* treat it as an unknown user attribute and the SVG no longer renders
|
|
20
|
+
* (pointer events stop hitting the inner geometry — surfaced as the
|
|
21
|
+
* Form Builder e2e regression in #1244's merge-emit follow-up).
|
|
22
|
+
*
|
|
23
|
+
* Coordinates with the compile-time `SVG_CAMEL_TO_KEBAB` table in
|
|
24
|
+
* `packages/jsx/src/ir-to-client-js/utils.ts`: presentation attrs
|
|
25
|
+
* (`clipPath`, `strokeWidth`, …) live there and must NOT appear here,
|
|
26
|
+
* or the same JSX prop would lower to `clip-path` via the explicit-
|
|
27
|
+
* attr path and stay `clipPath` via the spread path — a silent
|
|
28
|
+
* divergence. The list below is XML attribute names that have no
|
|
29
|
+
* kebab-case mirror (`viewBox`, `clipPathUnits`, …).
|
|
30
|
+
*
|
|
31
|
+
* The list mirrors React DOM's `DOMProperty` case-preserving entries
|
|
32
|
+
* (only the attributes that appear on actual SVG elements; ARIA and
|
|
33
|
+
* XLink namespaces are unrelated and handled by their `aria-*` /
|
|
34
|
+
* `xlink:*` literal prefix).
|
|
35
|
+
*/
|
|
36
|
+
const SVG_CAMEL_CASE_ATTRS: ReadonlySet<string> = new Set([
|
|
37
|
+
'allowReorder', 'attributeName', 'attributeType', 'autoReverse',
|
|
38
|
+
'baseFrequency', 'baseProfile', 'calcMode', 'clipPathUnits',
|
|
39
|
+
'contentScriptType', 'contentStyleType', 'diffuseConstant', 'edgeMode',
|
|
40
|
+
'externalResourcesRequired', 'filterRes', 'filterUnits', 'glyphRef',
|
|
41
|
+
'gradientTransform', 'gradientUnits', 'kernelMatrix', 'kernelUnitLength',
|
|
42
|
+
'keyPoints', 'keySplines', 'keyTimes', 'lengthAdjust', 'limitingConeAngle',
|
|
43
|
+
'markerHeight', 'markerUnits', 'markerWidth', 'maskContentUnits',
|
|
44
|
+
'maskUnits', 'numOctaves', 'pathLength', 'patternContentUnits',
|
|
45
|
+
'patternTransform', 'patternUnits', 'pointsAtX', 'pointsAtY', 'pointsAtZ',
|
|
46
|
+
'preserveAlpha', 'preserveAspectRatio', 'primitiveUnits', 'refX', 'refY',
|
|
47
|
+
'repeatCount', 'repeatDur', 'requiredExtensions', 'requiredFeatures',
|
|
48
|
+
'specularConstant', 'specularExponent', 'spreadMethod', 'startOffset',
|
|
49
|
+
'stdDeviation', 'stitchTiles', 'surfaceScale', 'systemLanguage',
|
|
50
|
+
'tableValues', 'targetX', 'targetY', 'textLength', 'viewBox', 'viewTarget',
|
|
51
|
+
'xChannelSelector', 'yChannelSelector', 'zoomAndPan',
|
|
52
|
+
])
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Convert an object to an HTML attribute string.
|
|
56
|
+
* Aligned with applyRestAttrs conventions: skips null/undefined/false,
|
|
57
|
+
* event handlers, maps className→class and htmlFor→for. The `style`
|
|
58
|
+
* prop is routed through `styleToCss` so object literals serialize to
|
|
59
|
+
* a real CSS string (matching the reactive `applyRestAttrs` path).
|
|
60
|
+
*
|
|
61
|
+
* SVG attributes listed in `SVG_CAMEL_CASE_ATTRS` are preserved
|
|
62
|
+
* verbatim — the SVG XML spec is case-sensitive for those names.
|
|
63
|
+
*/
|
|
64
|
+
export function spreadAttrs(obj: Record<string, unknown>): string {
|
|
65
|
+
if (!obj || typeof obj !== 'object') return ''
|
|
66
|
+
const parts: string[] = []
|
|
67
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
68
|
+
if (value == null || value === false) continue
|
|
69
|
+
// Skip event handlers
|
|
70
|
+
if (key.startsWith('on') && key.length > 2 && key[2] === key[2].toUpperCase()) continue
|
|
71
|
+
// Skip children prop
|
|
72
|
+
if (key === 'children') continue
|
|
73
|
+
if (key === 'style') {
|
|
74
|
+
const css = styleToCss(value)
|
|
75
|
+
if (css != null) parts.push(`style="${css}"`)
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
// Map JSX prop names to HTML attribute names. Case-sensitive SVG
|
|
79
|
+
// attrs keep their camelCase per the spec; HTML / CSS-style SVG
|
|
80
|
+
// presentation attrs lower-case to kebab-case.
|
|
81
|
+
const attr = key === 'className' ? 'class'
|
|
82
|
+
: key === 'htmlFor' ? 'for'
|
|
83
|
+
: SVG_CAMEL_CASE_ATTRS.has(key) ? key
|
|
84
|
+
: key.replace(/([A-Z])/g, '-$1').toLowerCase()
|
|
85
|
+
parts.push(value === true ? attr : `${attr}="${value}"`)
|
|
86
|
+
}
|
|
87
|
+
return parts.join(' ')
|
|
88
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarefootJS - Out-of-Order Streaming
|
|
3
|
+
*
|
|
4
|
+
* Client-side resolver for OOS (Out-of-Order Streaming) SSR.
|
|
5
|
+
* Handles swapping fallback content with resolved content that arrives
|
|
6
|
+
* via chunked HTTP responses.
|
|
7
|
+
*
|
|
8
|
+
* Protocol:
|
|
9
|
+
* 1. Server sends HTML with fallback placeholders: <div bf-async="a0">...</div>
|
|
10
|
+
* 2. As async data resolves, server appends chunks:
|
|
11
|
+
* <template bf-async-resolve="a0">...resolved...</template>
|
|
12
|
+
* <script>__bf_swap("a0")</script>
|
|
13
|
+
* 3. This module swaps fallback → resolved content and triggers hydration.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { BF_ASYNC, BF_ASYNC_RESOLVE } from '@barefootjs/shared'
|
|
17
|
+
import { rehydrateAll } from './hydrate'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Swap a streaming fallback placeholder with its resolved content.
|
|
21
|
+
*
|
|
22
|
+
* Finds the placeholder element (`[bf-async="<id>"]`) and the resolve
|
|
23
|
+
* template (`<template bf-async-resolve="<id>">`), replaces the placeholder's
|
|
24
|
+
* children with the resolved content, then triggers hydration.
|
|
25
|
+
*
|
|
26
|
+
* @param id - The async boundary ID (e.g., "a0")
|
|
27
|
+
*/
|
|
28
|
+
export function __bf_swap(id: string): void {
|
|
29
|
+
const slot = document.querySelector(`[${BF_ASYNC}="${id}"]`)
|
|
30
|
+
const tmpl = document.querySelector(`template[${BF_ASYNC_RESOLVE}="${id}"]`) as HTMLTemplateElement | null
|
|
31
|
+
|
|
32
|
+
if (!slot || !tmpl) return
|
|
33
|
+
|
|
34
|
+
// Replace fallback with resolved content
|
|
35
|
+
slot.replaceChildren(tmpl.content.cloneNode(true))
|
|
36
|
+
slot.removeAttribute(BF_ASYNC)
|
|
37
|
+
|
|
38
|
+
// Clean up the template element
|
|
39
|
+
tmpl.remove()
|
|
40
|
+
|
|
41
|
+
// Trigger hydration for newly inserted content. `rehydrateAll()`
|
|
42
|
+
// schedules its own microtask + rAF walk via `scheduleWalk()`, so the
|
|
43
|
+
// extra rAF wrapper that used to live here is redundant — calling
|
|
44
|
+
// through synchronously lets the microtask path catch the swap on
|
|
45
|
+
// the same tick.
|
|
46
|
+
rehydrateAll()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Install the global streaming resolver.
|
|
51
|
+
*
|
|
52
|
+
* Makes `__bf_swap` available as `window.__bf_swap` so that inline
|
|
53
|
+
* `<script>__bf_swap("a0")</script>` tags in streaming chunks can call it.
|
|
54
|
+
*
|
|
55
|
+
* Also exposes `window.__bf_hydrate` for manual re-hydration triggers.
|
|
56
|
+
*
|
|
57
|
+
* Call this once, early in the page (before any streaming chunks arrive).
|
|
58
|
+
*/
|
|
59
|
+
export function setupStreaming(): void {
|
|
60
|
+
if (typeof window === 'undefined') return
|
|
61
|
+
|
|
62
|
+
const w = window as unknown as Record<string, unknown>
|
|
63
|
+
w.__bf_swap = __bf_swap
|
|
64
|
+
w.__bf_hydrate = rehydrateAll
|
|
65
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style attribute helpers for client-side DOM updates.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert a style value (string or object) to a CSS string, or null to remove the attribute.
|
|
7
|
+
*
|
|
8
|
+
* - null/undefined → null (remove the attribute)
|
|
9
|
+
* - string → the string as-is
|
|
10
|
+
* - object → camelCase keys converted to kebab-case, joined with semicolons
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* styleToCss({ backgroundColor: 'red', fontSize: '16px' }) // "background-color:red;font-size:16px"
|
|
14
|
+
* styleToCss('color:red') // "color:red"
|
|
15
|
+
* styleToCss(null) // null
|
|
16
|
+
*/
|
|
17
|
+
export function styleToCss(value: unknown): string | null {
|
|
18
|
+
if (value == null) return null
|
|
19
|
+
if (typeof value !== 'object') return String(value)
|
|
20
|
+
const parts: string[] = []
|
|
21
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
22
|
+
if (v == null) continue
|
|
23
|
+
const prop = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
|
|
24
|
+
parts.push(`${prop}:${v}`)
|
|
25
|
+
}
|
|
26
|
+
return parts.join(';') || null
|
|
27
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarefootJS - Template Registry
|
|
3
|
+
*
|
|
4
|
+
* Stores template functions for client-side component creation.
|
|
5
|
+
* Templates generate HTML strings from props, used by createComponent().
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Template function type - generates HTML string from props
|
|
10
|
+
*/
|
|
11
|
+
export type TemplateFn = (props: Record<string, unknown>) => string
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Registry storing template functions by component name
|
|
15
|
+
*/
|
|
16
|
+
const templateRegistry = new Map<string, TemplateFn>()
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Register a template function for a component.
|
|
20
|
+
*
|
|
21
|
+
* @param name - Component name (e.g., 'TodoItem')
|
|
22
|
+
* @param templateFn - Function that generates HTML from props
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* registerTemplate('TodoItem', (props) => `
|
|
26
|
+
* <li class="${props.done ? 'done' : ''}">
|
|
27
|
+
* <span>${props.text}</span>
|
|
28
|
+
* </li>
|
|
29
|
+
* `)
|
|
30
|
+
*/
|
|
31
|
+
export function registerTemplate(name: string, templateFn: TemplateFn): void {
|
|
32
|
+
templateRegistry.set(name, templateFn)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get a registered template function by component name.
|
|
37
|
+
*
|
|
38
|
+
* @param name - Component name
|
|
39
|
+
* @returns Template function or undefined if not registered
|
|
40
|
+
*/
|
|
41
|
+
export function getTemplate(name: string): TemplateFn | undefined {
|
|
42
|
+
return templateRegistry.get(name)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if a template is registered for a component.
|
|
47
|
+
*
|
|
48
|
+
* @param name - Component name
|
|
49
|
+
* @returns true if template is registered
|
|
50
|
+
*/
|
|
51
|
+
export function hasTemplate(name: string): boolean {
|
|
52
|
+
return templateRegistry.has(name)
|
|
53
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarefootJS - Core Types
|
|
3
|
+
*
|
|
4
|
+
* Shared type definitions for component initialization and registration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Component init function type.
|
|
9
|
+
* Takes the scope element and props, initializes the component
|
|
10
|
+
* by setting up event handlers, effects, and reactive bindings.
|
|
11
|
+
*/
|
|
12
|
+
export type InitFn = (scope: Element, props: Record<string, unknown>) => void
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Component definition.
|
|
16
|
+
* Bundles the init function with optional template and scope metadata.
|
|
17
|
+
*/
|
|
18
|
+
export interface ComponentDef {
|
|
19
|
+
/** Component name (e.g., 'Counter'). Used for scope ID generation. */
|
|
20
|
+
name?: string
|
|
21
|
+
/** Init function that hydrates a scope element */
|
|
22
|
+
init: InitFn
|
|
23
|
+
/** Template function for client-side component creation */
|
|
24
|
+
template?: (props: Record<string, unknown>) => string
|
|
25
|
+
/** When true, use comment-based scope hydration (fragment roots) */
|
|
26
|
+
comment?: boolean
|
|
27
|
+
}
|
package/src/shims.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-only API shims.
|
|
3
|
+
*
|
|
4
|
+
* These functions have real implementations in `./runtime/` that the
|
|
5
|
+
* compiler emits for `'use client'` components. The exports here exist
|
|
6
|
+
* for type-checking in user source files.
|
|
7
|
+
*
|
|
8
|
+
* If one of these ever runs, it means a `'use client'` component was
|
|
9
|
+
* executed without going through the compiler — or a non-client file
|
|
10
|
+
* slipped past the `MISSING_USE_CLIENT` check. Either way, it's a bug.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Context } from './context'
|
|
14
|
+
import type { Portal, PortalChildren, PortalOptions } from './runtime/portal'
|
|
15
|
+
|
|
16
|
+
export type { Portal, PortalChildren, PortalOptions, Renderable } from './runtime/portal'
|
|
17
|
+
|
|
18
|
+
function browserOnly(name: string): never {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`[barefootjs] ${name}() is a browser-only API and can only be called from a "use client" component. ` +
|
|
21
|
+
`If you are seeing this at runtime, the BarefootJS compiler did not rewrite the import — please report a bug.`,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function useContext<T>(_context: Context<T>): T {
|
|
26
|
+
return browserOnly('useContext')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function provideContext<T>(_context: Context<T>, _value: T): void {
|
|
30
|
+
return browserOnly('provideContext')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createPortal(
|
|
34
|
+
_children: PortalChildren,
|
|
35
|
+
_container?: Element,
|
|
36
|
+
_options?: PortalOptions,
|
|
37
|
+
): Portal {
|
|
38
|
+
return browserOnly('createPortal')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isSSRPortal(_element: HTMLElement): boolean {
|
|
42
|
+
return browserOnly('isSSRPortal')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function findSiblingSlot(
|
|
46
|
+
_el: HTMLElement,
|
|
47
|
+
_slotSelector: string,
|
|
48
|
+
): HTMLElement | null {
|
|
49
|
+
return browserOnly('findSiblingSlot')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function cleanupPortalPlaceholder(_portalId: string): void {
|
|
53
|
+
return browserOnly('cleanupPortalPlaceholder')
|
|
54
|
+
}
|
package/src/slot.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot marker for JSX props that contain components.
|
|
3
|
+
*
|
|
4
|
+
* When a caller passes `<Button />` inside a JSX prop, the compiler wraps
|
|
5
|
+
* the value with `__slot()`. The callee's text effect checks `__isSlot`
|
|
6
|
+
* and skips the destructive `nodeValue = String(...)` update, preserving
|
|
7
|
+
* the server-rendered DOM for hydration.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface SlotMarker {
|
|
11
|
+
__isSlot: true
|
|
12
|
+
toString(): string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function __slot(thunk: () => unknown): SlotMarker {
|
|
16
|
+
return {
|
|
17
|
+
__isSlot: true,
|
|
18
|
+
toString() {
|
|
19
|
+
const result = thunk()
|
|
20
|
+
return result == null ? '' : String(result)
|
|
21
|
+
},
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarefootJS - splitProps
|
|
3
|
+
*
|
|
4
|
+
* SolidJS-compatible utility for splitting a props object into local and rest.
|
|
5
|
+
* Uses Proxy to preserve getter-based reactivity.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* import { splitProps } from '@barefootjs/client'
|
|
10
|
+
*
|
|
11
|
+
* function Checkbox(props: CheckboxProps) {
|
|
12
|
+
* const [local, rest] = splitProps(props, ['checked', 'onCheckedChange'])
|
|
13
|
+
* return <button {...rest} aria-checked={local.checked} />
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Split a props object into two: one with the specified keys, one with the rest.
|
|
20
|
+
* Both returned objects use Proxy to defer reads, preserving reactive tracking.
|
|
21
|
+
*
|
|
22
|
+
* @param props - The source props object
|
|
23
|
+
* @param keys - Keys to extract into the first (local) object
|
|
24
|
+
* @returns A tuple [local, rest] where local has the specified keys and rest has everything else
|
|
25
|
+
*/
|
|
26
|
+
export function splitProps<
|
|
27
|
+
T extends Record<string, unknown>,
|
|
28
|
+
K extends (keyof T)[]
|
|
29
|
+
>(
|
|
30
|
+
props: T,
|
|
31
|
+
keys: K
|
|
32
|
+
): [Pick<T, K[number]>, Omit<T, K[number]>] {
|
|
33
|
+
const keySet = new Set<string | symbol>(keys as (string | symbol)[])
|
|
34
|
+
|
|
35
|
+
const local = new Proxy({} as Pick<T, K[number]>, {
|
|
36
|
+
get(_, key) {
|
|
37
|
+
if (keySet.has(key)) {
|
|
38
|
+
return (props as Record<string | symbol, unknown>)[key]
|
|
39
|
+
}
|
|
40
|
+
return undefined
|
|
41
|
+
},
|
|
42
|
+
has(_, key) {
|
|
43
|
+
return keySet.has(key)
|
|
44
|
+
},
|
|
45
|
+
ownKeys() {
|
|
46
|
+
return [...keySet] as string[]
|
|
47
|
+
},
|
|
48
|
+
getOwnPropertyDescriptor(_, key) {
|
|
49
|
+
if (keySet.has(key)) {
|
|
50
|
+
return {
|
|
51
|
+
configurable: true,
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: () => (props as Record<string | symbol, unknown>)[key],
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const rest = new Proxy({} as Omit<T, K[number]>, {
|
|
61
|
+
get(_, key) {
|
|
62
|
+
if (!keySet.has(key)) {
|
|
63
|
+
return (props as Record<string | symbol, unknown>)[key]
|
|
64
|
+
}
|
|
65
|
+
return undefined
|
|
66
|
+
},
|
|
67
|
+
has(_, key) {
|
|
68
|
+
return key in props && !keySet.has(key)
|
|
69
|
+
},
|
|
70
|
+
ownKeys() {
|
|
71
|
+
return Object.keys(props).filter(k => !keySet.has(k))
|
|
72
|
+
},
|
|
73
|
+
getOwnPropertyDescriptor(_, key) {
|
|
74
|
+
if (key in props && !keySet.has(key)) {
|
|
75
|
+
return {
|
|
76
|
+
configurable: true,
|
|
77
|
+
enumerable: true,
|
|
78
|
+
get: () => (props as Record<string | symbol, unknown>)[key],
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return undefined
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return [local, rest]
|
|
86
|
+
}
|
package/src/unwrap.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BarefootJS - Unwrap Utility
|
|
3
|
+
*
|
|
4
|
+
* Unwrap a prop value that may be a getter function.
|
|
5
|
+
* When props are passed from parent to child components, reactive values
|
|
6
|
+
* are wrapped as getter functions to maintain reactivity.
|
|
7
|
+
* This helper unwraps them transparently.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Unwrap a prop value that may be a getter function.
|
|
12
|
+
*
|
|
13
|
+
* @param prop - The prop value (may be a value or a getter function)
|
|
14
|
+
* @returns The unwrapped value
|
|
15
|
+
*/
|
|
16
|
+
export function unwrap<T>(prop: T | (() => T)): T {
|
|
17
|
+
return typeof prop === 'function' ? (prop as () => T)() : prop
|
|
18
|
+
}
|