@gyral/core 0.2.0 → 0.3.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/CHANGELOG.md +87 -0
- package/README.md +7 -5
- package/dist/children.d.ts +5 -2
- package/dist/children.js +8 -3
- package/dist/children.js.map +1 -1
- package/dist/command.js +6 -0
- package/dist/command.js.map +1 -1
- package/dist/compiler/ast.d.ts +21 -0
- package/dist/compiler/ast.js +145 -0
- package/dist/compiler/ast.js.map +1 -0
- package/dist/compiler/compile.d.ts +38 -0
- package/dist/compiler/compile.js +129 -0
- package/dist/compiler/compile.js.map +1 -0
- package/dist/compiler/hooks.d.ts +18 -0
- package/dist/compiler/hooks.js +186 -0
- package/dist/compiler/hooks.js.map +1 -0
- package/dist/compiler/parse5-check.d.ts +21 -0
- package/dist/compiler/parse5-check.js +55 -0
- package/dist/compiler/parse5-check.js.map +1 -0
- package/dist/compiler/scan.d.ts +31 -0
- package/dist/compiler/scan.js +152 -0
- package/dist/compiler/scan.js.map +1 -0
- package/dist/compiler/source.d.ts +34 -0
- package/dist/compiler/source.js +114 -0
- package/dist/compiler/source.js.map +1 -0
- package/dist/define.d.ts +2 -2
- package/dist/define.js +35 -327
- package/dist/define.js.map +1 -1
- package/dist/devtools-events.d.ts +5 -0
- package/dist/devtools-events.js.map +1 -1
- package/dist/devtools-off.d.ts +1 -0
- package/dist/devtools-off.js +1 -0
- package/dist/devtools-off.js.map +1 -1
- package/dist/devtools.d.ts +1 -0
- package/dist/devtools.js +3 -2
- package/dist/devtools.js.map +1 -1
- package/dist/drivers-scope.d.ts +1 -1
- package/dist/drivers-scope.js +1 -8
- package/dist/drivers-scope.js.map +1 -1
- package/dist/element-types.d.ts +5 -3
- package/dist/element-types.js.map +1 -1
- package/dist/element.d.ts +3 -0
- package/dist/element.js +269 -0
- package/dist/element.js.map +1 -0
- package/dist/eslint/imports.d.ts +24 -0
- package/dist/eslint/imports.js +78 -0
- package/dist/eslint/imports.js.map +1 -0
- package/dist/eslint/index.d.ts +15 -0
- package/dist/eslint/index.js +15 -0
- package/dist/eslint/index.js.map +1 -0
- package/dist/eslint/locate.d.ts +33 -0
- package/dist/eslint/locate.js +132 -0
- package/dist/eslint/locate.js.map +1 -0
- package/dist/eslint/row-purity-rule.d.ts +2 -0
- package/dist/eslint/row-purity-rule.js +103 -0
- package/dist/eslint/row-purity-rule.js.map +1 -0
- package/dist/eslint/template-rule.d.ts +5 -0
- package/dist/eslint/template-rule.js +66 -0
- package/dist/eslint/template-rule.js.map +1 -0
- package/dist/features.d.ts +39 -0
- package/dist/features.js +2 -0
- package/dist/features.js.map +1 -0
- package/dist/focus.d.ts +7 -12
- package/dist/focus.js +13 -10
- package/dist/focus.js.map +1 -1
- package/dist/hooks/invalid.d.ts +14 -0
- package/dist/hooks/invalid.js +72 -0
- package/dist/hooks/invalid.js.map +1 -0
- package/dist/hooks/labelled-by.d.ts +13 -0
- package/dist/hooks/labelled-by.js +51 -0
- package/dist/hooks/labelled-by.js.map +1 -0
- package/dist/host-model.d.ts +48 -0
- package/dist/host-model.js +155 -0
- package/dist/host-model.js.map +1 -0
- package/dist/hydration-client.d.ts +9 -0
- package/dist/hydration-client.js +35 -0
- package/dist/hydration-client.js.map +1 -0
- package/dist/hydration.d.ts +13 -19
- package/dist/hydration.js +45 -45
- package/dist/hydration.js.map +1 -1
- package/dist/index.d.ts +9 -17
- package/dist/index.js +9 -18
- package/dist/index.js.map +1 -1
- package/dist/intent.d.ts +31 -8
- package/dist/intent.js +82 -27
- package/dist/intent.js.map +1 -1
- package/dist/internal/interpreter.d.ts +7 -1
- package/dist/internal/interpreter.js +11 -4
- package/dist/internal/interpreter.js.map +1 -1
- package/dist/invokers-shim.d.ts +6 -0
- package/dist/invokers-shim.js +32 -0
- package/dist/invokers-shim.js.map +1 -0
- package/dist/invokers.d.ts +3 -5
- package/dist/invokers.js +11 -51
- package/dist/invokers.js.map +1 -1
- package/dist/islands.d.ts +0 -2
- package/dist/islands.js +4 -11
- package/dist/islands.js.map +1 -1
- package/dist/light-dom.d.ts +3 -52
- package/dist/light-dom.js +5 -100
- package/dist/light-dom.js.map +1 -1
- package/dist/prop.d.ts +66 -0
- package/dist/prop.js +22 -0
- package/dist/prop.js.map +1 -0
- package/dist/props.d.ts +52 -21
- package/dist/props.js +150 -34
- package/dist/props.js.map +1 -1
- package/dist/scheduler.d.ts +39 -0
- package/dist/scheduler.js +230 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/server-component.d.ts +10 -0
- package/dist/server-component.js +102 -0
- package/dist/server-component.js.map +1 -0
- package/dist/server-specs.d.ts +6 -0
- package/dist/server-specs.js +13 -0
- package/dist/server-specs.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.js +50 -0
- package/dist/server.js.map +1 -0
- package/dist/settled.d.ts +10 -0
- package/dist/settled.js +40 -0
- package/dist/settled.js.map +1 -0
- package/dist/states.d.ts +3 -3
- package/dist/states.js +4 -7
- package/dist/states.js.map +1 -1
- package/dist/store-binding.d.ts +6 -3
- package/dist/store-binding.js +5 -6
- package/dist/store-binding.js.map +1 -1
- package/dist/store-scope.d.ts +6 -7
- package/dist/store-scope.js +11 -26
- package/dist/store-scope.js.map +1 -1
- package/dist/store.d.ts +3 -0
- package/dist/store.js +9 -2
- package/dist/store.js.map +1 -1
- package/dist/stores-provider.d.ts +2 -6
- package/dist/stores-provider.js +21 -55
- package/dist/stores-provider.js.map +1 -1
- package/dist/transitions.d.ts +5 -6
- package/dist/transitions.js +11 -20
- package/dist/transitions.js.map +1 -1
- package/dist/types.d.ts +19 -23
- package/dist/types.js.map +1 -1
- package/dist/view/attributes.d.ts +6 -0
- package/dist/view/attributes.js +10 -0
- package/dist/view/attributes.js.map +1 -0
- package/dist/view/compiled.d.ts +1 -0
- package/dist/view/compiled.js +6 -0
- package/dist/view/compiled.js.map +1 -0
- package/dist/view/dev-off.d.ts +1 -0
- package/dist/view/dev-off.js +4 -0
- package/dist/view/dev-off.js.map +1 -0
- package/dist/view/dev.d.ts +1 -0
- package/dist/view/dev.js +5 -0
- package/dist/view/dev.js.map +1 -0
- package/dist/view/flags.d.ts +1 -0
- package/dist/view/flags.js +4 -0
- package/dist/view/flags.js.map +1 -0
- package/dist/view/index.d.ts +21 -0
- package/dist/view/index.js +21 -0
- package/dist/view/index.js.map +1 -0
- package/dist/view/normalize/check.d.ts +20 -0
- package/dist/view/normalize/check.js +78 -0
- package/dist/view/normalize/check.js.map +1 -0
- package/dist/view/normalize/emit.d.ts +13 -0
- package/dist/view/normalize/emit.js +136 -0
- package/dist/view/normalize/emit.js.map +1 -0
- package/dist/view/normalize/entities.d.ts +4 -0
- package/dist/view/normalize/entities.js +38 -0
- package/dist/view/normalize/entities.js.map +1 -0
- package/dist/view/normalize/errors.d.ts +9 -0
- package/dist/view/normalize/errors.js +19 -0
- package/dist/view/normalize/errors.js.map +1 -0
- package/dist/view/normalize/id.d.ts +7 -0
- package/dist/view/normalize/id.js +48 -0
- package/dist/view/normalize/id.js.map +1 -0
- package/dist/view/normalize/normalize.d.ts +15 -0
- package/dist/view/normalize/normalize.js +36 -0
- package/dist/view/normalize/normalize.js.map +1 -0
- package/dist/view/normalize/repairs.d.ts +24 -0
- package/dist/view/normalize/repairs.js +166 -0
- package/dist/view/normalize/repairs.js.map +1 -0
- package/dist/view/normalize/rules.d.ts +30 -0
- package/dist/view/normalize/rules.js +90 -0
- package/dist/view/normalize/rules.js.map +1 -0
- package/dist/view/normalize/shape.d.ts +5 -0
- package/dist/view/normalize/shape.js +38 -0
- package/dist/view/normalize/shape.js.map +1 -0
- package/dist/view/normalize/source.d.ts +13 -0
- package/dist/view/normalize/source.js +34 -0
- package/dist/view/normalize/source.js.map +1 -0
- package/dist/view/normalize/tag-states.d.ts +23 -0
- package/dist/view/normalize/tag-states.js +192 -0
- package/dist/view/normalize/tag-states.js.map +1 -0
- package/dist/view/normalize/tokenizer.d.ts +5 -0
- package/dist/view/normalize/tokenizer.js +233 -0
- package/dist/view/normalize/tokenizer.js.map +1 -0
- package/dist/view/normalize/tokens.d.ts +51 -0
- package/dist/view/normalize/tokens.js +24 -0
- package/dist/view/normalize/tokens.js.map +1 -0
- package/dist/view/normalize/tree.d.ts +59 -0
- package/dist/view/normalize/tree.js +190 -0
- package/dist/view/normalize/tree.js.map +1 -0
- package/dist/view/normalize/types.d.ts +126 -0
- package/dist/view/normalize/types.js +26 -0
- package/dist/view/normalize/types.js.map +1 -0
- package/dist/view/normalize/whitespace.d.ts +2 -0
- package/dist/{template-whitespace.js → view/normalize/whitespace.js} +24 -34
- package/dist/view/normalize/whitespace.js.map +1 -0
- package/dist/view/prepare-stub.d.ts +3 -0
- package/dist/view/prepare-stub.js +7 -0
- package/dist/view/prepare-stub.js.map +1 -0
- package/dist/view/prepare.d.ts +8 -0
- package/dist/view/prepare.js +57 -0
- package/dist/view/prepare.js.map +1 -0
- package/dist/view/registry.d.ts +63 -0
- package/dist/view/registry.js +19 -0
- package/dist/view/registry.js.map +1 -0
- package/dist/view/render/adopt-attr.d.ts +14 -0
- package/dist/view/render/adopt-attr.js +77 -0
- package/dist/view/render/adopt-attr.js.map +1 -0
- package/dist/view/render/adopt-plan.d.ts +10 -0
- package/dist/view/render/adopt-plan.js +26 -0
- package/dist/view/render/adopt-plan.js.map +1 -0
- package/dist/view/render/adopt.d.ts +3 -0
- package/dist/view/render/adopt.js +269 -0
- package/dist/view/render/adopt.js.map +1 -0
- package/dist/view/render/attr-parts.d.ts +61 -0
- package/dist/view/render/attr-parts.js +229 -0
- package/dist/view/render/attr-parts.js.map +1 -0
- package/dist/view/render/child-part.d.ts +51 -0
- package/dist/view/render/child-part.js +170 -0
- package/dist/view/render/child-part.js.map +1 -0
- package/dist/view/render/dev-check.d.ts +12 -0
- package/dist/view/render/dev-check.js +122 -0
- package/dist/view/render/dev-check.js.map +1 -0
- package/dist/view/render/hook-part.d.ts +3 -0
- package/dist/view/render/hook-part.js +20 -0
- package/dist/view/render/hook-part.js.map +1 -0
- package/dist/view/render/hooks.d.ts +40 -0
- package/dist/view/render/hooks.js +46 -0
- package/dist/view/render/hooks.js.map +1 -0
- package/dist/view/render/hydrate.d.ts +10 -0
- package/dist/view/render/hydrate.js +33 -0
- package/dist/view/render/hydrate.js.map +1 -0
- package/dist/view/render/instance.d.ts +33 -0
- package/dist/view/render/instance.js +107 -0
- package/dist/view/render/instance.js.map +1 -0
- package/dist/view/render/list.d.ts +20 -0
- package/dist/view/render/list.js +156 -0
- package/dist/view/render/list.js.map +1 -0
- package/dist/view/render/mismatch.d.ts +10 -0
- package/dist/view/render/mismatch.js +53 -0
- package/dist/view/render/mismatch.js.map +1 -0
- package/dist/view/render/nodes.d.ts +10 -0
- package/dist/view/render/nodes.js +37 -0
- package/dist/view/render/nodes.js.map +1 -0
- package/dist/view/render/place.d.ts +18 -0
- package/dist/view/render/place.js +69 -0
- package/dist/view/render/place.js.map +1 -0
- package/dist/view/render/plan.d.ts +26 -0
- package/dist/view/render/plan.js +124 -0
- package/dist/view/render/plan.js.map +1 -0
- package/dist/view/render/raw.d.ts +18 -0
- package/dist/view/render/raw.js +68 -0
- package/dist/view/render/raw.js.map +1 -0
- package/dist/view/render/render.d.ts +12 -0
- package/dist/view/render/render.js +60 -0
- package/dist/view/render/render.js.map +1 -0
- package/dist/view/render/reorder.d.ts +4 -0
- package/dist/view/render/reorder.js +101 -0
- package/dist/view/render/reorder.js.map +1 -0
- package/dist/view/render/rows.d.ts +23 -0
- package/dist/view/render/rows.js +95 -0
- package/dist/view/render/rows.js.map +1 -0
- package/dist/view/render/seen.d.ts +11 -0
- package/dist/view/render/seen.js +17 -0
- package/dist/view/render/seen.js.map +1 -0
- package/dist/view/render/values.d.ts +29 -0
- package/dist/view/render/values.js +13 -0
- package/dist/view/render/values.js.map +1 -0
- package/dist/view/render/warn.d.ts +14 -0
- package/dist/view/render/warn.js +69 -0
- package/dist/view/render/warn.js.map +1 -0
- package/dist/view/server/component.d.ts +9 -0
- package/dist/view/server/component.js +42 -0
- package/dist/view/server/component.js.map +1 -0
- package/dist/view/server/escape.d.ts +10 -0
- package/dist/view/server/escape.js +34 -0
- package/dist/view/server/escape.js.map +1 -0
- package/dist/view/server/index.d.ts +28 -0
- package/dist/view/server/index.js +72 -0
- package/dist/view/server/index.js.map +1 -0
- package/dist/view/server/sha256.d.ts +2 -0
- package/dist/view/server/sha256.js +75 -0
- package/dist/view/server/sha256.js.map +1 -0
- package/dist/view/server/tags.d.ts +57 -0
- package/dist/view/server/tags.js +121 -0
- package/dist/view/server/tags.js.map +1 -0
- package/dist/view/server/values.d.ts +16 -0
- package/dist/view/server/values.js +86 -0
- package/dist/view/server/values.js.map +1 -0
- package/dist/view/server/writer.d.ts +16 -0
- package/dist/view/server/writer.js +155 -0
- package/dist/view/server/writer.js.map +1 -0
- package/dist/view/styles.d.ts +25 -0
- package/dist/view/styles.js +58 -0
- package/dist/view/styles.js.map +1 -0
- package/dist/view/template-element.d.ts +3 -0
- package/dist/view/template-element.js +25 -0
- package/dist/view/template-element.js.map +1 -0
- package/dist/view/template.d.ts +21 -0
- package/dist/view/template.js +30 -0
- package/dist/view/template.js.map +1 -0
- package/dist/vite.d.ts +45 -6
- package/dist/vite.js +150 -25
- package/dist/vite.js.map +1 -1
- package/package.json +39 -4
- package/dist/accessible-name.d.ts +0 -19
- package/dist/accessible-name.js +0 -56
- package/dist/accessible-name.js.map +0 -1
- package/dist/element-directive.d.ts +0 -20
- package/dist/element-directive.js +0 -31
- package/dist/element-directive.js.map +0 -1
- package/dist/invalid.d.ts +0 -21
- package/dist/invalid.js +0 -70
- package/dist/invalid.js.map +0 -1
- package/dist/lit-html-check.d.ts +0 -6
- package/dist/lit-html-check.js +0 -48
- package/dist/lit-html-check.js.map +0 -1
- package/dist/live-boolean.d.ts +0 -22
- package/dist/live-boolean.js +0 -34
- package/dist/live-boolean.js.map +0 -1
- package/dist/styles.d.ts +0 -12
- package/dist/styles.js +0 -11
- package/dist/styles.js.map +0 -1
- package/dist/template-whitespace.d.ts +0 -7
- package/dist/template-whitespace.js.map +0 -1
- package/dist/templates.d.ts +0 -5
- package/dist/templates.js +0 -15
- package/dist/templates.js.map +0 -1
- package/dist/textarea.d.ts +0 -26
- package/dist/textarea.js +0 -85
- package/dist/textarea.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/eslint/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAUlD,MAAM,KAAK,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,CAAC;AAC3E,MAAM,OAAO,GAAmC,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC;AAEpE,MAAM,MAAM,GAAgB,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAErF,8FAA8F;AAC9F,4EAA4E;AAC5E,OAAO,CAAC,WAAW,GAAG;IACpB,IAAI,EAAE,mBAAmB;IACzB,OAAO,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;IAC1B,KAAK,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,uBAAuB,EAAE,OAAO,EAAE;CACvE,CAAC;AAEF,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC","sourcesContent":["// @gyral/core/eslint: the template rules (view/09-template-rules.md) in the editor, as an\n// ESLint flat-config plugin. ESLint is an optional peer dependency; nothing here reaches a\n// browser or server bundle.\n//\n// import gyral from '@gyral/core/eslint';\n// export default [gyral.configs.recommended];\nimport type { ESLint, Linter } from 'eslint';\nimport { rowPurityRule } from './row-purity-rule.js';\nimport { templateRule } from './template-rule.js';\n\nexport interface GyralPlugin extends ESLint.Plugin {\n readonly rules: {\n readonly template: typeof templateRule;\n readonly 'each-row-purity': typeof rowPurityRule;\n };\n readonly configs: { readonly recommended: Linter.Config };\n}\n\nconst rules = { template: templateRule, 'each-row-purity': rowPurityRule };\nconst configs: { recommended: Linter.Config } = { recommended: {} };\n\nconst plugin: GyralPlugin = { meta: { name: '@gyral/core/eslint' }, rules, configs };\n\n// The config names the plugin object itself, so it can be combined with configs that register\n// `gyral` too (ESLint rejects two different objects under one plugin name).\nconfigs.recommended = {\n name: 'gyral/recommended',\n plugins: { gyral: plugin },\n rules: { 'gyral/template': 'error', 'gyral/each-row-purity': 'error' },\n};\n\nexport default plugin;\nexport { rowPurityRule, templateRule };\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** One quasi of a template literal: where its text starts in the file, and its offset map. */
|
|
2
|
+
export interface Quasi {
|
|
3
|
+
/** File offset of the quasi's first character (after the backtick or `}`). */
|
|
4
|
+
readonly start: number;
|
|
5
|
+
/** File offset where the quasi's text ends (at the closing backtick or `${`). */
|
|
6
|
+
readonly end: number;
|
|
7
|
+
/** For each cooked offset (and one past the end), the offset in the source text. */
|
|
8
|
+
readonly map: readonly number[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Maps each offset of the cooked string to the source text it came from. Returns undefined when
|
|
12
|
+
* decoding `text` doesn't give `cooked` (the caller then points at the quasi's start).
|
|
13
|
+
*/
|
|
14
|
+
export declare function cookedMap(text: string, cooked: string): number[] | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Maps each offset of `minified` (and one past its end) to `original`. The minifier only
|
|
17
|
+
* collapses a whitespace run to one space or drops it, and copies everything else.
|
|
18
|
+
*/
|
|
19
|
+
export declare function minifiedMap(original: string, minified: string): number[];
|
|
20
|
+
/** A source range, as file offsets. */
|
|
21
|
+
export interface Span {
|
|
22
|
+
readonly start: number;
|
|
23
|
+
readonly end: number;
|
|
24
|
+
}
|
|
25
|
+
/** A position in the normalizer's minified strings: string index, offset (view/normalize/check.ts). */
|
|
26
|
+
type At = readonly [index: number, offset: number];
|
|
27
|
+
/**
|
|
28
|
+
* The range to report for a template's first error: the token the tree builder was handling
|
|
29
|
+
* (a tag, an end tag, text) when it has one; the whole `${…}` when the error is at a hole
|
|
30
|
+
* (including `<${…}>`, where the tag name would be); else the character the tokenizer stopped at.
|
|
31
|
+
*/
|
|
32
|
+
export declare function errorSpan(quasis: readonly Quasi[], originals: readonly string[], minified: readonly string[], at: At, from: At | undefined): Span;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// Where a template rule error is in the source (@gyral/core/eslint, view/09-template-rules.md).
|
|
2
|
+
// The normalizer reports the string and offset it stopped at, in the whitespace-minified cooked
|
|
3
|
+
// strings. Three maps lead back to the file: minified → cooked (whitespace runs the minifier
|
|
4
|
+
// collapsed or dropped), cooked → source text (escapes, line continuations and CRLF, which the
|
|
5
|
+
// cooked string spells differently), and the quasi's place in the file.
|
|
6
|
+
const SIMPLE = {
|
|
7
|
+
n: '\n',
|
|
8
|
+
t: '\t',
|
|
9
|
+
r: '\r',
|
|
10
|
+
b: '\b',
|
|
11
|
+
f: '\f',
|
|
12
|
+
v: '\v',
|
|
13
|
+
};
|
|
14
|
+
const LINE_END = /[\n\r\u2028\u2029]/;
|
|
15
|
+
/** The length of the escape sequence at `k` (after its backslash) and the text it cooks to. */
|
|
16
|
+
function escape(text, k) {
|
|
17
|
+
const c = text.charAt(k);
|
|
18
|
+
const simple = SIMPLE[c];
|
|
19
|
+
if (simple !== undefined)
|
|
20
|
+
return { length: 1, value: simple };
|
|
21
|
+
if (c === '\r')
|
|
22
|
+
return { length: text.charAt(k + 1) === '\n' ? 2 : 1, value: '' };
|
|
23
|
+
if (LINE_END.test(c))
|
|
24
|
+
return { length: 1, value: '' };
|
|
25
|
+
if (c === '0' && !/[0-9]/.test(text.charAt(k + 1)))
|
|
26
|
+
return { length: 1, value: '\0' };
|
|
27
|
+
const hex = c === 'x'
|
|
28
|
+
? /^x([0-9a-fA-F]{2})/.exec(text.slice(k))
|
|
29
|
+
: c === 'u'
|
|
30
|
+
? /^u(?:([0-9a-fA-F]{4})|\{([0-9a-fA-F]+)\})/.exec(text.slice(k))
|
|
31
|
+
: null;
|
|
32
|
+
if (hex !== null) {
|
|
33
|
+
const code = Number.parseInt(hex[1] ?? hex[2] ?? '0', 16);
|
|
34
|
+
return { length: hex[0].length, value: String.fromCodePoint(code) };
|
|
35
|
+
}
|
|
36
|
+
return { length: c.length, value: c };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Maps each offset of the cooked string to the source text it came from. Returns undefined when
|
|
40
|
+
* decoding `text` doesn't give `cooked` (the caller then points at the quasi's start).
|
|
41
|
+
*/
|
|
42
|
+
export function cookedMap(text, cooked) {
|
|
43
|
+
const map = [];
|
|
44
|
+
let out = '';
|
|
45
|
+
let k = 0;
|
|
46
|
+
while (k < text.length) {
|
|
47
|
+
const c = text.charAt(k);
|
|
48
|
+
let length = 1;
|
|
49
|
+
let value = c;
|
|
50
|
+
if (c === '\\') {
|
|
51
|
+
const e = escape(text, k + 1);
|
|
52
|
+
length = 1 + e.length;
|
|
53
|
+
value = e.value;
|
|
54
|
+
}
|
|
55
|
+
else if (c === '\r') {
|
|
56
|
+
length = text.charAt(k + 1) === '\n' ? 2 : 1;
|
|
57
|
+
value = '\n';
|
|
58
|
+
}
|
|
59
|
+
for (let n = 0; n < value.length; n++)
|
|
60
|
+
map.push(k);
|
|
61
|
+
out += value;
|
|
62
|
+
k += length;
|
|
63
|
+
}
|
|
64
|
+
map.push(text.length);
|
|
65
|
+
return out === cooked ? map : undefined;
|
|
66
|
+
}
|
|
67
|
+
const isWs = (c) => c === ' ' || c === '\t' || c === '\n' || c === '\r' || c === '\f';
|
|
68
|
+
/**
|
|
69
|
+
* Maps each offset of `minified` (and one past its end) to `original`. The minifier only
|
|
70
|
+
* collapses a whitespace run to one space or drops it, and copies everything else.
|
|
71
|
+
*/
|
|
72
|
+
export function minifiedMap(original, minified) {
|
|
73
|
+
const map = [];
|
|
74
|
+
let a = 0;
|
|
75
|
+
for (let b = 0; b < minified.length; b++) {
|
|
76
|
+
const c = minified.charAt(b);
|
|
77
|
+
// A dropped run: skip it.
|
|
78
|
+
while (c !== ' ' && isWs(original.charAt(a)) && original.charAt(a) !== c)
|
|
79
|
+
a++;
|
|
80
|
+
map.push(a);
|
|
81
|
+
// A run collapsed to this space (one that starts with a space is copied char by char,
|
|
82
|
+
// and its rest skipped as dropped before the next character).
|
|
83
|
+
if (c === ' ' && original.charAt(a) !== ' ' && isWs(original.charAt(a))) {
|
|
84
|
+
while (isWs(original.charAt(a)))
|
|
85
|
+
a++;
|
|
86
|
+
}
|
|
87
|
+
else if (a < original.length)
|
|
88
|
+
a++;
|
|
89
|
+
}
|
|
90
|
+
map.push(original.length);
|
|
91
|
+
return map;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The range to report for a template's first error: the token the tree builder was handling
|
|
95
|
+
* (a tag, an end tag, text) when it has one; the whole `${…}` when the error is at a hole
|
|
96
|
+
* (including `<${…}>`, where the tag name would be); else the character the tokenizer stopped at.
|
|
97
|
+
*/
|
|
98
|
+
export function errorSpan(quasis, originals, minified, at, from) {
|
|
99
|
+
const maps = minified.map((m, i) => minifiedMap(originals[i] ?? '', m));
|
|
100
|
+
/** File offset where minified character `off` of string `i` starts. */
|
|
101
|
+
const startOf = (i, off) => {
|
|
102
|
+
const q = quasis[i];
|
|
103
|
+
return q === undefined ? 0 : q.start + (q.map[maps[i]?.[off] ?? 0] ?? 0);
|
|
104
|
+
};
|
|
105
|
+
/** File offset just after minified character `off - 1` of string `i`. */
|
|
106
|
+
const endOf = (i, off) => {
|
|
107
|
+
const q = quasis[i];
|
|
108
|
+
if (q === undefined)
|
|
109
|
+
return 0;
|
|
110
|
+
if (off <= 0)
|
|
111
|
+
return q.start;
|
|
112
|
+
return q.start + (q.map[(maps[i]?.[off - 1] ?? 0) + 1] ?? 0);
|
|
113
|
+
};
|
|
114
|
+
const [index, offset] = at;
|
|
115
|
+
const s = minified[index] ?? '';
|
|
116
|
+
const quasi = quasis[index];
|
|
117
|
+
const next = quasis[index + 1];
|
|
118
|
+
if (quasi === undefined)
|
|
119
|
+
return { start: 0, end: 0 };
|
|
120
|
+
if (from !== undefined && (from[0] < index || from[1] < offset)) {
|
|
121
|
+
return { start: startOf(from[0], from[1]), end: endOf(index, offset) };
|
|
122
|
+
}
|
|
123
|
+
if (next !== undefined && /^(<\/?)?$/.test(s.slice(offset))) {
|
|
124
|
+
return { start: offset < s.length ? startOf(index, offset) : quasi.end, end: next.start };
|
|
125
|
+
}
|
|
126
|
+
if (offset < s.length)
|
|
127
|
+
return { start: startOf(index, offset), end: endOf(index, offset + 1) };
|
|
128
|
+
return s.length > 0
|
|
129
|
+
? { start: startOf(index, s.length - 1), end: endOf(index, s.length) }
|
|
130
|
+
: { start: quasi.end, end: quasi.end + 1 };
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=locate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locate.js","sourceRoot":"","sources":["../../src/eslint/locate.ts"],"names":[],"mappings":"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","sourcesContent":["// Where a template rule error is in the source (@gyral/core/eslint, view/09-template-rules.md).\n// The normalizer reports the string and offset it stopped at, in the whitespace-minified cooked\n// strings. Three maps lead back to the file: minified → cooked (whitespace runs the minifier\n// collapsed or dropped), cooked → source text (escapes, line continuations and CRLF, which the\n// cooked string spells differently), and the quasi's place in the file.\n\n/** One quasi of a template literal: where its text starts in the file, and its offset map. */\nexport interface Quasi {\n /** File offset of the quasi's first character (after the backtick or `}`). */\n readonly start: number;\n /** File offset where the quasi's text ends (at the closing backtick or `${`). */\n readonly end: number;\n /** For each cooked offset (and one past the end), the offset in the source text. */\n readonly map: readonly number[];\n}\n\nconst SIMPLE: Readonly<Record<string, string>> = {\n n: '\\n',\n t: '\\t',\n r: '\\r',\n b: '\\b',\n f: '\\f',\n v: '\\v',\n};\nconst LINE_END = /[\\n\\r\\u2028\\u2029]/;\n\n/** The length of the escape sequence at `k` (after its backslash) and the text it cooks to. */\nfunction escape(text: string, k: number): { length: number; value: string } {\n const c = text.charAt(k);\n const simple = SIMPLE[c];\n if (simple !== undefined) return { length: 1, value: simple };\n if (c === '\\r') return { length: text.charAt(k + 1) === '\\n' ? 2 : 1, value: '' };\n if (LINE_END.test(c)) return { length: 1, value: '' };\n if (c === '0' && !/[0-9]/.test(text.charAt(k + 1))) return { length: 1, value: '\\0' };\n const hex =\n c === 'x'\n ? /^x([0-9a-fA-F]{2})/.exec(text.slice(k))\n : c === 'u'\n ? /^u(?:([0-9a-fA-F]{4})|\\{([0-9a-fA-F]+)\\})/.exec(text.slice(k))\n : null;\n if (hex !== null) {\n const code = Number.parseInt(hex[1] ?? hex[2] ?? '0', 16);\n return { length: hex[0].length, value: String.fromCodePoint(code) };\n }\n return { length: c.length, value: c };\n}\n\n/**\n * Maps each offset of the cooked string to the source text it came from. Returns undefined when\n * decoding `text` doesn't give `cooked` (the caller then points at the quasi's start).\n */\nexport function cookedMap(text: string, cooked: string): number[] | undefined {\n const map: number[] = [];\n let out = '';\n let k = 0;\n while (k < text.length) {\n const c = text.charAt(k);\n let length = 1;\n let value = c;\n if (c === '\\\\') {\n const e = escape(text, k + 1);\n length = 1 + e.length;\n value = e.value;\n } else if (c === '\\r') {\n length = text.charAt(k + 1) === '\\n' ? 2 : 1;\n value = '\\n';\n }\n for (let n = 0; n < value.length; n++) map.push(k);\n out += value;\n k += length;\n }\n map.push(text.length);\n return out === cooked ? map : undefined;\n}\n\nconst isWs = (c: string): boolean =>\n c === ' ' || c === '\\t' || c === '\\n' || c === '\\r' || c === '\\f';\n\n/**\n * Maps each offset of `minified` (and one past its end) to `original`. The minifier only\n * collapses a whitespace run to one space or drops it, and copies everything else.\n */\nexport function minifiedMap(original: string, minified: string): number[] {\n const map: number[] = [];\n let a = 0;\n for (let b = 0; b < minified.length; b++) {\n const c = minified.charAt(b);\n // A dropped run: skip it.\n while (c !== ' ' && isWs(original.charAt(a)) && original.charAt(a) !== c) a++;\n map.push(a);\n // A run collapsed to this space (one that starts with a space is copied char by char,\n // and its rest skipped as dropped before the next character).\n if (c === ' ' && original.charAt(a) !== ' ' && isWs(original.charAt(a))) {\n while (isWs(original.charAt(a))) a++;\n } else if (a < original.length) a++;\n }\n map.push(original.length);\n return map;\n}\n\n/** A source range, as file offsets. */\nexport interface Span {\n readonly start: number;\n readonly end: number;\n}\n\n/** A position in the normalizer's minified strings: string index, offset (view/normalize/check.ts). */\ntype At = readonly [index: number, offset: number];\n\n/**\n * The range to report for a template's first error: the token the tree builder was handling\n * (a tag, an end tag, text) when it has one; the whole `${…}` when the error is at a hole\n * (including `<${…}>`, where the tag name would be); else the character the tokenizer stopped at.\n */\nexport function errorSpan(\n quasis: readonly Quasi[],\n originals: readonly string[],\n minified: readonly string[],\n at: At,\n from: At | undefined,\n): Span {\n const maps = minified.map((m, i) => minifiedMap(originals[i] ?? '', m));\n /** File offset where minified character `off` of string `i` starts. */\n const startOf = (i: number, off: number): number => {\n const q = quasis[i];\n return q === undefined ? 0 : q.start + (q.map[maps[i]?.[off] ?? 0] ?? 0);\n };\n /** File offset just after minified character `off - 1` of string `i`. */\n const endOf = (i: number, off: number): number => {\n const q = quasis[i];\n if (q === undefined) return 0;\n if (off <= 0) return q.start;\n return q.start + (q.map[(maps[i]?.[off - 1] ?? 0) + 1] ?? 0);\n };\n const [index, offset] = at;\n const s = minified[index] ?? '';\n const quasi = quasis[index];\n const next = quasis[index + 1];\n if (quasi === undefined) return { start: 0, end: 0 };\n if (from !== undefined && (from[0] < index || from[1] < offset)) {\n return { start: startOf(from[0], from[1]), end: endOf(index, offset) };\n }\n if (next !== undefined && /^(<\\/?)?$/.test(s.slice(offset))) {\n return { start: offset < s.length ? startOf(index, offset) : quasi.end, end: next.start };\n }\n if (offset < s.length) return { start: startOf(index, offset), end: endOf(index, offset + 1) };\n return s.length > 0\n ? { start: startOf(index, s.length - 1), end: endOf(index, s.length) }\n : { start: quasi.end, end: quasi.end + 1 };\n}\n"]}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { findVariable, isGyral, OPTIONS_SCHEMA } from './imports.js';
|
|
2
|
+
const isFn = (node) => node?.type === 'ArrowFunctionExpression' ||
|
|
3
|
+
node?.type === 'FunctionExpression' ||
|
|
4
|
+
node?.type === 'FunctionDeclaration';
|
|
5
|
+
/** The function a binding holds when that is static: `function F`, or `const F = (…) => …`. */
|
|
6
|
+
function boundFunction(variable) {
|
|
7
|
+
const def = variable?.defs.length === 1 ? variable.defs[0] : undefined;
|
|
8
|
+
if (def?.type === 'FunctionName')
|
|
9
|
+
return def.node;
|
|
10
|
+
if (def?.type !== 'Variable' || def.parent.kind !== 'const')
|
|
11
|
+
return undefined;
|
|
12
|
+
const init = def.node.init;
|
|
13
|
+
return isFn(init) ? init : undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The function a `row` argument is: inline, or a name bound to one. Anything else (a call, a
|
|
17
|
+
* parameter, a `let`) can't be followed statically; the development check (view/03) covers it.
|
|
18
|
+
*/
|
|
19
|
+
function rowFunction(context, row) {
|
|
20
|
+
if (isFn(row))
|
|
21
|
+
return row;
|
|
22
|
+
if (row.type !== 'Identifier')
|
|
23
|
+
return undefined;
|
|
24
|
+
return boundFunction(findVariable(context.sourceCode.getScope(row), row.name));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The references in `fn` (nested functions included) to bindings of enclosing functions. A
|
|
28
|
+
* helper beside the row (`const Cell = (…) => …` in the same function) is followed instead: it
|
|
29
|
+
* is fine when it reads nothing from an enclosing function itself. `seen` stops recursion.
|
|
30
|
+
*/
|
|
31
|
+
function enclosingReads(context, fn, seen) {
|
|
32
|
+
seen.add(fn);
|
|
33
|
+
return context.sourceCode.getScope(fn).through.filter((ref) => {
|
|
34
|
+
if ('isValueReference' in ref && ref.isValueReference === false)
|
|
35
|
+
return false; // a type
|
|
36
|
+
const variable = ref.resolved;
|
|
37
|
+
if (variable === null)
|
|
38
|
+
return false; // a global
|
|
39
|
+
const type = variable.scope.variableScope.type;
|
|
40
|
+
if (type === 'module' || type === 'global')
|
|
41
|
+
return false;
|
|
42
|
+
const helper = boundFunction(variable);
|
|
43
|
+
if (helper === undefined)
|
|
44
|
+
return true;
|
|
45
|
+
return !seen.has(helper) && enclosingReads(context, helper, seen).length > 0;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** `s`, or `s.selected` / `s.a.b` when the reference starts a property chain, and its node. */
|
|
49
|
+
function readOf(id) {
|
|
50
|
+
let name = id.type === 'Identifier' ? id.name : '';
|
|
51
|
+
let node = id;
|
|
52
|
+
for (let p = node.parent; p?.type === 'MemberExpression'; p = p.parent) {
|
|
53
|
+
if (p.object !== node || p.computed || p.property.type !== 'Identifier')
|
|
54
|
+
break;
|
|
55
|
+
name += `.${p.property.name}`;
|
|
56
|
+
node = p;
|
|
57
|
+
}
|
|
58
|
+
return { name, node };
|
|
59
|
+
}
|
|
60
|
+
const isMissing = (key) => (key.type === 'Identifier' && key.name === 'undefined') ||
|
|
61
|
+
(key.type === 'Literal' && key.value === null);
|
|
62
|
+
export const rowPurityRule = {
|
|
63
|
+
meta: {
|
|
64
|
+
type: 'problem',
|
|
65
|
+
docs: {
|
|
66
|
+
description: "each()'s row reads only its item, its picked value, module-level bindings and " +
|
|
67
|
+
'imports; each() has a key function (view/03-lists.md, rules 8 and 9).',
|
|
68
|
+
recommended: true,
|
|
69
|
+
url: 'https://github.com/gyraljs/gyral/blob/main/docs/design-docs/view/03-lists.md',
|
|
70
|
+
},
|
|
71
|
+
schema: OPTIONS_SCHEMA,
|
|
72
|
+
messages: {
|
|
73
|
+
reads: '`row` reads `{{name}}`; return it from `pick` and take it as the second argument ' +
|
|
74
|
+
'(view/03-lists.md).',
|
|
75
|
+
key: 'each() needs a key function as its second argument: each(items, (x) => x.id, Row) ' +
|
|
76
|
+
'(docs/design-docs/view/03-lists.md "Keys", rule 9).',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
create(context) {
|
|
80
|
+
const reported = new Set();
|
|
81
|
+
return {
|
|
82
|
+
CallExpression(node) {
|
|
83
|
+
if (!isGyral(context, node.callee, 'each'))
|
|
84
|
+
return;
|
|
85
|
+
const [, key, row] = node.arguments;
|
|
86
|
+
if (key === undefined || row === undefined || isMissing(key)) {
|
|
87
|
+
context.report({ node, messageId: 'key' });
|
|
88
|
+
}
|
|
89
|
+
const fn = row === undefined ? undefined : rowFunction(context, row);
|
|
90
|
+
if (fn === undefined)
|
|
91
|
+
return;
|
|
92
|
+
for (const ref of enclosingReads(context, fn, new Set())) {
|
|
93
|
+
const { name, node: read } = readOf(ref.identifier);
|
|
94
|
+
if (reported.has(read))
|
|
95
|
+
continue; // one row passed to several each() calls
|
|
96
|
+
reported.add(read);
|
|
97
|
+
context.report({ node: read, messageId: 'reads', data: { name } });
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=row-purity-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"row-purity-rule.js","sourceRoot":"","sources":["../../src/eslint/row-purity-rule.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAA0B,MAAM,cAAc,CAAC;AAK7F,MAAM,IAAI,GAAG,CAAC,IAA6B,EAAc,EAAE,CACzD,IAAI,EAAE,IAAI,KAAK,yBAAyB;IACxC,IAAI,EAAE,IAAI,KAAK,oBAAoB;IACnC,IAAI,EAAE,IAAI,KAAK,qBAAqB,CAAC;AAEvC,+FAA+F;AAC/F,SAAS,aAAa,CAAC,QAA2C;IAChE,MAAM,GAAG,GAAG,QAAQ,EAAE,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvE,IAAI,GAAG,EAAE,IAAI,KAAK,cAAc;QAAE,OAAO,GAAG,CAAC,IAAU,CAAC;IACxD,IAAI,GAAG,EAAE,IAAI,KAAK,UAAU,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,SAAS,CAAC;IAC9E,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAA+B,CAAC;IACtD,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,OAAyB,EAAE,GAAS;IACvD,IAAI,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IAC1B,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,SAAS,CAAC;IAChD,OAAO,aAAa,CAAC,YAAY,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;AACjF,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,OAAyB,EAAE,EAAM,EAAE,IAAa;IACtE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACb,OAAO,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE;QAC5D,IAAI,kBAAkB,IAAI,GAAG,IAAI,GAAG,CAAC,gBAAgB,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC,CAAC,SAAS;QACxF,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC9B,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC,CAAC,WAAW;QAChD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC;QAC/C,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACzD,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACtC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/E,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+FAA+F;AAC/F,SAAS,MAAM,CAAC,EAAQ;IACtB,IAAI,IAAI,GAAG,EAAE,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACnD,IAAI,IAAI,GAAS,EAAE,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,KAAK,kBAAkB,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;QACvE,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,YAAY;YAAE,MAAM;QAC/E,IAAI,IAAI,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,GAAG,CAAC,CAAC;IACX,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,GAAS,EAAW,EAAE,CACvC,CAAC,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,CAAC;IACvD,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;AAEjD,MAAM,CAAC,MAAM,aAAa,GAAoB;IAC5C,IAAI,EAAE;QACJ,IAAI,EAAE,SAAS;QACf,IAAI,EAAE;YACJ,WAAW,EACT,gFAAgF;gBAChF,uEAAuE;YACzE,WAAW,EAAE,IAAI;YACjB,GAAG,EAAE,8EAA8E;SACpF;QACD,MAAM,EAAE,cAAc;QACtB,QAAQ,EAAE;YACR,KAAK,EACH,mFAAmF;gBACnF,qBAAqB;YACvB,GAAG,EACD,oFAAoF;gBACpF,qDAAqD;SACxD;KACF;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAQ,CAAC;QACjC,OAAO;YACL,cAAc,CAAC,IAAI;gBACjB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,MAAc,EAAE,MAAM,CAAC;oBAAE,OAAO;gBAC3D,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,SAAmB,CAAC;gBAC9C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC7D,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC7C,CAAC;gBACD,MAAM,EAAE,GAAG,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;gBACrE,IAAI,EAAE,KAAK,SAAS;oBAAE,OAAO;gBAC7B,KAAK,MAAM,GAAG,IAAI,cAAc,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,GAAG,EAAE,CAAC,EAAE,CAAC;oBACzD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,UAAkB,CAAC,CAAC;oBAC5D,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;wBAAE,SAAS,CAAC,yCAAyC;oBAC3E,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBACnB,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;gBACrE,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["// gyral/each-row-purity: rules 8 and 9 of view/09-template-rules.md (view/03-lists.md \"Rows must\n// be pure\", \"Keys\"). A skipped row is only correct when its output depends on nothing but\n// (item, picked), so `each`'s row may read only its own parameters and locals, module-level\n// bindings, imports and globals; anything from an enclosing function (the view's `s`, `i`, `ctx`,\n// its locals) must come through `pick`. Helper functions declared beside the row are followed\n// (they may read only the same). And every `each` needs a key function.\nimport type { Rule, Scope } from 'eslint';\nimport { findVariable, isGyral, OPTIONS_SCHEMA, type Node, type NodeOf } from './imports.js';\n\ntype Fn =\n NodeOf<'ArrowFunctionExpression'> | NodeOf<'FunctionExpression'> | NodeOf<'FunctionDeclaration'>;\n\nconst isFn = (node: Node | null | undefined): node is Fn =>\n node?.type === 'ArrowFunctionExpression' ||\n node?.type === 'FunctionExpression' ||\n node?.type === 'FunctionDeclaration';\n\n/** The function a binding holds when that is static: `function F`, or `const F = (…) => …`. */\nfunction boundFunction(variable: Scope.Variable | null | undefined): Fn | undefined {\n const def = variable?.defs.length === 1 ? variable.defs[0] : undefined;\n if (def?.type === 'FunctionName') return def.node as Fn;\n if (def?.type !== 'Variable' || def.parent.kind !== 'const') return undefined;\n const init = def.node.init as Node | null | undefined;\n return isFn(init) ? init : undefined;\n}\n\n/**\n * The function a `row` argument is: inline, or a name bound to one. Anything else (a call, a\n * parameter, a `let`) can't be followed statically; the development check (view/03) covers it.\n */\nfunction rowFunction(context: Rule.RuleContext, row: Node): Fn | undefined {\n if (isFn(row)) return row;\n if (row.type !== 'Identifier') return undefined;\n return boundFunction(findVariable(context.sourceCode.getScope(row), row.name));\n}\n\n/**\n * The references in `fn` (nested functions included) to bindings of enclosing functions. A\n * helper beside the row (`const Cell = (…) => …` in the same function) is followed instead: it\n * is fine when it reads nothing from an enclosing function itself. `seen` stops recursion.\n */\nfunction enclosingReads(context: Rule.RuleContext, fn: Fn, seen: Set<Fn>): Scope.Reference[] {\n seen.add(fn);\n return context.sourceCode.getScope(fn).through.filter((ref) => {\n if ('isValueReference' in ref && ref.isValueReference === false) return false; // a type\n const variable = ref.resolved;\n if (variable === null) return false; // a global\n const type = variable.scope.variableScope.type;\n if (type === 'module' || type === 'global') return false;\n const helper = boundFunction(variable);\n if (helper === undefined) return true;\n return !seen.has(helper) && enclosingReads(context, helper, seen).length > 0;\n });\n}\n\n/** `s`, or `s.selected` / `s.a.b` when the reference starts a property chain, and its node. */\nfunction readOf(id: Node): { name: string; node: Node } {\n let name = id.type === 'Identifier' ? id.name : '';\n let node: Node = id;\n for (let p = node.parent; p?.type === 'MemberExpression'; p = p.parent) {\n if (p.object !== node || p.computed || p.property.type !== 'Identifier') break;\n name += `.${p.property.name}`;\n node = p;\n }\n return { name, node };\n}\n\nconst isMissing = (key: Node): boolean =>\n (key.type === 'Identifier' && key.name === 'undefined') ||\n (key.type === 'Literal' && key.value === null);\n\nexport const rowPurityRule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n \"each()'s row reads only its item, its picked value, module-level bindings and \" +\n 'imports; each() has a key function (view/03-lists.md, rules 8 and 9).',\n recommended: true,\n url: 'https://github.com/gyraljs/gyral/blob/main/docs/design-docs/view/03-lists.md',\n },\n schema: OPTIONS_SCHEMA,\n messages: {\n reads:\n '`row` reads `{{name}}`; return it from `pick` and take it as the second argument ' +\n '(view/03-lists.md).',\n key:\n 'each() needs a key function as its second argument: each(items, (x) => x.id, Row) ' +\n '(docs/design-docs/view/03-lists.md \"Keys\", rule 9).',\n },\n },\n create(context) {\n const reported = new Set<Node>();\n return {\n CallExpression(node) {\n if (!isGyral(context, node.callee as Node, 'each')) return;\n const [, key, row] = node.arguments as Node[];\n if (key === undefined || row === undefined || isMissing(key)) {\n context.report({ node, messageId: 'key' });\n }\n const fn = row === undefined ? undefined : rowFunction(context, row);\n if (fn === undefined) return;\n for (const ref of enclosingReads(context, fn, new Set())) {\n const { name, node: read } = readOf(ref.identifier as Node);\n if (reported.has(read)) continue; // one row passed to several each() calls\n reported.add(read);\n context.report({ node: read, messageId: 'reads', data: { name } });\n }\n },\n };\n },\n};\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
import { TemplateError } from '../view/index.js';
|
|
3
|
+
/** The message ESLint shows: the TemplateError's first line (rule, fix and spec section). */
|
|
4
|
+
export declare const messageOf: (error: TemplateError) => string;
|
|
5
|
+
export declare const templateRule: Rule.RuleModule;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { checkTemplate, TemplateError } from '../view/index.js';
|
|
2
|
+
import { isGyral, OPTIONS_SCHEMA } from './imports.js';
|
|
3
|
+
import { cookedMap, errorSpan } from './locate.js';
|
|
4
|
+
/** The message ESLint shows: the TemplateError's first line (rule, fix and spec section). */
|
|
5
|
+
export const messageOf = (error) => error.message.split('\n')[0] ?? '';
|
|
6
|
+
const INVALID_ESCAPE = 'This html template has an invalid escape sequence, so it has no string value at runtime. ' +
|
|
7
|
+
'Fix the escape (write \\\\ for a backslash).';
|
|
8
|
+
function quasisOf(context, node) {
|
|
9
|
+
const text = context.sourceCode.text;
|
|
10
|
+
const quasis = [];
|
|
11
|
+
const cooked = [];
|
|
12
|
+
for (const q of node.quasis) {
|
|
13
|
+
const value = q.value.cooked;
|
|
14
|
+
if (typeof value !== 'string')
|
|
15
|
+
return undefined;
|
|
16
|
+
const [from, to] = q.range ?? [0, 0];
|
|
17
|
+
// Parsers include the delimiters (` or } before, ` or ${ after) in the element's range.
|
|
18
|
+
const start = text.charAt(from) === '`' || text.charAt(from) === '}' ? from + 1 : from;
|
|
19
|
+
const end = q.tail ? to - 1 : to - 2;
|
|
20
|
+
// Undecodable (it shouldn't be): point at the quasi's start.
|
|
21
|
+
const map = cookedMap(text.slice(start, end), value) ?? Array(value.length + 1).fill(0);
|
|
22
|
+
quasis.push({ start, end, map });
|
|
23
|
+
cooked.push(value);
|
|
24
|
+
}
|
|
25
|
+
return { quasis, cooked };
|
|
26
|
+
}
|
|
27
|
+
export const templateRule = {
|
|
28
|
+
meta: {
|
|
29
|
+
type: 'problem',
|
|
30
|
+
docs: {
|
|
31
|
+
description: 'Template rules for html`…` (view/09-template-rules.md): the same checks and messages ' +
|
|
32
|
+
'as the template compiler and the development runtime.',
|
|
33
|
+
recommended: true,
|
|
34
|
+
url: 'https://github.com/gyraljs/gyral/blob/main/docs/design-docs/view/09-template-rules.md',
|
|
35
|
+
},
|
|
36
|
+
schema: OPTIONS_SCHEMA,
|
|
37
|
+
messages: { rule: '{{message}}', escape: INVALID_ESCAPE },
|
|
38
|
+
},
|
|
39
|
+
create(context) {
|
|
40
|
+
return {
|
|
41
|
+
TaggedTemplateExpression(node) {
|
|
42
|
+
if (!isGyral(context, node.tag, 'html'))
|
|
43
|
+
return;
|
|
44
|
+
const parts = quasisOf(context, node.quasi);
|
|
45
|
+
if (parts === undefined) {
|
|
46
|
+
context.report({ node: node.quasi, messageId: 'escape' });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const issue = checkTemplate(parts.cooked);
|
|
50
|
+
if (issue === undefined)
|
|
51
|
+
return;
|
|
52
|
+
const span = errorSpan(parts.quasis, parts.cooked, issue.strings, issue.at, issue.from);
|
|
53
|
+
const { sourceCode } = context;
|
|
54
|
+
context.report({
|
|
55
|
+
loc: {
|
|
56
|
+
start: sourceCode.getLocFromIndex(span.start),
|
|
57
|
+
end: sourceCode.getLocFromIndex(Math.max(span.end, span.start)),
|
|
58
|
+
},
|
|
59
|
+
messageId: 'rule',
|
|
60
|
+
data: { message: messageOf(issue.error) },
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=template-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-rule.js","sourceRoot":"","sources":["../../src/eslint/template-rule.ts"],"names":[],"mappings":"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","sourcesContent":["// gyral/template: the template rules of view/09-template-rules.md, in the editor. Every html`…`\n// whose tag is imported from a template source goes through the normalizer's own check\n// (view/normalize/check.ts), the one rule set the compiler and the development runtime use, so\n// the message is the same TemplateError message (minus the `near:`/`at` context, which the\n// editor's location replaces). Not checked here: rule 7's parse5 comparison (compiler only) and\n// the browser's own parse (runtime only); rule 11 (a page shell is fine on the server).\nimport type { Rule } from 'eslint';\nimport { checkTemplate, TemplateError } from '../view/index.js';\nimport { isGyral, OPTIONS_SCHEMA } from './imports.js';\nimport { cookedMap, errorSpan, type Quasi } from './locate.js';\n\n/** The message ESLint shows: the TemplateError's first line (rule, fix and spec section). */\nexport const messageOf = (error: TemplateError): string => error.message.split('\\n')[0] ?? '';\n\nconst INVALID_ESCAPE =\n 'This html template has an invalid escape sequence, so it has no string value at runtime. ' +\n 'Fix the escape (write \\\\\\\\ for a backslash).';\n\n/** What this rule reads of a TemplateElement (ESTree's, or typescript-eslint's). */\ninterface QuasiNode {\n readonly value: { readonly cooked?: string | null | undefined };\n readonly range?: [number, number] | undefined;\n readonly tail: boolean;\n}\n\nfunction quasisOf(\n context: Rule.RuleContext,\n node: { readonly quasis: readonly QuasiNode[] },\n): { quasis: Quasi[]; cooked: string[] } | undefined {\n const text = context.sourceCode.text;\n const quasis: Quasi[] = [];\n const cooked: string[] = [];\n for (const q of node.quasis) {\n const value = q.value.cooked;\n if (typeof value !== 'string') return undefined;\n const [from, to] = q.range ?? [0, 0];\n // Parsers include the delimiters (` or } before, ` or ${ after) in the element's range.\n const start = text.charAt(from) === '`' || text.charAt(from) === '}' ? from + 1 : from;\n const end = q.tail ? to - 1 : to - 2;\n // Undecodable (it shouldn't be): point at the quasi's start.\n const map = cookedMap(text.slice(start, end), value) ?? Array<number>(value.length + 1).fill(0);\n quasis.push({ start, end, map });\n cooked.push(value);\n }\n return { quasis, cooked };\n}\n\nexport const templateRule: Rule.RuleModule = {\n meta: {\n type: 'problem',\n docs: {\n description:\n 'Template rules for html`…` (view/09-template-rules.md): the same checks and messages ' +\n 'as the template compiler and the development runtime.',\n recommended: true,\n url: 'https://github.com/gyraljs/gyral/blob/main/docs/design-docs/view/09-template-rules.md',\n },\n schema: OPTIONS_SCHEMA,\n messages: { rule: '{{message}}', escape: INVALID_ESCAPE },\n },\n create(context) {\n return {\n TaggedTemplateExpression(node) {\n if (!isGyral(context, node.tag as Rule.Node, 'html')) return;\n const parts = quasisOf(context, node.quasi);\n if (parts === undefined) {\n context.report({ node: node.quasi, messageId: 'escape' });\n return;\n }\n const issue = checkTemplate(parts.cooked);\n if (issue === undefined) return;\n const span = errorSpan(parts.quasis, parts.cooked, issue.strings, issue.at, issue.from);\n const { sourceCode } = context;\n context.report({\n loc: {\n start: sourceCode.getLocFromIndex(span.start),\n end: sourceCode.getLocFromIndex(Math.max(span.end, span.start)),\n },\n messageId: 'rule',\n data: { message: messageOf(issue.error) },\n });\n },\n };\n },\n};\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DriverOverrides } from './command.js';
|
|
2
|
+
import type { Interpreter } from './internal/interpreter.js';
|
|
3
|
+
import type { PropFeature } from './props.js';
|
|
4
|
+
import type { AnyStore, StoreOverrides, StoreRef, StoreSendInput } from './store.js';
|
|
5
|
+
import type { CommandTrace } from './devtools-events.js';
|
|
6
|
+
import type { IntentRejected, Tagged } from './types.js';
|
|
7
|
+
/** A host's element, as features see it. */
|
|
8
|
+
export type FeatureHost = HTMLElement & {
|
|
9
|
+
drivers: DriverOverrides;
|
|
10
|
+
stores: StoreOverrides;
|
|
11
|
+
};
|
|
12
|
+
/** What a component's store binding offers its model (store-binding.ts). */
|
|
13
|
+
export interface StoreLink {
|
|
14
|
+
read<S>(ref: StoreRef<S>): S;
|
|
15
|
+
send(input: StoreSendInput): void;
|
|
16
|
+
connect(): boolean;
|
|
17
|
+
disconnect(): void;
|
|
18
|
+
}
|
|
19
|
+
/** The host a marker driver's `local` handler runs against (focus, emit, store send). */
|
|
20
|
+
export interface LocalHost {
|
|
21
|
+
readonly el: FeatureHost;
|
|
22
|
+
readonly tag: string;
|
|
23
|
+
readonly root: () => ParentNode | undefined;
|
|
24
|
+
readonly stores: () => StoreLink;
|
|
25
|
+
}
|
|
26
|
+
/** A marker driver core runs itself, synchronously, without the interpreter. */
|
|
27
|
+
export interface LocalDriver {
|
|
28
|
+
readonly local: (host: LocalHost, input: unknown) => void;
|
|
29
|
+
}
|
|
30
|
+
type Msg = Tagged | IntentRejected;
|
|
31
|
+
export declare const features: {
|
|
32
|
+
/** Set by `command()`: a host's command interpreter, resolving drivers by name (ADR 0006). */
|
|
33
|
+
commands?: (el: FeatureHost, drivers: DriverOverrides | undefined, dispatch: (msg: Msg) => void, trace: CommandTrace | undefined) => Interpreter<Msg>;
|
|
34
|
+
/** Set by `defineStore()`: binds a host to the stores it declares (ADR 0013). */
|
|
35
|
+
stores?: (el: FeatureHost, tag: string, declared: readonly AnyStore[], onChange: (store: AnyStore, state: unknown, prev: unknown) => void) => StoreLink;
|
|
36
|
+
/** Set by the prop builders: attribute parsing and validation (view/05 "Props"). */
|
|
37
|
+
props?: PropFeature;
|
|
38
|
+
};
|
|
39
|
+
export {};
|
package/dist/features.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"features.js","sourceRoot":"","sources":["../src/features.ts"],"names":[],"mappings":"AAsCA,MAAM,CAAC,MAAM,QAAQ,GAiBjB,EAAE,CAAC","sourcesContent":["// Features that register themselves when their API is used (ADR 0018 \"Size\", gyral-g1r.18).\n// Core reaches them only through these slots, which the feature's own API fills when called:\n// `command()` the interpreter, `defineStore()` the store binding, a prop builder the prop\n// machinery. An app that never calls one doesn't bundle what it registers. No module does this\n// at load time: bundlers keep `sideEffects: false` and tree-shake as usual.\nimport type { DriverOverrides } from './command.js';\nimport type { Interpreter } from './internal/interpreter.js';\nimport type { PropFeature } from './props.js';\nimport type { AnyStore, StoreOverrides, StoreRef, StoreSendInput } from './store.js';\nimport type { CommandTrace } from './devtools-events.js';\nimport type { IntentRejected, Tagged } from './types.js';\n\n/** A host's element, as features see it. */\nexport type FeatureHost = HTMLElement & { drivers: DriverOverrides; stores: StoreOverrides };\n\n/** What a component's store binding offers its model (store-binding.ts). */\nexport interface StoreLink {\n read<S>(ref: StoreRef<S>): S;\n send(input: StoreSendInput): void;\n connect(): boolean;\n disconnect(): void;\n}\n\n/** The host a marker driver's `local` handler runs against (focus, emit, store send). */\nexport interface LocalHost {\n readonly el: FeatureHost;\n readonly tag: string;\n readonly root: () => ParentNode | undefined;\n readonly stores: () => StoreLink;\n}\n\n/** A marker driver core runs itself, synchronously, without the interpreter. */\nexport interface LocalDriver {\n readonly local: (host: LocalHost, input: unknown) => void;\n}\n\ntype Msg = Tagged | IntentRejected;\n\nexport const features: {\n /** Set by `command()`: a host's command interpreter, resolving drivers by name (ADR 0006). */\n commands?: (\n el: FeatureHost,\n drivers: DriverOverrides | undefined,\n dispatch: (msg: Msg) => void,\n trace: CommandTrace | undefined,\n ) => Interpreter<Msg>;\n /** Set by `defineStore()`: binds a host to the stores it declares (ADR 0013). */\n stores?: (\n el: FeatureHost,\n tag: string,\n declared: readonly AnyStore[],\n onChange: (store: AnyStore, state: unknown, prev: unknown) => void,\n ) => StoreLink;\n /** Set by the prop builders: attribute parsing and validation (view/05 \"Props\"). */\n props?: PropFeature;\n} = {};\n"]}
|
package/dist/focus.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Command } from './command.js';
|
|
2
|
-
|
|
2
|
+
import type { LocalHost } from './features.js';
|
|
3
|
+
/** Marker driver: the host runs focus commands itself (`local`). */
|
|
3
4
|
export declare const FOCUS: {
|
|
4
5
|
readonly name: "@gyral/focus";
|
|
5
6
|
readonly run: () => undefined;
|
|
7
|
+
readonly local: (host: LocalHost, input: unknown) => void;
|
|
6
8
|
};
|
|
7
9
|
export interface FocusOptions {
|
|
8
10
|
/** Don't scroll the element into view (default `false`: the browser scrolls if needed). */
|
|
@@ -23,17 +25,10 @@ export interface FocusInput extends FocusOptions {
|
|
|
23
25
|
* Non-focusable targets (a heading) need `tabindex="-1"` in the view.
|
|
24
26
|
*/
|
|
25
27
|
export declare function focus(selector: string, options?: FocusOptions): Command<never>;
|
|
26
|
-
/** Runs a focus command against `root` (
|
|
28
|
+
/** Runs a focus command against `root` (the scheduler calls it after the render). */
|
|
27
29
|
export declare function runFocus(root: ParentNode, tag: string, input: FocusInput): void;
|
|
28
|
-
/** What focusAfterUpdate needs from a component: Lit's update promise and its render root. */
|
|
29
|
-
export interface FocusHost {
|
|
30
|
-
readonly updateComplete: Promise<boolean>;
|
|
31
|
-
readonly isConnected: boolean;
|
|
32
|
-
readonly renderRoot: ParentNode;
|
|
33
|
-
}
|
|
34
30
|
/**
|
|
35
|
-
* Runs a focus command after the
|
|
36
|
-
*
|
|
37
|
-
* microtask first, then for updateComplete, which waits for the transition (gyral-6zz).
|
|
31
|
+
* Runs a focus command after the flush that renders the same update, view transition
|
|
32
|
+
* included: it is post-render work, so the DOM already shows the new state.
|
|
38
33
|
*/
|
|
39
|
-
export declare function
|
|
34
|
+
export declare function queueFocus(host: Element, root: () => ParentNode | undefined, tag: string, input: FocusInput): void;
|
package/dist/focus.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { afterRender, POST_FOCUS } from './scheduler.js';
|
|
2
|
+
/** Marker driver: the host runs focus commands itself (`local`). */
|
|
2
3
|
export const FOCUS = {
|
|
3
4
|
name: '@gyral/focus',
|
|
4
5
|
run: () => undefined,
|
|
6
|
+
local: (host, input) => {
|
|
7
|
+
queueFocus(host.el, host.root, host.tag, input);
|
|
8
|
+
},
|
|
5
9
|
};
|
|
6
10
|
/**
|
|
7
11
|
* A command that focuses the first element matching `selector` inside this component (its
|
|
@@ -15,7 +19,7 @@ export function focus(selector, options = {}) {
|
|
|
15
19
|
const input = { selector, ...options };
|
|
16
20
|
return { driver: FOCUS, input, onSuccess: () => undefined };
|
|
17
21
|
}
|
|
18
|
-
/** Runs a focus command against `root` (
|
|
22
|
+
/** Runs a focus command against `root` (the scheduler calls it after the render). */
|
|
19
23
|
export function runFocus(root, tag, input) {
|
|
20
24
|
const target = root.querySelector(input.selector);
|
|
21
25
|
if (!(target instanceof HTMLElement || target instanceof SVGElement)) {
|
|
@@ -28,15 +32,14 @@ export function runFocus(root, tag, input) {
|
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
34
|
/**
|
|
31
|
-
* Runs a focus command after the
|
|
32
|
-
*
|
|
33
|
-
* microtask first, then for updateComplete, which waits for the transition (gyral-6zz).
|
|
35
|
+
* Runs a focus command after the flush that renders the same update, view transition
|
|
36
|
+
* included: it is post-render work, so the DOM already shows the new state.
|
|
34
37
|
*/
|
|
35
|
-
export function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (host.isConnected)
|
|
39
|
-
runFocus(
|
|
38
|
+
export function queueFocus(host, root, tag, input) {
|
|
39
|
+
afterRender(POST_FOCUS, () => {
|
|
40
|
+
const target = root();
|
|
41
|
+
if (host.isConnected && target !== undefined)
|
|
42
|
+
runFocus(target, tag, input);
|
|
40
43
|
});
|
|
41
44
|
}
|
|
42
45
|
//# sourceMappingURL=focus.js.map
|
package/dist/focus.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"focus.js","sourceRoot":"","sources":["../src/focus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"focus.js","sourceRoot":"","sources":["../src/focus.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEzD,oEAAoE;AACpE,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,IAAI,EAAE,cAAc;IACpB,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;IACpB,KAAK,EAAE,CAAC,IAAe,EAAE,KAAc,EAAE,EAAE;QACzC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,KAAmB,CAAC,CAAC;IAChE,CAAC;CACO,CAAC;AAcX;;;;;;;GAOG;AACH,MAAM,UAAU,KAAK,CAAC,QAAgB,EAAE,UAAwB,EAAE;IAChE,MAAM,KAAK,GAAe,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IACnD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;AAC9D,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,QAAQ,CAAC,IAAgB,EAAE,GAAW,EAAE,KAAiB;IACvE,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,CAAC,MAAM,YAAY,WAAW,IAAI,MAAM,YAAY,UAAU,CAAC,EAAE,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,IAAI,GAAG,YAAY,KAAK,CAAC,QAAQ,kCAAkC,CAAC,CAAC;QAClF,OAAO;IACT,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,KAAK,EAAE,CAAC,CAAC;IAC9D,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,QAAQ,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACtF,MAA2B,CAAC,MAAM,EAAE,CAAC;IACxC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,IAAa,EACb,IAAkC,EAClC,GAAW,EACX,KAAiB;IAEjB,WAAW,CAAC,UAAU,EAAE,GAAG,EAAE;QAC3B,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC;QACtB,IAAI,IAAI,CAAC,WAAW,IAAI,MAAM,KAAK,SAAS;YAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["// Focus management from the model (gyral-czi.28). Moving focus is a side effect, so the model\n// asks for it with a command; define() queues it as the scheduler's first post-render work\n// (view/04-scheduler.md \"Post-render queue\"), against the component's own root, so the element\n// to focus already exists.\nimport type { Command } from './command.js';\nimport type { LocalHost } from './features.js';\nimport { afterRender, POST_FOCUS } from './scheduler.js';\n\n/** Marker driver: the host runs focus commands itself (`local`). */\nexport const FOCUS = {\n name: '@gyral/focus',\n run: () => undefined,\n local: (host: LocalHost, input: unknown) => {\n queueFocus(host.el, host.root, host.tag, input as FocusInput);\n },\n} as const;\n\nexport interface FocusOptions {\n /** Don't scroll the element into view (default `false`: the browser scrolls if needed). */\n readonly preventScroll?: boolean;\n /** Also select the text of an input or textarea. */\n readonly select?: boolean;\n}\n\n/** The input of a `focus()` command. */\nexport interface FocusInput extends FocusOptions {\n readonly selector: string;\n}\n\n/**\n * A command that focuses the first element matching `selector` inside this component (its\n * shadow root, or its own children in light-DOM mode) once the current update has rendered:\n *\n * NextPage: (s, m) => [{ ...s, page: m.page }, [focus('h2', { preventScroll: true })]]\n *\n * Non-focusable targets (a heading) need `tabindex=\"-1\"` in the view.\n */\nexport function focus(selector: string, options: FocusOptions = {}): Command<never> {\n const input: FocusInput = { selector, ...options };\n return { driver: FOCUS, input, onSuccess: () => undefined };\n}\n\n/** Runs a focus command against `root` (the scheduler calls it after the render). */\nexport function runFocus(root: ParentNode, tag: string, input: FocusInput): void {\n const target = root.querySelector(input.selector);\n if (!(target instanceof HTMLElement || target instanceof SVGElement)) {\n console.warn(`<${tag}> focus(\"${input.selector}\") matched no focusable element.`);\n return;\n }\n target.focus({ preventScroll: input.preventScroll ?? false });\n if (input.select === true && 'select' in target && typeof target.select === 'function') {\n (target as HTMLInputElement).select();\n }\n}\n\n/**\n * Runs a focus command after the flush that renders the same update, view transition\n * included: it is post-render work, so the DOM already shows the new state.\n */\nexport function queueFocus(\n host: Element,\n root: () => ParentNode | undefined,\n tag: string,\n input: FocusInput,\n): void {\n afterRender(POST_FOCUS, () => {\n const target = root();\n if (host.isConnected && target !== undefined) runFocus(target, tag, input);\n });\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type Errors = readonly string[] | string | undefined;
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors model errors to native validity: `setCustomValidity` and `aria-invalid`.
|
|
4
|
+
*
|
|
5
|
+
* <input name="email" ${invalid(s.errors.email)} />
|
|
6
|
+
*
|
|
7
|
+
* The custom error clears when the control fires `input` or `change` (the user edited it), when
|
|
8
|
+
* the bound errors become `undefined` or empty (the model cleared them), or when a submit finds
|
|
9
|
+
* the error stale (the value changed without an event, e.g. set from code): then the error is
|
|
10
|
+
* dropped and the form is submitted again once. An error is applied again only when a new value
|
|
11
|
+
* arrives (keep `fieldErrors()` results in state): `client` runs only when the errors change.
|
|
12
|
+
*/
|
|
13
|
+
export declare const invalid: (errors?: Errors) => import("../index.js").HookResult<[errors?: Errors]>;
|
|
14
|
+
export {};
|