create-kerf-component 5.0.0-beta.56 → 5.0.0-beta.58
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/README.md +26 -5
- package/catalog.js +30 -5
- package/{component-catalog-v2.schema.json → component-composition.schema.json} +21 -5
- package/component-metadata.schema.json +14 -1
- package/index.js +3 -2
- package/package.json +2 -2
- package/template/README.md +21 -4
- package/template/{component-catalog-v2.json → component-composition.json} +6 -4
- package/template/kerf.components.json +2 -2
- package/template/package.json +9 -5
- package/template/src/counter.css +38 -0
- package/template/src/counter.tsx +14 -2
package/README.md
CHANGED
|
@@ -12,6 +12,7 @@ cd my-widgets
|
|
|
12
12
|
npm install
|
|
13
13
|
npm run build
|
|
14
14
|
npm run catalog:check
|
|
15
|
+
npm run check:styles
|
|
15
16
|
```
|
|
16
17
|
|
|
17
18
|
Run it with **no argument** (`npm create kerf-component`) and it prompts for the
|
|
@@ -36,11 +37,21 @@ A ready-to-publish component package that encodes the rules from the kerf docs
|
|
|
36
37
|
component needs:
|
|
37
38
|
- **per-instance state via a factory + props** (`createCounter` → `<Counter store={…} />`), and
|
|
38
39
|
- **a `wire(root)` delegation disposer** (`wireCounter`) instead of inline event handlers.
|
|
40
|
+
- **A stylesheet that owns only its component** (`src/counter.css`, exported as
|
|
41
|
+
`./counter.css`): it styles `.kerf-counter` and its own internals, writes only
|
|
42
|
+
`--_kerf-counter-*` private variables (the context it gives its buttons is
|
|
43
|
+
named after Counter), exposes a typed `size` prop and one public token
|
|
44
|
+
(`--kerf-counter-gap`) as its configuration, and adapts itself inside a Kerf
|
|
45
|
+
UI Toolbar from its own stylesheet (`.kui-toolbar .kerf-counter`). `npm run
|
|
46
|
+
check:styles` runs `kerf-ui-analyze` (from the `@kerfjs/ui` dev dependency)
|
|
47
|
+
over `src/`, so a rule that restyles another package's component, touches
|
|
48
|
+
its private variables, overrides a token a typed prop sets, or uses a hook
|
|
49
|
+
class (`KUI-L019`–`KUI-L022`) fails before `prepublishOnly` publishes.
|
|
39
50
|
- **Author-owned AI metadata** in `kerf.components.json`, with explicit purpose,
|
|
40
51
|
exports, composition, geometry, public `rootClass`, tokens, accessibility,
|
|
41
52
|
and source links. Run
|
|
42
53
|
`npm run catalog:generate` to emit the package-qualified
|
|
43
|
-
`component-
|
|
54
|
+
`component-composition.json`; `npm run catalog:check` fails on drift, deleted
|
|
44
55
|
sources, renamed exports, duplicate ids, an omitted author decision, or any
|
|
45
56
|
source/output field that violates the shipped schemas. Export verification
|
|
46
57
|
uses the TypeScript/TSX syntax tree, so JSX text, nested scopes, comments, and
|
|
@@ -54,6 +65,13 @@ A ready-to-publish component package that encodes the rules from the kerf docs
|
|
|
54
65
|
not render, remove, or treat those attributes as their own state. The field
|
|
55
66
|
is optional; names must be unique per component, and the checker rejects a
|
|
56
67
|
helper that is not listed in `wiring.helpers`.
|
|
68
|
+
`boundaries.placeableClasses` (optional) names the public classes an app may
|
|
69
|
+
write onto its own elements; every other public class is the component's
|
|
70
|
+
rendered anatomy, which `eslint-plugin-kerfjs` reports on an app-owned
|
|
71
|
+
element as `KUI-L103` with the component to render. `boundaries.rootElement`
|
|
72
|
+
names the element the component itself renders around its placeable classes,
|
|
73
|
+
so a plain element of that tag carrying them is reported too. Placeable
|
|
74
|
+
classes must be public; `rootElement` requires them.
|
|
57
75
|
A wrapper that renders a cataloged component declares it under
|
|
58
76
|
`composition.rendersAs` (for example
|
|
59
77
|
`["@kerfjs/ui:toolbar-control-group"]`), so the `ui-composition` lint treats
|
|
@@ -79,21 +97,24 @@ my-widgets/
|
|
|
79
97
|
├── tsconfig.json # jsxImportSource: "kerfjs"
|
|
80
98
|
├── tsup.config.ts # external: ['kerfjs'], format esm, dts
|
|
81
99
|
├── kerf.components.json # explicit source metadata (never inferred from pixels)
|
|
82
|
-
├── component-
|
|
100
|
+
├── component-composition.json # deterministic generated AI catalog
|
|
83
101
|
├── scripts/
|
|
84
102
|
│ ├── kerf-component-catalog.mjs # local generator + check mode
|
|
85
103
|
│ ├── component-metadata.schema.json # author-source schema used by the checker
|
|
86
|
-
│ └── component-
|
|
104
|
+
│ └── component-composition.schema.json # emitted-catalog schema used by the checker
|
|
87
105
|
├── LICENSE # MIT license with the package contributor notice
|
|
88
106
|
├── .gitignore
|
|
89
107
|
├── README.md
|
|
90
108
|
└── src/
|
|
91
109
|
├── index.ts # public barrel
|
|
92
|
-
|
|
110
|
+
├── counter.tsx # factory + component + wire() disposer
|
|
111
|
+
└── counter.css # styles only Counter; configured by props and tokens
|
|
93
112
|
```
|
|
94
113
|
|
|
95
114
|
This package is part of the kerf repository and releases in lockstep with
|
|
96
|
-
`kerfjs`.
|
|
115
|
+
`kerfjs`. The 5.0.0 template targets `kerfjs` and `@kerfjs/ui` on the 5.x
|
|
116
|
+
line; the generated package keeps Kerf as a peer dependency and uses UI only
|
|
117
|
+
for its authoring checks.
|
|
97
118
|
|
|
98
119
|
The package also exposes `kerf-component-catalog`. It accepts `--write` (the
|
|
99
120
|
default), `--check`, and `--root <path>`. A root package with npm `workspaces`
|
package/catalog.js
CHANGED
|
@@ -20,7 +20,7 @@ const metadataSchema = JSON.parse(
|
|
|
20
20
|
readFileSync(join(scriptRoot, 'component-metadata.schema.json'), 'utf8'),
|
|
21
21
|
);
|
|
22
22
|
const catalogSchema = JSON.parse(
|
|
23
|
-
readFileSync(join(scriptRoot, 'component-
|
|
23
|
+
readFileSync(join(scriptRoot, 'component-composition.schema.json'), 'utf8'),
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
export class CatalogError extends Error {
|
|
@@ -619,6 +619,31 @@ function validateComponent(
|
|
|
619
619
|
diagnostics.push(
|
|
620
620
|
`${at}.boundaries.rootClass: expected null or one publicClasses entry`,
|
|
621
621
|
);
|
|
622
|
+
// An application may place only the declared subset of public classes on
|
|
623
|
+
// its own elements; every other public class is the component's rendered
|
|
624
|
+
// anatomy (eslint-plugin-kerfjs KUI-L103). `rootElement` names the element
|
|
625
|
+
// the component renders around its placeable classes.
|
|
626
|
+
if (component.boundaries && 'placeableClasses' in component.boundaries) {
|
|
627
|
+
validateStringList(
|
|
628
|
+
component.boundaries.placeableClasses,
|
|
629
|
+
`${at}.boundaries.placeableClasses`,
|
|
630
|
+
diagnostics,
|
|
631
|
+
);
|
|
632
|
+
for (const className of Array.isArray(component.boundaries.placeableClasses)
|
|
633
|
+
? component.boundaries.placeableClasses
|
|
634
|
+
: [])
|
|
635
|
+
if (!component.boundaries.publicClasses?.includes(className))
|
|
636
|
+
diagnostics.push(
|
|
637
|
+
`${at}.boundaries.placeableClasses: ${className} is not one of publicClasses`,
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
if (
|
|
641
|
+
component.boundaries?.rootElement !== undefined &&
|
|
642
|
+
!component.boundaries.placeableClasses?.length
|
|
643
|
+
)
|
|
644
|
+
diagnostics.push(
|
|
645
|
+
`${at}.boundaries.rootElement: requires a non-empty placeableClasses`,
|
|
646
|
+
);
|
|
622
647
|
validateStringList(
|
|
623
648
|
component.boundaries?.publicTokens,
|
|
624
649
|
`${at}.boundaries.publicTokens`,
|
|
@@ -697,7 +722,7 @@ function dependencyCatalogKeys(packageRoot, packageName, cache) {
|
|
|
697
722
|
} catch {
|
|
698
723
|
// An unreadable manifest leaves the package unresolved below.
|
|
699
724
|
}
|
|
700
|
-
catalogPath ??= join(dependencyRoot, 'ai', 'component-
|
|
725
|
+
catalogPath ??= join(dependencyRoot, 'ai', 'component-composition.json');
|
|
701
726
|
try {
|
|
702
727
|
const catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
|
|
703
728
|
keys = new Set(
|
|
@@ -800,11 +825,11 @@ export function generateCatalogs(root = process.cwd()) {
|
|
|
800
825
|
.map((component) => toEntry(packageJson.name, component));
|
|
801
826
|
const catalog = {
|
|
802
827
|
$schema:
|
|
803
|
-
'https://raw.githubusercontent.com/brianwestphal/kerf/main/ui/ai/component-
|
|
804
|
-
schemaVersion:
|
|
828
|
+
'https://raw.githubusercontent.com/brianwestphal/kerf/main/ui/ai/component-composition-extension.schema.json',
|
|
829
|
+
schemaVersion: 1,
|
|
805
830
|
package: packageJson.name,
|
|
806
831
|
compatibility: {
|
|
807
|
-
|
|
832
|
+
componentCatalog: metadata.componentCatalog ?? 'not-applicable',
|
|
808
833
|
identity: 'package:id',
|
|
809
834
|
},
|
|
810
835
|
entries,
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://raw.githubusercontent.com/brianwestphal/kerf/main/ui/ai/component-
|
|
4
|
-
"title": "Kerf UI composition catalog
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/brianwestphal/kerf/main/ui/ai/component-composition.schema.json",
|
|
4
|
+
"title": "Kerf UI composition catalog",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": ["schemaVersion", "package", "compatibility", "entries"],
|
|
7
7
|
"properties": {
|
|
8
8
|
"$schema": { "type": "string" },
|
|
9
|
-
"schemaVersion": {
|
|
9
|
+
"schemaVersion": {
|
|
10
|
+
"const": 1,
|
|
11
|
+
"description": "Version of the composition catalog format itself, independent of component-catalog.json's own schemaVersion."
|
|
12
|
+
},
|
|
10
13
|
"package": { "type": "string", "minLength": 1 },
|
|
11
14
|
"compatibility": {
|
|
12
15
|
"type": "object",
|
|
13
|
-
"required": ["
|
|
16
|
+
"required": ["componentCatalog", "identity"],
|
|
14
17
|
"properties": {
|
|
15
|
-
"
|
|
18
|
+
"componentCatalog": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"minLength": 1,
|
|
21
|
+
"description": "Relative path to the component (selection) catalog this composition layer describes, or \"not-applicable\" when the package publishes none."
|
|
22
|
+
},
|
|
16
23
|
"identity": { "const": "package:id" }
|
|
17
24
|
},
|
|
18
25
|
"additionalProperties": false
|
|
@@ -274,6 +281,15 @@
|
|
|
274
281
|
]
|
|
275
282
|
},
|
|
276
283
|
"publicClasses": { "$ref": "#/$defs/stringList" },
|
|
284
|
+
"placeableClasses": {
|
|
285
|
+
"$ref": "#/$defs/stringList",
|
|
286
|
+
"description": "The subset of publicClasses an application may place on its own elements. Absent means none: every public class is the component's rendered anatomy, reached by rendering the component."
|
|
287
|
+
},
|
|
288
|
+
"rootElement": {
|
|
289
|
+
"type": "string",
|
|
290
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
291
|
+
"description": "The intrinsic element the component renders at its root. A placeableClasses class on a plain element of this tag recreates the component; the class stays placeable on any other carrier element."
|
|
292
|
+
},
|
|
277
293
|
"publicTokens": { "$ref": "#/$defs/stringList" },
|
|
278
294
|
"publicParts": { "$ref": "#/$defs/stringList" }
|
|
279
295
|
},
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
"properties": {
|
|
8
8
|
"$schema": { "type": "string" },
|
|
9
9
|
"schemaVersion": { "const": 1 },
|
|
10
|
-
"
|
|
10
|
+
"componentCatalog": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"minLength": 1,
|
|
13
|
+
"description": "Relative path to the package's component (selection) catalog, copied into the generated composition catalog's compatibility.componentCatalog; defaults to \"not-applicable\"."
|
|
14
|
+
},
|
|
11
15
|
"components": {
|
|
12
16
|
"type": "array",
|
|
13
17
|
"items": { "$ref": "#/$defs/component" }
|
|
@@ -121,6 +125,15 @@
|
|
|
121
125
|
]
|
|
122
126
|
},
|
|
123
127
|
"publicClasses": { "$ref": "#/$defs/stringList" },
|
|
128
|
+
"placeableClasses": {
|
|
129
|
+
"$ref": "#/$defs/stringList",
|
|
130
|
+
"description": "The subset of publicClasses an application may place on its own elements (a layout utility, item geometry on another carrier). Absent means none: every public class is the component's rendered anatomy, which eslint-plugin-kerfjs reports on an application-owned element as KUI-L103 with the component to render."
|
|
131
|
+
},
|
|
132
|
+
"rootElement": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
135
|
+
"description": "The intrinsic element the component renders around its own placeableClasses. A placeable class on a plain element of this tag is the component recreated (KUI-L103); another carrier element keeps it. Requires placeableClasses."
|
|
136
|
+
},
|
|
124
137
|
"publicTokens": { "$ref": "#/$defs/stringList" }
|
|
125
138
|
},
|
|
126
139
|
"additionalProperties": false
|
package/index.js
CHANGED
|
@@ -136,7 +136,7 @@ async function main(argv) {
|
|
|
136
136
|
);
|
|
137
137
|
for (const schema of [
|
|
138
138
|
'component-metadata.schema.json',
|
|
139
|
-
'component-
|
|
139
|
+
'component-composition.schema.json',
|
|
140
140
|
])
|
|
141
141
|
copyFileSync(
|
|
142
142
|
join(dirname(fileURLToPath(import.meta.url)), schema),
|
|
@@ -162,8 +162,9 @@ async function main(argv) {
|
|
|
162
162
|
' npm install\n' +
|
|
163
163
|
' npm run build # tsup → ESM + .d.ts (kerfjs stays external)\n' +
|
|
164
164
|
' npm run catalog:check # verify AI metadata and public exports\n' +
|
|
165
|
+
' npm run check:styles # each component styles only itself (kerf-ui-analyze)\n' +
|
|
165
166
|
' npm run typecheck\n\n' +
|
|
166
|
-
'Edit src/counter.tsx and kerf.components.json, then publish with `npm publish --access public`.\n',
|
|
167
|
+
'Edit src/counter.tsx, src/counter.css, and kerf.components.json, then publish with `npm publish --access public`.\n',
|
|
167
168
|
);
|
|
168
169
|
}
|
|
169
170
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kerf-component",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.58",
|
|
4
4
|
"description": "Scaffold a publishable kerf component package that already follows kerf's hard packaging rules.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"index.js",
|
|
34
34
|
"catalog.js",
|
|
35
35
|
"component-metadata.schema.json",
|
|
36
|
-
"component-
|
|
36
|
+
"component-composition.schema.json",
|
|
37
37
|
"template",
|
|
38
38
|
"README.md",
|
|
39
39
|
"LICENSE"
|
package/template/README.md
CHANGED
|
@@ -9,7 +9,8 @@ scaffolded with `create-kerf-component`.
|
|
|
9
9
|
npm install
|
|
10
10
|
npm run build # tsup → dist/ (ESM + .d.ts); kerfjs stays external
|
|
11
11
|
npm run typecheck
|
|
12
|
-
npm run catalog:check # verify component-
|
|
12
|
+
npm run catalog:check # verify component-composition.json is current
|
|
13
|
+
npm run check:styles # each component styles only itself (kerf-ui-analyze)
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
## Use it
|
|
@@ -20,11 +21,12 @@ render the component like any local function — there's no extra toolchain:
|
|
|
20
21
|
```tsx
|
|
21
22
|
import { mount } from "kerfjs";
|
|
22
23
|
import { Counter, createCounter, wireCounter } from "__PKG_NAME__";
|
|
24
|
+
import "__PKG_NAME__/counter.css";
|
|
23
25
|
|
|
24
26
|
const counter = createCounter(0); // per-instance state (a factory)
|
|
25
27
|
const root = document.getElementById("app")!;
|
|
26
28
|
|
|
27
|
-
mount(root, () => <Counter store={counter} label="Clicks" />);
|
|
29
|
+
mount(root, () => <Counter store={counter} label="Clicks" size="compact" />);
|
|
28
30
|
|
|
29
31
|
const dispose = wireCounter(root, counter); // delegation disposer (call on teardown)
|
|
30
32
|
```
|
|
@@ -44,6 +46,14 @@ have to:
|
|
|
44
46
|
- **No inline JSX event handlers.** Components are pure `(props) => SafeHtml`
|
|
45
47
|
string-builders; emit `data-action` hooks and let the host wire events with
|
|
46
48
|
`delegate()` (see `wireCounter`), which returns a disposer.
|
|
49
|
+
- **Each component owns its styles; configure, never override.** `counter.css`
|
|
50
|
+
styles only `.kerf-counter` and its own internals and writes only
|
|
51
|
+
`--_kerf-counter-*` private variables (context it provides is named after
|
|
52
|
+
Counter). Consumers configure it through props (`size`) and its public token
|
|
53
|
+
(`--kerf-counter-gap`); when it must adapt inside a parent, Counter does so
|
|
54
|
+
in its own stylesheet (`.kui-toolbar .kerf-counter`). A needed variation with
|
|
55
|
+
no prop is a gap to add here, not an override for consumers.
|
|
56
|
+
`npm run check:styles` enforces this with `kerf-ui-analyze`.
|
|
47
57
|
- **Build emits ESM + `.d.ts`; `tsconfig` sets `jsxImportSource: "kerfjs"`.**
|
|
48
58
|
- **Never import `kerfjs/dev`.** kerf's dev diagnostics install global hooks, so
|
|
49
59
|
installing them is the consuming _app's_ call, not a library's — the app
|
|
@@ -66,7 +76,14 @@ the metadata/catalog pair, the README, and the license.
|
|
|
66
76
|
public exports, composition rules, geometry ownership, tokens, accessibility,
|
|
67
77
|
and source links. `boundaries.rootClass` explicitly names the public class that
|
|
68
78
|
owns runtime geometry (or is `null` when none does); `publicClasses` order has no
|
|
69
|
-
semantic meaning.
|
|
79
|
+
semantic meaning. Every public class is the component's rendered anatomy unless
|
|
80
|
+
`boundaries.placeableClasses` lists it: once an app's `.kerf-ui-profile.json`
|
|
81
|
+
declares this package's catalog under `catalogs`, `eslint-plugin-kerfjs`
|
|
82
|
+
reports an app that writes `kerf-counter` onto its own `<div>` (`KUI-L103`) and
|
|
83
|
+
tells it to render `Counter` instead. List a class there only when apps may legitimately
|
|
84
|
+
place it themselves (a layout utility, item geometry on another carrier), and
|
|
85
|
+
add `rootElement` when the component itself renders those classes on that tag.
|
|
86
|
+
Keep decisions explicit: the generator deliberately does not
|
|
70
87
|
derive semantic or geometry ownership from rendered appearance. It validates
|
|
71
88
|
both this source and the generated catalog against the schema copies beside the
|
|
72
89
|
checker, including rejection of unknown fields. Named exports must exist in
|
|
@@ -75,7 +92,7 @@ are not exports. The checker uses this package's installed TypeScript compiler,
|
|
|
75
92
|
so run `npm install` before the first local catalog command.
|
|
76
93
|
|
|
77
94
|
```bash
|
|
78
|
-
npm run catalog:generate # write component-
|
|
95
|
+
npm run catalog:generate # write component-composition.json
|
|
79
96
|
npm run catalog:check # no writes; fail when metadata, source, or output drift
|
|
80
97
|
```
|
|
81
98
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://raw.githubusercontent.com/brianwestphal/kerf/main/ui/ai/component-
|
|
3
|
-
"schemaVersion":
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/brianwestphal/kerf/main/ui/ai/component-composition-extension.schema.json",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
4
|
"package": "__PKG_NAME__",
|
|
5
5
|
"compatibility": {
|
|
6
|
-
"
|
|
6
|
+
"componentCatalog": "not-applicable",
|
|
7
7
|
"identity": "package:id"
|
|
8
8
|
},
|
|
9
9
|
"entries": [
|
|
@@ -130,7 +130,9 @@
|
|
|
130
130
|
"publicClasses": [
|
|
131
131
|
"kerf-counter"
|
|
132
132
|
],
|
|
133
|
-
"publicTokens": [
|
|
133
|
+
"publicTokens": [
|
|
134
|
+
"--kerf-counter-gap"
|
|
135
|
+
]
|
|
134
136
|
},
|
|
135
137
|
"diagnostics": [],
|
|
136
138
|
"provenance": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/brianwestphal/kerf/main/create-kerf-component/component-metadata.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"
|
|
4
|
+
"componentCatalog": "not-applicable",
|
|
5
5
|
"components": [
|
|
6
6
|
{
|
|
7
7
|
"id": "counter",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"boundaries": {
|
|
55
55
|
"rootClass": "kerf-counter",
|
|
56
56
|
"publicClasses": ["kerf-counter"],
|
|
57
|
-
"publicTokens": []
|
|
57
|
+
"publicTokens": ["--kerf-counter-gap"]
|
|
58
58
|
},
|
|
59
59
|
"accessibility": {
|
|
60
60
|
"obligations": [
|
package/template/package.json
CHANGED
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
"./counter": {
|
|
13
13
|
"types": "./dist/counter.d.ts",
|
|
14
14
|
"import": "./dist/counter.js"
|
|
15
|
-
}
|
|
15
|
+
},
|
|
16
|
+
"./counter.css": "./src/counter.css"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
18
19
|
"dist",
|
|
19
|
-
"
|
|
20
|
+
"src/counter.css",
|
|
21
|
+
"component-composition.json",
|
|
20
22
|
"kerf.components.json",
|
|
21
23
|
"README.md",
|
|
22
24
|
"LICENSE"
|
|
@@ -25,18 +27,20 @@
|
|
|
25
27
|
"build": "tsup",
|
|
26
28
|
"catalog:generate": "node scripts/kerf-component-catalog.mjs --write",
|
|
27
29
|
"catalog:check": "node scripts/kerf-component-catalog.mjs --check",
|
|
30
|
+
"check:styles": "kerf-ui-analyze --root . src",
|
|
28
31
|
"typecheck": "tsc --noEmit",
|
|
29
|
-
"prepublishOnly": "npm run catalog:check && npm run build"
|
|
32
|
+
"prepublishOnly": "npm run catalog:check && npm run check:styles && npm run build"
|
|
30
33
|
},
|
|
31
34
|
"kerfComponentCatalog": {
|
|
32
35
|
"source": "./kerf.components.json",
|
|
33
|
-
"output": "./component-
|
|
36
|
+
"output": "./component-composition.json"
|
|
34
37
|
},
|
|
35
38
|
"peerDependencies": {
|
|
36
39
|
"kerfjs": "^5.0.0-0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
|
-
"kerfjs": "^5.0.0-beta.
|
|
42
|
+
"kerfjs": "^5.0.0-beta.58",
|
|
43
|
+
"@kerfjs/ui": "^5.0.0-beta.58",
|
|
40
44
|
"tsup": "^8",
|
|
41
45
|
"typescript": "^5 || ^6"
|
|
42
46
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Counter owns this stylesheet, and this stylesheet styles only Counter.
|
|
3
|
+
*
|
|
4
|
+
* - It selects `.kerf-counter` and Counter's own internals, never another
|
|
5
|
+
* component's class, `[data-component]` root, `wa-*` element, or `::part()`.
|
|
6
|
+
* - It writes only Counter's own `--_kerf-counter-*` private variables. The
|
|
7
|
+
* context Counter hands its buttons is named after Counter, the provider.
|
|
8
|
+
* - Consumers configure Counter through its typed props (`size`) and its one
|
|
9
|
+
* public theme token (`--kerf-counter-gap`), never by restyling
|
|
10
|
+
* `.kerf-counter` from their own CSS. A need with no prop is a gap to add to
|
|
11
|
+
* Counter, not an override for the consumer to write.
|
|
12
|
+
*/
|
|
13
|
+
.kerf-counter {
|
|
14
|
+
--_kerf-counter-gap: var(--kerf-counter-gap, 0.5rem);
|
|
15
|
+
--_kerf-counter-control-size: 2rem;
|
|
16
|
+
display: inline-flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: var(--_kerf-counter-gap);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.kerf-counter[data-size="compact"] {
|
|
22
|
+
--_kerf-counter-control-size: 1.5rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.kerf-counter > button {
|
|
26
|
+
inline-size: var(--_kerf-counter-control-size);
|
|
27
|
+
block-size: var(--_kerf-counter-control-size);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/*
|
|
31
|
+
* Parent context: when a Counter sits in a Kerf UI Toolbar, Counter adapts
|
|
32
|
+
* itself here, in its own stylesheet. The parent appears only as an ancestor
|
|
33
|
+
* and Counter stays the subject, so the Toolbar never styles Counter and
|
|
34
|
+
* Counter never styles the Toolbar.
|
|
35
|
+
*/
|
|
36
|
+
.kui-toolbar .kerf-counter {
|
|
37
|
+
--_kerf-counter-control-size: 1.75rem;
|
|
38
|
+
}
|
package/template/src/counter.tsx
CHANGED
|
@@ -28,6 +28,11 @@ export type CounterStore = ReturnType<typeof createCounter>;
|
|
|
28
28
|
export interface CounterProps {
|
|
29
29
|
store: CounterStore;
|
|
30
30
|
label?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Control density. A typed prop is how consumers configure the component's
|
|
33
|
+
* look; they never restyle `.kerf-counter` from their own CSS.
|
|
34
|
+
*/
|
|
35
|
+
size?: 'standard' | 'compact';
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
/**
|
|
@@ -35,10 +40,17 @@ export interface CounterProps {
|
|
|
35
40
|
* `data-action` hooks instead of inline event handlers — inline `onClick={...}`
|
|
36
41
|
* handlers don't survive kerf's morph (and the `no-inline-jsx-event-handlers`
|
|
37
42
|
* lint rule flags them). The host wires the events; see `wireCounter` below.
|
|
43
|
+
*
|
|
44
|
+
* Its look lives in `counter.css` (the `./counter.css` export), which styles
|
|
45
|
+
* only Counter; `size` is the configuration consumers use instead of CSS.
|
|
38
46
|
*/
|
|
39
|
-
export function Counter({
|
|
47
|
+
export function Counter({
|
|
48
|
+
store,
|
|
49
|
+
label = 'Count',
|
|
50
|
+
size = 'standard',
|
|
51
|
+
}: CounterProps): SafeHtml {
|
|
40
52
|
return (
|
|
41
|
-
<div class="kerf-counter">
|
|
53
|
+
<div class="kerf-counter" data-size={size}>
|
|
42
54
|
<button type="button" data-action="counter:dec" aria-label="Decrement">
|
|
43
55
|
−
|
|
44
56
|
</button>
|