@lankajs/svelte 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/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +53 -0
- package/dist/testing.js +12855 -0
- package/dist/testing.js.map +1 -0
- package/dist/wrapper-scaffold-3RO7M4FF.js +39 -0
- package/dist/wrapper-scaffold-3RO7M4FF.js.map +1 -0
- package/dist/wrapper-scaffold-legacy-UQDPD4HL.js +48 -0
- package/dist/wrapper-scaffold-legacy-UQDPD4HL.js.map +1 -0
- package/package.json +56 -0
- package/skills/lanka-svelte/SKILL.md +175 -0
- package/skills/lanka-svelte/reference.md +247 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import "./chunk-5WRI5ZAA.js";
|
|
2
|
+
|
|
3
|
+
// ../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold.svelte
|
|
4
|
+
import "svelte/internal/disclose-version";
|
|
5
|
+
import * as $ from "svelte/internal/client";
|
|
6
|
+
function Wrapper_scaffold($$anchor, $$props) {
|
|
7
|
+
$.push($$props, true);
|
|
8
|
+
let wrapperInstance = $.state(void 0);
|
|
9
|
+
let componentInstance = $.state(void 0);
|
|
10
|
+
const getWrapper = () => $.get(wrapperInstance);
|
|
11
|
+
const getComponent = () => $.get(componentInstance);
|
|
12
|
+
const getComponentProps = () => $$props.componentProps;
|
|
13
|
+
var $$exports = { getWrapper, getComponent, getComponentProps };
|
|
14
|
+
var fragment = $.comment();
|
|
15
|
+
var node = $.first_child(fragment);
|
|
16
|
+
$.component(node, () => $$props.wrapper, ($$anchor2, Wrapper_1) => {
|
|
17
|
+
$.bind_this(
|
|
18
|
+
Wrapper_1($$anchor2, $.spread_props(() => $$props.wrapperProps, {
|
|
19
|
+
children: ($$anchor3, $$slotProps) => {
|
|
20
|
+
var fragment_1 = $.comment();
|
|
21
|
+
var node_1 = $.first_child(fragment_1);
|
|
22
|
+
$.component(node_1, () => $$props.component, ($$anchor4, Component_1) => {
|
|
23
|
+
$.bind_this(Component_1($$anchor4, $.spread_props(() => $$props.componentProps)), ($$value) => $.set(componentInstance, $$value, true), () => $.get(componentInstance));
|
|
24
|
+
});
|
|
25
|
+
$.append($$anchor3, fragment_1);
|
|
26
|
+
},
|
|
27
|
+
$$slots: { default: true }
|
|
28
|
+
})),
|
|
29
|
+
($$value) => $.set(wrapperInstance, $$value, true),
|
|
30
|
+
() => $.get(wrapperInstance)
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
$.append($$anchor, fragment);
|
|
34
|
+
return $.pop($$exports);
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
Wrapper_scaffold as default
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=wrapper-scaffold-3RO7M4FF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold.svelte"],"sourcesContent":["import 'svelte/internal/disclose-version';\nimport * as $ from 'svelte/internal/client';\n\nexport default function Wrapper_scaffold($$anchor, $$props) {\n\t$.push($$props, true);\n\n\tlet wrapperInstance = $.state(void 0);\n\tlet componentInstance = $.state(void 0);\n\tconst getWrapper = () => $.get(wrapperInstance);\n\tconst getComponent = () => $.get(componentInstance);\n\tconst getComponentProps = () => $$props.componentProps;\n\tvar $$exports = { getWrapper, getComponent, getComponentProps };\n\tvar fragment = $.comment();\n\tvar node = $.first_child(fragment);\n\n\t$.component(node, () => $$props.wrapper, ($$anchor, Wrapper_1) => {\n\t\t$.bind_this(\n\t\t\tWrapper_1($$anchor, $.spread_props(() => $$props.wrapperProps, {\n\t\t\t\tchildren: ($$anchor, $$slotProps) => {\n\t\t\t\t\tvar fragment_1 = $.comment();\n\t\t\t\t\tvar node_1 = $.first_child(fragment_1);\n\n\t\t\t\t\t$.component(node_1, () => $$props.component, ($$anchor, Component_1) => {\n\t\t\t\t\t\t$.bind_this(Component_1($$anchor, $.spread_props(() => $$props.componentProps)), ($$value) => $.set(componentInstance, $$value, true), () => $.get(componentInstance));\n\t\t\t\t\t});\n\n\t\t\t\t\t$.append($$anchor, fragment_1);\n\t\t\t\t},\n\t\t\t\t$$slots: { default: true }\n\t\t\t})),\n\t\t\t($$value) => $.set(wrapperInstance, $$value, true),\n\t\t\t() => $.get(wrapperInstance)\n\t\t);\n\t});\n\n\t$.append($$anchor, fragment);\n\n\treturn $.pop($$exports);\n}"],"mappings":";;;AAAA,OAAO;AACP,YAAY,OAAO;AAEJ,SAAR,iBAAkC,UAAU,SAAS;AAC3D,EAAE,OAAK,SAAS,IAAI;AAEpB,MAAI,kBAAoB,QAAM,MAAM;AACpC,MAAI,oBAAsB,QAAM,MAAM;AACtC,QAAM,aAAa,MAAQ,MAAI,eAAe;AAC9C,QAAM,eAAe,MAAQ,MAAI,iBAAiB;AAClD,QAAM,oBAAoB,MAAM,QAAQ;AACxC,MAAI,YAAY,EAAE,YAAY,cAAc,kBAAkB;AAC9D,MAAI,WAAa,UAAQ;AACzB,MAAI,OAAS,cAAY,QAAQ;AAEjC,EAAE,YAAU,MAAM,MAAM,QAAQ,SAAS,CAACA,WAAU,cAAc;AACjE,IAAE;AAAA,MACD,UAAUA,WAAY,eAAa,MAAM,QAAQ,cAAc;AAAA,QAC9D,UAAU,CAACA,WAAU,gBAAgB;AACpC,cAAI,aAAe,UAAQ;AAC3B,cAAI,SAAW,cAAY,UAAU;AAErC,UAAE,YAAU,QAAQ,MAAM,QAAQ,WAAW,CAACA,WAAU,gBAAgB;AACvE,YAAE,YAAU,YAAYA,WAAY,eAAa,MAAM,QAAQ,cAAc,CAAC,GAAG,CAAC,YAAc,MAAI,mBAAmB,SAAS,IAAI,GAAG,MAAQ,MAAI,iBAAiB,CAAC;AAAA,UACtK,CAAC;AAED,UAAE,SAAOA,WAAU,UAAU;AAAA,QAC9B;AAAA,QACA,SAAS,EAAE,SAAS,KAAK;AAAA,MAC1B,CAAC,CAAC;AAAA,MACF,CAAC,YAAc,MAAI,iBAAiB,SAAS,IAAI;AAAA,MACjD,MAAQ,MAAI,eAAe;AAAA,IAC5B;AAAA,EACD,CAAC;AAED,EAAE,SAAO,UAAU,QAAQ;AAE3B,SAAS,MAAI,SAAS;AACvB;","names":["$$anchor"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import "./chunk-5WRI5ZAA.js";
|
|
2
|
+
|
|
3
|
+
// ../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold-legacy.svelte
|
|
4
|
+
import "svelte/internal/disclose-version";
|
|
5
|
+
import "svelte/internal/flags/legacy";
|
|
6
|
+
import * as $ from "svelte/internal/client";
|
|
7
|
+
function Wrapper_scaffold_legacy($$anchor, $$props) {
|
|
8
|
+
$.push($$props, false);
|
|
9
|
+
let wrapper = $.prop($$props, "wrapper", 8);
|
|
10
|
+
let wrapperProps = $.prop($$props, "wrapperProps", 8);
|
|
11
|
+
let component2 = $.prop($$props, "component", 8);
|
|
12
|
+
let componentProps = $.prop($$props, "componentProps", 8);
|
|
13
|
+
let wrapperInstance = $.mutable_source();
|
|
14
|
+
let componentInstance = $.mutable_source();
|
|
15
|
+
const getWrapper = () => $.get(wrapperInstance);
|
|
16
|
+
const getComponent = () => $.get(componentInstance);
|
|
17
|
+
const getComponentProps = () => componentProps();
|
|
18
|
+
var $$exports = { getWrapper, getComponent, getComponentProps };
|
|
19
|
+
var fragment = $.comment();
|
|
20
|
+
var node = $.first_child(fragment);
|
|
21
|
+
$.component(node, wrapper, ($$anchor2, $$component) => {
|
|
22
|
+
$.bind_this(
|
|
23
|
+
$$component($$anchor2, $.spread_props(wrapperProps, {
|
|
24
|
+
children: ($$anchor3, $$slotProps) => {
|
|
25
|
+
var fragment_1 = $.comment();
|
|
26
|
+
var node_1 = $.first_child(fragment_1);
|
|
27
|
+
$.component(node_1, component2, ($$anchor4, $$component2) => {
|
|
28
|
+
$.bind_this($$component2($$anchor4, $.spread_props(componentProps, { $$legacy: true })), ($$value) => $.set(componentInstance, $$value), () => $.get(componentInstance));
|
|
29
|
+
});
|
|
30
|
+
$.append($$anchor3, fragment_1);
|
|
31
|
+
},
|
|
32
|
+
$$slots: { default: true },
|
|
33
|
+
$$legacy: true
|
|
34
|
+
})),
|
|
35
|
+
($$value) => $.set(wrapperInstance, $$value),
|
|
36
|
+
() => $.get(wrapperInstance)
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
$.append($$anchor, fragment);
|
|
40
|
+
$.bind_prop($$props, "getWrapper", getWrapper);
|
|
41
|
+
$.bind_prop($$props, "getComponent", getComponent);
|
|
42
|
+
$.bind_prop($$props, "getComponentProps", getComponentProps);
|
|
43
|
+
return $.pop($$exports);
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
Wrapper_scaffold_legacy as default
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=wrapper-scaffold-legacy-UQDPD4HL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../node_modules/.pnpm/@testing-library+svelte-cor_4dd48ea7741982ca29c6e300d35ac45b/node_modules/@testing-library/svelte-core/src/wrapper-scaffold-legacy.svelte"],"sourcesContent":["import 'svelte/internal/disclose-version';\nimport 'svelte/internal/flags/legacy';\nimport * as $ from 'svelte/internal/client';\n\nexport default function Wrapper_scaffold_legacy($$anchor, $$props) {\n\t$.push($$props, false);\n\n\tlet wrapper = $.prop($$props, 'wrapper', 8);\n\tlet wrapperProps = $.prop($$props, 'wrapperProps', 8);\n\tlet component = $.prop($$props, 'component', 8);\n\tlet componentProps = $.prop($$props, 'componentProps', 8);\n\tlet wrapperInstance = $.mutable_source();\n\tlet componentInstance = $.mutable_source();\n\tconst getWrapper = () => $.get(wrapperInstance);\n\tconst getComponent = () => $.get(componentInstance);\n\tconst getComponentProps = () => componentProps();\n\tvar $$exports = { getWrapper, getComponent, getComponentProps };\n\tvar fragment = $.comment();\n\tvar node = $.first_child(fragment);\n\n\t$.component(node, wrapper, ($$anchor, $$component) => {\n\t\t$.bind_this(\n\t\t\t$$component($$anchor, $.spread_props(wrapperProps, {\n\t\t\t\tchildren: ($$anchor, $$slotProps) => {\n\t\t\t\t\tvar fragment_1 = $.comment();\n\t\t\t\t\tvar node_1 = $.first_child(fragment_1);\n\n\t\t\t\t\t$.component(node_1, component, ($$anchor, $$component) => {\n\t\t\t\t\t\t$.bind_this($$component($$anchor, $.spread_props(componentProps, { $$legacy: true })), ($$value) => $.set(componentInstance, $$value), () => $.get(componentInstance));\n\t\t\t\t\t});\n\n\t\t\t\t\t$.append($$anchor, fragment_1);\n\t\t\t\t},\n\t\t\t\t$$slots: { default: true },\n\t\t\t\t$$legacy: true\n\t\t\t})),\n\t\t\t($$value) => $.set(wrapperInstance, $$value),\n\t\t\t() => $.get(wrapperInstance)\n\t\t);\n\t});\n\n\t$.append($$anchor, fragment);\n\t$.bind_prop($$props, 'getWrapper', getWrapper);\n\t$.bind_prop($$props, 'getComponent', getComponent);\n\t$.bind_prop($$props, 'getComponentProps', getComponentProps);\n\n\treturn $.pop($$exports);\n}"],"mappings":";;;AAAA,OAAO;AACP,OAAO;AACP,YAAY,OAAO;AAEJ,SAAR,wBAAyC,UAAU,SAAS;AAClE,EAAE,OAAK,SAAS,KAAK;AAErB,MAAI,UAAY,OAAK,SAAS,WAAW,CAAC;AAC1C,MAAI,eAAiB,OAAK,SAAS,gBAAgB,CAAC;AACpD,MAAIA,aAAc,OAAK,SAAS,aAAa,CAAC;AAC9C,MAAI,iBAAmB,OAAK,SAAS,kBAAkB,CAAC;AACxD,MAAI,kBAAoB,iBAAe;AACvC,MAAI,oBAAsB,iBAAe;AACzC,QAAM,aAAa,MAAQ,MAAI,eAAe;AAC9C,QAAM,eAAe,MAAQ,MAAI,iBAAiB;AAClD,QAAM,oBAAoB,MAAM,eAAe;AAC/C,MAAI,YAAY,EAAE,YAAY,cAAc,kBAAkB;AAC9D,MAAI,WAAa,UAAQ;AACzB,MAAI,OAAS,cAAY,QAAQ;AAEjC,EAAE,YAAU,MAAM,SAAS,CAACC,WAAU,gBAAgB;AACrD,IAAE;AAAA,MACD,YAAYA,WAAY,eAAa,cAAc;AAAA,QAClD,UAAU,CAACA,WAAU,gBAAgB;AACpC,cAAI,aAAe,UAAQ;AAC3B,cAAI,SAAW,cAAY,UAAU;AAErC,UAAE,YAAU,QAAQD,YAAW,CAACC,WAAUC,iBAAgB;AACzD,YAAE,YAAUA,aAAYD,WAAY,eAAa,gBAAgB,EAAE,UAAU,KAAK,CAAC,CAAC,GAAG,CAAC,YAAc,MAAI,mBAAmB,OAAO,GAAG,MAAQ,MAAI,iBAAiB,CAAC;AAAA,UACtK,CAAC;AAED,UAAE,SAAOA,WAAU,UAAU;AAAA,QAC9B;AAAA,QACA,SAAS,EAAE,SAAS,KAAK;AAAA,QACzB,UAAU;AAAA,MACX,CAAC,CAAC;AAAA,MACF,CAAC,YAAc,MAAI,iBAAiB,OAAO;AAAA,MAC3C,MAAQ,MAAI,eAAe;AAAA,IAC5B;AAAA,EACD,CAAC;AAED,EAAE,SAAO,UAAU,QAAQ;AAC3B,EAAE,YAAU,SAAS,cAAc,UAAU;AAC7C,EAAE,YAAU,SAAS,gBAAgB,YAAY;AACjD,EAAE,YAAU,SAAS,qBAAqB,iBAAiB;AAE3D,SAAS,MAAI,SAAS;AACvB;","names":["component","$$anchor","$$component"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lankajs/svelte",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "module: One function — `useLankaVM` — over `createSubscriber`, and the access tracking core already does.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/lankajs/lanka.git",
|
|
10
|
+
"directory": "modules/bindings/svelte"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/lankajs/lanka/tree/main/modules/bindings/svelte#readme",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./testing": {
|
|
21
|
+
"types": "./dist/testing.d.ts",
|
|
22
|
+
"default": "./dist/testing.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"LICENSE",
|
|
29
|
+
"README.md",
|
|
30
|
+
"skills"
|
|
31
|
+
],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"lanka": "^2.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@testing-library/svelte": "^5.4.2",
|
|
37
|
+
"@lankajs/tool-testing": "^2.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"svelte": "^5.7.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"@testing-library/svelte": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"lint": "eslint src _playground --max-warnings=0",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:coverage": "vitest run --coverage",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"bench": "vitest bench --run",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lanka-svelte
|
|
3
|
+
description: Read a lanka ViewModel from a Svelte 5 component with useLankaVM, select one value through `.current`, or satisfy the `svelte/store` contract with toLankaSvelteVM so `$todos` works. Use when writing or reviewing a Svelte or SvelteKit screen in a lanka application, when markup does not update after state changed, when a selector wakes on every change, when a store helper refuses a ViewModel, or when reviewing code that imports `@lankajs/svelte`.
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: lankajs
|
|
7
|
+
package: @lankajs/svelte
|
|
8
|
+
version: "0.1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# @lankajs/svelte
|
|
12
|
+
|
|
13
|
+
One call to read a ViewModel, and the store contract for the half of Svelte that
|
|
14
|
+
still speaks it. `reference.md` beside this file is the full guide.
|
|
15
|
+
|
|
16
|
+
> [!NOTE]
|
|
17
|
+
> Only what the framework or a gate refuses is binding. Everything else here is a
|
|
18
|
+
> recommendation you can adapt.
|
|
19
|
+
|
|
20
|
+
## Pick the call
|
|
21
|
+
|
|
22
|
+
| The situation | Use |
|
|
23
|
+
| ------------------------------------------------- | ------------------------------------------------------- |
|
|
24
|
+
| a component reads a ViewModel | `useLankaVM(todoVM)` — an object of getters |
|
|
25
|
+
| it needs one derived value | `useLankaVM(todoVM, (s) => s.rows.length)` → `.current` |
|
|
26
|
+
| `derived`, `get`, a `$` prefix, a SvelteKit store | `toLankaSvelteVM(todoVM)` |
|
|
27
|
+
| outside a component — a handler, a module | `todoVM.getState()` |
|
|
28
|
+
| a component test | `renderWithLanka` from `@lankajs/svelte/testing` |
|
|
29
|
+
|
|
30
|
+
```svelte
|
|
31
|
+
<script lang="ts">
|
|
32
|
+
import { useLankaVM } from "@lankajs/svelte";
|
|
33
|
+
import { todoVM } from "./todoVM";
|
|
34
|
+
|
|
35
|
+
const state = useLankaVM(todoVM);
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
{#if state.isLoading}
|
|
39
|
+
<p>loading</p>
|
|
40
|
+
{:else}
|
|
41
|
+
<ul onclick={() => state.load()}>
|
|
42
|
+
{#each state.todos as todo (todo.id)}
|
|
43
|
+
<li>{todo.title}</li>
|
|
44
|
+
{/each}
|
|
45
|
+
</ul>
|
|
46
|
+
{/if}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
It answers **an object whose properties are getters** — Svelte 5's own shape, and
|
|
50
|
+
the one thing the shelf does not make uniform. A read registers with the
|
|
51
|
+
reactivity graph and with the access tracker in ONE access, so nothing needs a
|
|
52
|
+
`$`. `const` is right: the object never changes, its getters do.
|
|
53
|
+
|
|
54
|
+
**No compiler and no extra plugin.** `createSubscriber` from `svelte/reactivity`
|
|
55
|
+
is a runtime function, so this package is plain TypeScript.
|
|
56
|
+
|
|
57
|
+
## Selecting one value
|
|
58
|
+
|
|
59
|
+
```svelte
|
|
60
|
+
<script lang="ts">
|
|
61
|
+
const count = useLankaVM(todoVM, (state) => state.rows.length);
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<p>{count.current}</p>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`current` is Svelte's own convention for a reactive value, the way `MediaQuery`
|
|
68
|
+
and the rest of `svelte/reactivity` read. Any selector, including one answering a
|
|
69
|
+
number.
|
|
70
|
+
|
|
71
|
+
The reader wakes when the **selection** moves, compared with `Object.is` — so a
|
|
72
|
+
selector building a fresh object every call is a reader saying it depends on
|
|
73
|
+
everything. Pick the leaves instead.
|
|
74
|
+
|
|
75
|
+
## The store contract, when you want `$`
|
|
76
|
+
|
|
77
|
+
```svelte
|
|
78
|
+
<script lang="ts">
|
|
79
|
+
import { toLankaSvelteVM } from "@lankajs/svelte";
|
|
80
|
+
const todos = toLankaSvelteVM(todoVM);
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
{#each $todos.rows as row}<li>{row}</li>{/each}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`run` is called immediately and synchronously, so `$todos` is never `undefined`
|
|
87
|
+
on the first render, and `derived`, `get` and every other `svelte/store` helper
|
|
88
|
+
accept it. Each subscriber gets its own recording.
|
|
89
|
+
|
|
90
|
+
Reach for it when something else demands the contract — a `derived`, a `get`, a
|
|
91
|
+
codebase that has not moved to runes. `useLankaVM` is the default, because a
|
|
92
|
+
plain read is what every other binding on the shelf writes.
|
|
93
|
+
|
|
94
|
+
## What re-renders
|
|
95
|
+
|
|
96
|
+
Without a selector the view RECORDS which keys you read, and the next change
|
|
97
|
+
repaints only if one of those moved. With a selector, the selector decides and
|
|
98
|
+
tracking is bypassed.
|
|
99
|
+
|
|
100
|
+
> [!WARNING]
|
|
101
|
+
> **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
|
|
102
|
+
> inside a derived getter is invisible to it, so a change to that key repaints
|
|
103
|
+
> nothing and the screen freezes with no error. Set
|
|
104
|
+
> `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT read the
|
|
105
|
+
> underlying keys in the markup "for the side effect": that is dead code, and a
|
|
106
|
+
> refactor or a lint autofix removes it. In development the framework announces
|
|
107
|
+
> the mismatch by ViewModel and key name.
|
|
108
|
+
|
|
109
|
+
## Releasing the subscription
|
|
110
|
+
|
|
111
|
+
`createSubscriber` releases it when the last effect reading the view is
|
|
112
|
+
destroyed, which is every case inside a component. A read where there is no
|
|
113
|
+
effect at all — a module-level snapshot, a script, a test — has none, so the view
|
|
114
|
+
carries `stop()` and you own it.
|
|
115
|
+
|
|
116
|
+
## Testing
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { renderWithLanka } from "@lankajs/svelte/testing";
|
|
120
|
+
|
|
121
|
+
renderWithLanka(TodoScreen, {
|
|
122
|
+
fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Every call gets a fresh instance and disposes the previous one.
|
|
127
|
+
|
|
128
|
+
## A selector that builds an object
|
|
129
|
+
|
|
130
|
+
A selector answering a fresh object is never identical to its own last answer,
|
|
131
|
+
so the reader wakes for EVERY change in the ViewModel — including the keys the
|
|
132
|
+
selector exists to ignore. Hold it:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { createLankaShallowHold } from "lanka/viewmodel";
|
|
136
|
+
|
|
137
|
+
const hold = createLankaShallowHold<{ title: string }>();
|
|
138
|
+
const mission = useLankaVM(missionVM, (s) => hold({ title: s.title }));
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
One hold per reader, declared in the component script — never at module level and never
|
|
142
|
+
shared between two components. A selector answering a **primitive** needs none
|
|
143
|
+
of this. The comparison is one level deep: own keys, same count, `Object.is` on
|
|
144
|
+
each value, arrays included.
|
|
145
|
+
|
|
146
|
+
## Never do these
|
|
147
|
+
|
|
148
|
+
- **Never pass an object-building selector without a hold.** The reader then
|
|
149
|
+
wakes for every change in the ViewModel, selector or no selector.
|
|
150
|
+
- **Never destructure the view.** `const { rows } = state` reads the getter once
|
|
151
|
+
and the value stops tracking; keep reading through `state.rows`.
|
|
152
|
+
- **Never look for `.current` without a selector.** The plain call answers the
|
|
153
|
+
getters directly; `current` is the selected shape only.
|
|
154
|
+
- **Never build a fresh object in a selector.** `Object.is` then says it changed
|
|
155
|
+
every time, and the reader wakes on everything.
|
|
156
|
+
- **Never leave a read outside an effect unstopped.** A module-level or script
|
|
157
|
+
read has no effect to hang on, so nothing releases the subscription but you.
|
|
158
|
+
- **Never reach for `$` by default.** `toLankaSvelteVM` is for the store
|
|
159
|
+
contract; a plain read is the spelling the rest of the shelf shares.
|
|
160
|
+
|
|
161
|
+
## Symptom → cause
|
|
162
|
+
|
|
163
|
+
| What you see | What it is |
|
|
164
|
+
| ------------------------------------------------- | ----------------------------------------------- |
|
|
165
|
+
| a screen repainting for changes it never selected | an object selector with no hold |
|
|
166
|
+
| the first paint is right, nothing updates | the view was destructured |
|
|
167
|
+
| `undefined` from a selected read | `.current` missing |
|
|
168
|
+
| a reader waking on every change | a selector returning a fresh object |
|
|
169
|
+
| `derived` or `get` refuses the ViewModel | it wants the store contract — `toLankaSvelteVM` |
|
|
170
|
+
| a subscription that outlives the test | a read with no effect, `stop()` never called |
|
|
171
|
+
|
|
172
|
+
## More
|
|
173
|
+
|
|
174
|
+
`reference.md` — the full guide: the tracking rules, the store contract in
|
|
175
|
+
detail, and what this package deliberately is not.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
<!-- Generated from modules/bindings/svelte/GUIDE.md by scripts/skills.mjs. Edit the guide. -->
|
|
2
|
+
|
|
3
|
+
> **`@lankajs/svelte@0.1.0`** — this document describes that version.
|
|
4
|
+
>
|
|
5
|
+
> Install: `npm install @lankajs/svelte svelte zustand` (the peers are not optional; only npm adds a missing one for you).
|
|
6
|
+
>
|
|
7
|
+
> Complete code, compiled and run in CI: [modules/bindings/svelte/_playground/playground.test.ts](https://github.com/lankajs/lanka/blob/main/modules/bindings/svelte/_playground/playground.test.ts)
|
|
8
|
+
|
|
9
|
+
# @lankajs/svelte — user guide
|
|
10
|
+
|
|
11
|
+
How a Svelte component reads a lanka ViewModel.
|
|
12
|
+
|
|
13
|
+
## You will learn
|
|
14
|
+
|
|
15
|
+
- the one call this package publishes, and what it answers
|
|
16
|
+
- when a component re-renders and when it deliberately does not
|
|
17
|
+
- why a selector that builds an object needs a hold, and when it needs nothing
|
|
18
|
+
- what to do about a ViewModel that derives what the screen shows
|
|
19
|
+
- how to test a Svelte component with a live framework behind it
|
|
20
|
+
|
|
21
|
+
## When to reach for this
|
|
22
|
+
|
|
23
|
+
Reach for it the moment a Svelte component has to read a lanka ViewModel — that
|
|
24
|
+
is the whole job, and there is no other supported way to do it. Install this one
|
|
25
|
+
package and no other binding: the five are alternatives, not layers.
|
|
26
|
+
|
|
27
|
+
You do NOT need it to reach the rest of the framework. Gateways, scenarios and
|
|
28
|
+
the locator are plain calls with no view in them, and `viewModel.getState()`
|
|
29
|
+
works anywhere, including on a server.
|
|
30
|
+
|
|
31
|
+
> [!NOTE]
|
|
32
|
+
> Everything below is how this package is _meant_ to be used, not how it must
|
|
33
|
+
> be. The framework bends at the seams it publishes — see
|
|
34
|
+
> [ARCHITECTURE.md](https://github.com/lankajs/lanka/blob/main/ARCHITECTURE.md) for what is checked and what is
|
|
35
|
+
> merely advice.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @lankajs/svelte svelte zustand
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> [!IMPORTANT]
|
|
44
|
+
> `svelte` is already in your project; `zustand` is `lanka`'s own peer. No
|
|
45
|
+
> compiler plugin is needed — this package is plain TypeScript. npm adds a
|
|
46
|
+
> missing peer for you and pnpm does not, so the line names all of them.
|
|
47
|
+
|
|
48
|
+
## The one call
|
|
49
|
+
|
|
50
|
+
`useLankaVM` is a function. Every member of `modules/bindings/` publishes that same
|
|
51
|
+
name, so moving a screen from one framework to another rewrites the view and not
|
|
52
|
+
the vocabulary.
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<script lang="ts">
|
|
56
|
+
import { useLankaVM } from "@lankajs/svelte";
|
|
57
|
+
import { todoVM } from "./todoVM";
|
|
58
|
+
|
|
59
|
+
const state = useLankaVM(todoVM);
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
{#if state.isLoading}
|
|
63
|
+
<p>loading</p>
|
|
64
|
+
{:else}
|
|
65
|
+
<ul onclick={() => state.load()}>
|
|
66
|
+
{#each state.todos as todo (todo.id)}
|
|
67
|
+
<li>{todo.title}</li>
|
|
68
|
+
{/each}
|
|
69
|
+
</ul>
|
|
70
|
+
{/if}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
It answers **an object whose properties are getters** — the one thing this shelf does not make uniform,
|
|
74
|
+
because that is Svelte's own idea of reactivity and a binding that hid it
|
|
75
|
+
would be a second reactivity system fighting the first.
|
|
76
|
+
|
|
77
|
+
## Svelte's store contract, when you want `$`
|
|
78
|
+
|
|
79
|
+
`useLankaVM` answers an object of getters, which is Svelte 5's own shape: a read
|
|
80
|
+
registers with the reactivity graph and with the access tracker in one access,
|
|
81
|
+
and nothing needs a `$`.
|
|
82
|
+
|
|
83
|
+
The store contract is the other half of Svelte and has not gone anywhere —
|
|
84
|
+
`$page`, `derived`, `get`, and every codebase that has not moved to runes — so
|
|
85
|
+
this package publishes it:
|
|
86
|
+
|
|
87
|
+
```svelte
|
|
88
|
+
<script lang="ts">
|
|
89
|
+
import { toLankaSvelteVM } from "@lankajs/svelte";
|
|
90
|
+
const todos = toLankaSvelteVM(todosVM);
|
|
91
|
+
</script>
|
|
92
|
+
|
|
93
|
+
{#each $todos.rows as row}<li>{row}</li>{/each}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
It satisfies the contract properly: `run` is called immediately and
|
|
97
|
+
synchronously, so `$todos` is never `undefined` on the first render, and
|
|
98
|
+
`derived`, `get` and every other `svelte/store` helper accept it.
|
|
99
|
+
|
|
100
|
+
Each Svelte subscriber gets its own recording, because two readers of one
|
|
101
|
+
ViewModel read different keys and must be woken for different changes — the same
|
|
102
|
+
rule every binding on the shelf follows.
|
|
103
|
+
|
|
104
|
+
## Selecting one value
|
|
105
|
+
|
|
106
|
+
With a selector the call answers ONE value, under `current` — Svelte's own
|
|
107
|
+
convention for a reactive value, the way `MediaQuery` and the rest of
|
|
108
|
+
`svelte/reactivity` read:
|
|
109
|
+
|
|
110
|
+
```svelte
|
|
111
|
+
<script lang="ts">
|
|
112
|
+
const count = useLankaVM(todosVM, (state) => state.rows.length);
|
|
113
|
+
</script>
|
|
114
|
+
|
|
115
|
+
<p>{count.current}</p>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Any selector, including one answering a number: the shape that carried the
|
|
119
|
+
selection's own keys could not, and a member of this shelf narrowing the shared
|
|
120
|
+
name is what the conformance suite's selector scenes now refuse.
|
|
121
|
+
|
|
122
|
+
The reader wakes when the SELECTION moves, not when the state does — compared
|
|
123
|
+
with `Object.is`.
|
|
124
|
+
|
|
125
|
+
### A selector that builds its answer
|
|
126
|
+
|
|
127
|
+
That comparison is why a selector answering a fresh object wakes the reader for
|
|
128
|
+
**every** change in the ViewModel, including the keys it exists to ignore: the
|
|
129
|
+
object is new on every call, so it is never identical to the previous one.
|
|
130
|
+
`createLankaShallowHold` is the comparison that fixes it — it answers the
|
|
131
|
+
PREVIOUS object while nothing in the selection moved, one level deep over own
|
|
132
|
+
keys:
|
|
133
|
+
|
|
134
|
+
```svelte
|
|
135
|
+
<script lang="ts">
|
|
136
|
+
import { createLankaShallowHold } from "lanka/viewmodel";
|
|
137
|
+
import { useLankaVM } from "@lankajs/svelte";
|
|
138
|
+
|
|
139
|
+
const hold = createLankaShallowHold<{ title: string; status: string }>();
|
|
140
|
+
const mission = useLankaVM(missionVM, (state) =>
|
|
141
|
+
hold({ title: state.title, status: state.status }),
|
|
142
|
+
);
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<h1>{mission.current.title} — {mission.current.status}</h1>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
One hold per reader, created beside the read in the component — never at module
|
|
149
|
+
level and never shared between two of them, because the answer it holds belongs
|
|
150
|
+
to whoever selected it.
|
|
151
|
+
|
|
152
|
+
A selector answering a **primitive** needs none of this and was always free. A
|
|
153
|
+
selection with a **nested** object wants a selector that picks the leaves —
|
|
154
|
+
comparing deeper would mean walking a state of unknown size on every read, which
|
|
155
|
+
is the cost a selector was taken to avoid.
|
|
156
|
+
|
|
157
|
+
## What re-renders, and what does not
|
|
158
|
+
|
|
159
|
+
Without a selector you get a value that RECORDS which keys you read. The next
|
|
160
|
+
change re-renders only if one of those moved:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// reads `todos`; a change to `isLoading` alone repaints nothing
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
With a selector, the selector decides and tracking is bypassed:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
const count = useLankaVM(todoVM, (state) => state.todos.length);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
> [!WARNING]
|
|
173
|
+
> **The blind spot.** Tracking sees keys you read DIRECTLY. A key reached only
|
|
174
|
+
> inside a derived getter — an action calling `get()` — is invisible to it, so a
|
|
175
|
+
> change to that key re-renders nothing and the screen freezes with no error.
|
|
176
|
+
>
|
|
177
|
+
> Set `enableAccessTrackingOptimization: false` on such a ViewModel. Do NOT patch
|
|
178
|
+
> it in the view by reading the underlying keys "for the side effect": that is
|
|
179
|
+
> dead code, and a refactor or a lint autofix removes it.
|
|
180
|
+
>
|
|
181
|
+
> In development the framework announces the mismatch by ViewModel and key name.
|
|
182
|
+
|
|
183
|
+
## Releasing the subscription
|
|
184
|
+
|
|
185
|
+
`createSubscriber` releases the subscription when the last effect reading this
|
|
186
|
+
view is destroyed, which is every case inside a component. A read where there is
|
|
187
|
+
no effect at all — a module-level snapshot, a script — has none, so the view
|
|
188
|
+
carries `stop()` and you own it.
|
|
189
|
+
|
|
190
|
+
## No compiler
|
|
191
|
+
|
|
192
|
+
This package is plain TypeScript: `createSubscriber` from `svelte/reactivity` is
|
|
193
|
+
a runtime function, so nothing here needs the Svelte compiler and your build
|
|
194
|
+
needs no extra plugin.
|
|
195
|
+
|
|
196
|
+
## Which spelling, and when
|
|
197
|
+
|
|
198
|
+
`useLankaVM` is the default. A read registers with the reactivity graph and with
|
|
199
|
+
the access tracker in one access, it composes with `$state` and `$derived`, and
|
|
200
|
+
it is the plain read every other binding on the shelf writes — so a screen moves
|
|
201
|
+
between frameworks with the view rewritten and the vocabulary kept.
|
|
202
|
+
|
|
203
|
+
`toLankaSvelteVM` is for when something else demands the store contract: a
|
|
204
|
+
`derived`, a `get`, a helper from `svelte/store`, or a codebase that has not
|
|
205
|
+
moved to runes. Reaching for `$todoVM` by preference is the one case to weigh,
|
|
206
|
+
because it is the spelling no other framework has.
|
|
207
|
+
|
|
208
|
+
## Testing
|
|
209
|
+
|
|
210
|
+
`@lankajs/svelte/testing` renders a component with a bootstrapped framework, so a component
|
|
211
|
+
test needs no bootstrap preamble of its own:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { renderWithLanka } from "@lankajs/svelte/testing";
|
|
215
|
+
|
|
216
|
+
renderWithLanka(TodoScreen, {
|
|
217
|
+
fakes: { gateways: { TodoGateway: { list: () => Promise.resolve([]) } } },
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Every call gets a FRESH instance and disposes the previous one, so a test never
|
|
222
|
+
inherits its neighbour's subscriptions.
|
|
223
|
+
|
|
224
|
+
## What this package is not
|
|
225
|
+
|
|
226
|
+
It is a subscription and a render trigger, and nothing else. The recording of
|
|
227
|
+
which keys you read, the comparison that decides whether a change is worth a
|
|
228
|
+
render, and the blind-spot warning are all in `lanka` itself — which is why the
|
|
229
|
+
behaviour you see is the framework's rather than this package's reading of it,
|
|
230
|
+
and why `lankaViewBindingConformance` can hold every binding to one list.
|
|
231
|
+
|
|
232
|
+
If this package ever needs more than the ViewModel port gives it, the port has
|
|
233
|
+
the defect and the fix belongs in `lanka`, for every framework at once.
|
|
234
|
+
|
|
235
|
+
## Recap
|
|
236
|
+
|
|
237
|
+
- `useLankaVM(todoVM)` is the one call, and every binding publishes that name.
|
|
238
|
+
- It answers an object of getters, so reading `state.todos` registers with Svelte's graph and records the key in one access.
|
|
239
|
+
- A selected read answers one value under `.current` — Svelte's own convention, and the only shape that can carry a selection which is not an object.
|
|
240
|
+
- A key reached only through a derived getter is invisible to tracking: set `enableAccessTrackingOptimization: false` on that ViewModel.
|
|
241
|
+
- `toLankaSvelteVM` is there for the `$` spelling; the plain read is what the other four frameworks write.
|
|
242
|
+
- Inside a component the subscription is released for you; outside one, `stop()` is yours to call.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
Maintaining this package: [SKILL.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/svelte/SKILL.md) · What it is:
|
|
247
|
+
[README.md](https://github.com/lankajs/lanka/blob/main/modules/bindings/svelte/README.md) · Repository map: [../../../README.md](https://github.com/lankajs/lanka/blob/main/README.md)
|