@abide/abide 0.29.0 → 0.30.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 CHANGED
@@ -178,9 +178,10 @@ Same selector grammar as `cache.invalidate`; also accept a `Subscribable`. See C
178
178
 
179
179
  ### `.abide` component format (see `src/lib/ui/README.md`)
180
180
  Valid HTML with `<script>` + native `<template>` control flow + scoped `<style>`.
181
- - **Bindings:** `{expr}` text, `name={expr}` attr, `onclick={fn}`, `bind:value={…}` / `bind:checked` / `bind:group`.
181
+ - **Bindings:** `{expr}` text, `name={expr}` attr, `onclick={fn}`, `bind:value={…}` / `bind:checked` / `bind:group`, `attach={fn}` (node-lifetime attachment — the dual of `on`; the `use:`-action / `{@attach}` equivalent, lowered to `ui/dom/attach`).
182
182
  - **Control flow (native `<template>`):** `if`/`else`, `each={list} as="x" key="x.id"`, `await={p}`/`then`/`catch`, `switch`/`case`/`default`.
183
183
  - **Components:** capitalised tags (`<Layout title=…>`); children fill `<slot/>`; props are reactive (passed as thunks). `prop('name')` reads a typed page param.
184
+ - **Snippets / named slots:** `<template name="x" args={…}>` declares a reusable named builder (the `snippet()` form), rendered like a function — covers named slots / `{@render}`.
184
185
  - **Reactivity:** write plain assignment (`count += 1`, `items.push(x)`); the compiler lowers it to patches. Deep-field edits wake only that field.
185
186
  - **SSR:** byte-identical HTML string; `renderToStream` ships the shell then streams `<template await>` fragments out of order; `hydrate` adopts static structure in place (control-flow blocks + child components fall back to `mount`/re-render — known gap).
186
187
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # abide
2
2
 
3
+ ## 0.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`311f3e7`](https://github.com/briancray/abide/commit/311f3e76732eb2b98d2739f2f7eff0fbbb667645) - cross-fade navigations via the View Transition API ([`d3ea6c0`](https://github.com/briancray/abide/commit/d3ea6c001c47748dd2be3ae47dfc3624ae7c65ee))
8
+
9
+ ### Patch Changes
10
+
11
+ - [`311f3e7`](https://github.com/briancray/abide/commit/311f3e76732eb2b98d2739f2f7eff0fbbb667645) - surface attach= binding and named-template snippets in .abide grammar ([`51a37ee`](https://github.com/briancray/abide/commit/51a37ee0b0cef708d0571de272ace3584d45a8c0))
12
+
13
+ - [`311f3e7`](https://github.com/briancray/abide/commit/311f3e76732eb2b98d2739f2f7eff0fbbb667645) - hoist component-local types above \_\_Props so prop annotations resolve ([`8d674c5`](https://github.com/briancray/abide/commit/8d674c543be53ae5775124f392ee44fd8731204f))
14
+
3
15
  ## 0.29.0
4
16
 
5
17
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abide/abide",
3
- "version": "0.29.0",
3
+ "version": "0.30.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "Isomorphic multimodal HTTP framework built for humans and machines in a single Bun runtime",
@@ -46,11 +46,18 @@ export function compileShadow(source: string): CompiledShadow {
46
46
  const scriptStart = leadingScript ? source.indexOf('>', leadingScript.index) + 1 : 0
47
47
  const templateStart = leadingScript ? (leadingScript.index ?? 0) + leadingScript[0].length : 0
48
48
 
49
- const { imports, scope, props } = analyzeScript(scriptBody, scriptStart)
49
+ const { imports, types, scope, props } = analyzeScript(scriptBody, scriptStart)
50
50
  builder.raw(SHADOW_PREAMBLE)
51
51
  for (const line of imports) {
52
52
  builder.flush(line)
53
53
  }
54
+ /* Component-local `type`/`interface` declarations are hoisted to module scope —
55
+ above `__Props` so prop annotations referencing them resolve, and still visible
56
+ inside the function body where the rest of the scope and template expressions use
57
+ them. (Emitting them as in-function scope lines would hide them from `__Props`.) */
58
+ for (const line of types) {
59
+ builder.flush(line)
60
+ }
54
61
  builder.raw(`interface __Props {\n${props.join('\n')}\n}\n`)
55
62
  /* async so `await` blocks are legal; never executed, so the return is void. */
56
63
  builder.raw('export default async function (props: __Props): Promise<void> {\n')
@@ -117,17 +124,24 @@ function createBuilder(): Builder {
117
124
  return builder
118
125
  }
119
126
 
120
- type ScriptAnalysis = { imports: ScopeLine[]; scope: ScopeLine[]; props: string[] }
127
+ type ScriptAnalysis = {
128
+ imports: ScopeLine[]
129
+ types: ScopeLine[]
130
+ scope: ScopeLine[]
131
+ props: string[]
132
+ }
121
133
 
122
134
  /* Walks the leading `<script>` and produces the shadow's module imports, the
123
- value-typed scope lines, and the Props interface fields. `scriptStart` is the
124
- body's absolute offset in the source, so verbatim spans map back exactly. */
135
+ module-scope type declarations, the value-typed scope lines, and the Props
136
+ interface fields. `scriptStart` is the body's absolute offset in the source, so
137
+ verbatim spans map back exactly. */
125
138
  function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis {
126
139
  const imports: ScopeLine[] = []
140
+ const types: ScopeLine[] = []
127
141
  const scope: ScopeLine[] = []
128
142
  const props: string[] = []
129
143
  if (scriptBody.trim() === '') {
130
- return { imports, scope, props }
144
+ return { imports, types, scope, props }
131
145
  }
132
146
  const file = ts.createSourceFile('script.ts', scriptBody, ts.ScriptTarget.Latest, true)
133
147
  /* A verbatim span: original text + the segment mapping it back, relative to the
@@ -145,6 +159,11 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
145
159
  imports.push({ text: verbatim(statement), segments: [span(statement, 0)] })
146
160
  continue
147
161
  }
162
+ if (ts.isTypeAliasDeclaration(statement) || ts.isInterfaceDeclaration(statement)) {
163
+ /* Hoist to module scope (verbatim, mapped) so prop annotations resolve them. */
164
+ types.push({ text: verbatim(statement), segments: [span(statement, 0)] })
165
+ continue
166
+ }
148
167
  const reactive = reactiveDeclarations(statement)
149
168
  if (reactive === undefined) {
150
169
  /* Plain statement (function, const, expression) — emit verbatim, mapped. */
@@ -155,7 +174,7 @@ function analyzeScript(scriptBody: string, scriptStart: number): ScriptAnalysis
155
174
  scope.push(scopeLineFor(declaration, props, verbatim, span))
156
175
  }
157
176
  }
158
- return { imports, scope, props }
177
+ return { imports, types, scope, props }
159
178
  }
160
179
 
161
180
  /* The `state`/`derived`/`prop` declarations in a variable statement, or undefined
@@ -264,12 +264,29 @@ export function router(
264
264
  const hydrating =
265
265
  first && pageView?.hydratable === true && pageView.hydrate !== undefined
266
266
  first = false
267
- const base = disposeFrom(divergence)
268
- /* A fresh mount clears the torn-down DOM; hydration adopts it in place. */
269
- if (!hydrating) {
270
- base.textContent = ''
267
+ /* The DOM mutation a navigation makes: tear the divergent chain down,
268
+ clear its DOM (a fresh mount; hydration adopts in place), rebuild. */
269
+ const swap = (): void => {
270
+ const base = disposeFrom(divergence)
271
+ if (!hydrating) {
272
+ base.textContent = ''
273
+ }
274
+ buildFrom(base, divergence, chainKeys, layoutViews, pageView, params, hydrating)
275
+ }
276
+ /* Wrap the swap in a View Transition where the browser supports it, so
277
+ the page change cross-fades (and shared `view-transition-name` elements
278
+ morph) — the synchronous swap is exactly the mutation the API snapshots
279
+ around. Skipped while hydrating: the first paint adopts SSR DOM in place,
280
+ not animate. CSS owns opting out (e.g. prefers-reduced-motion). */
281
+ if (
282
+ !hydrating &&
283
+ typeof document !== 'undefined' &&
284
+ 'startViewTransition' in document
285
+ ) {
286
+ document.startViewTransition(swap)
287
+ } else {
288
+ swap()
271
289
  }
272
- buildFrom(base, divergence, chainKeys, layoutViews, pageView, params, hydrating)
273
290
  })
274
291
  })
275
292
  })