@keenmate/web-components-devkit 0.0.0-stage → 1.0.0-rc02

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.
Files changed (109) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/LICENSE +21 -0
  3. package/README.md +434 -2
  4. package/components.json +206 -0
  5. package/package.json +77 -4
  6. package/src/cli/args.js +16 -0
  7. package/src/cli/commands/check-release.js +66 -0
  8. package/src/cli/commands/check-vars.js +116 -0
  9. package/src/cli/commands/doctor.js +289 -0
  10. package/src/cli/commands/new.js +323 -0
  11. package/src/cli/commands/readme.js +111 -0
  12. package/src/cli/commands/sync.js +48 -0
  13. package/src/cli/kmwc.js +73 -0
  14. package/src/cli/sync-plan.js +163 -0
  15. package/src/config.d.ts +149 -0
  16. package/src/config.js +279 -0
  17. package/src/examples/auto.js +20 -0
  18. package/src/examples/chapter-nav.js +246 -0
  19. package/src/examples/constants.js +80 -0
  20. package/src/examples/controls-persistence.js +97 -0
  21. package/src/examples/copy-link.js +89 -0
  22. package/src/examples/css/base.css +215 -0
  23. package/src/examples/css/chapter-nav.css +179 -0
  24. package/src/examples/css/content.css +251 -0
  25. package/src/examples/css/controls.css +205 -0
  26. package/src/examples/css/copy-link.css +82 -0
  27. package/src/examples/css/demo.css +50 -0
  28. package/src/examples/css/index-page.css +118 -0
  29. package/src/examples/css/style.css +30 -0
  30. package/src/examples/css/theme-switch.css +38 -0
  31. package/src/examples/css/tokens.css +124 -0
  32. package/src/examples/css/widgets.css +133 -0
  33. package/src/examples/demo-code.js +30 -0
  34. package/src/examples/index-page.js +78 -0
  35. package/src/examples/index.d.ts +107 -0
  36. package/src/examples/index.js +18 -0
  37. package/src/examples/kit.js +22 -0
  38. package/src/examples/page-config.js +30 -0
  39. package/src/examples/sections.js +53 -0
  40. package/src/examples/theme-switch.js +72 -0
  41. package/src/examples/version-badge.js +23 -0
  42. package/src/playwright/index.d.ts +15 -0
  43. package/src/playwright/index.js +66 -0
  44. package/src/template.js +11 -0
  45. package/src/vite/index.d.ts +28 -0
  46. package/src/vite/index.js +243 -0
  47. package/src/vite/page-composer.js +216 -0
  48. package/templates/Dockerfile.tmpl +39 -0
  49. package/templates/Makefile.tmpl +136 -0
  50. package/templates/claude-block.md +40 -0
  51. package/templates/dockerignore.tmpl +10 -0
  52. package/templates/examples-layout.html +13 -0
  53. package/templates/new/.claude/commands/publish.md +226 -0
  54. package/templates/new/CHANGELOG.md +13 -0
  55. package/templates/new/LICENSE +21 -0
  56. package/templates/new/README.md +71 -0
  57. package/templates/new/_gitignore +7 -0
  58. package/templates/new/component-variables.manifest.json +32 -0
  59. package/templates/new/custom-elements-manifest.config.mjs +23 -0
  60. package/templates/new/docs/accessibility.md +24 -0
  61. package/templates/new/docs/examples.md +33 -0
  62. package/templates/new/docs/theming.md +54 -0
  63. package/templates/new/docs/usage.md +51 -0
  64. package/templates/new/e2e/dark-mode.spec.ts +30 -0
  65. package/templates/new/e2e/smoke.spec.ts +26 -0
  66. package/templates/new/examples-basic.html +57 -0
  67. package/templates/new/examples-theming.html +28 -0
  68. package/templates/new/index.html +16 -0
  69. package/templates/new/partials/note.html +5 -0
  70. package/templates/new/playwright.config.ts +4 -0
  71. package/templates/new/src/__FEATURE__.ts +55 -0
  72. package/templates/new/src/css/animations.css +1 -0
  73. package/templates/new/src/css/base.css +43 -0
  74. package/templates/new/src/css/controls.css +20 -0
  75. package/templates/new/src/css/dark-mode.css +36 -0
  76. package/templates/new/src/css/floating.css +1 -0
  77. package/templates/new/src/css/main.css +27 -0
  78. package/templates/new/src/css/states.css +22 -0
  79. package/templates/new/src/css/variables.css +42 -0
  80. package/templates/new/src/env.d.ts +7 -0
  81. package/templates/new/src/index.ts +17 -0
  82. package/templates/new/src/logger.ts +11 -0
  83. package/templates/new/src/types.ts +25 -0
  84. package/templates/new/src/web-component.ts +73 -0
  85. package/templates/new/test/dark-mode.html +18 -0
  86. package/templates/new/test/fixture.html +11 -0
  87. package/templates/new/tsconfig.json +15 -0
  88. package/templates/new/vite.config.ts +5 -0
  89. package/templates/new-minimal/CHANGELOG.md +13 -0
  90. package/templates/new-minimal/README.md +71 -0
  91. package/templates/new-minimal/component-variables.manifest.json +24 -0
  92. package/templates/new-minimal/docs/accessibility.md +20 -0
  93. package/templates/new-minimal/docs/examples.md +17 -0
  94. package/templates/new-minimal/docs/theming.md +53 -0
  95. package/templates/new-minimal/docs/usage.md +43 -0
  96. package/templates/new-minimal/e2e/smoke.spec.ts +10 -0
  97. package/templates/new-minimal/examples-basic.html +23 -0
  98. package/templates/new-minimal/examples-theming.html +28 -0
  99. package/templates/new-minimal/src/__FEATURE__.ts +37 -0
  100. package/templates/new-minimal/src/css/base.css +48 -0
  101. package/templates/new-minimal/src/css/controls.css +1 -0
  102. package/templates/new-minimal/src/css/states.css +1 -0
  103. package/templates/new-minimal/src/css/variables.css +28 -0
  104. package/templates/new-minimal/src/index.ts +17 -0
  105. package/templates/new-minimal/src/types.ts +10 -0
  106. package/templates/new-minimal/src/web-component.ts +48 -0
  107. package/templates/new-minimal/test/dark-mode.html +18 -0
  108. package/templates/new-minimal/test/fixture.html +11 -0
  109. package/templates/nginx.conf.tmpl +43 -0
@@ -0,0 +1,226 @@
1
+ ---
2
+ description: Prepare {{PACKAGE_NAME}} for npm publish — bump version, finalize CHANGELOG/README, build, test, commit
3
+ argument-hint: rc|release|patch|minor|major
4
+ ---
5
+
6
+ # /publish — prepare an npm release of {{PACKAGE_NAME}}
7
+
8
+ You are preparing this package for `npm publish`. **Do not run `npm publish`** — the user logs in and publishes manually.
9
+
10
+ This command follows the canonical `/publish` structure defined in the BlissFramework component guidelines at
11
+ `web-components/publish-command.md`. Sections marked **[canonical]** are byte-identical across every
12
+ component's `/publish`; sections marked **[per-repo]** are customized for this repo's layout, build, and tests.
13
+
14
+ ## Argument [canonical]
15
+
16
+ The release type: **$ARGUMENTS**
17
+
18
+ Must be one of:
19
+
20
+ - `rc` — ship the WIP rc as-is. The topmost CHANGELOG heading (e.g. `## [1.10.0-rc02] - 2026-06-18`) gets ` [PUBLISHED]` appended.
21
+ - `release` — promote a WIP rc to a final release. `X.Y.Z-rcN` → `X.Y.Z`. CHANGELOG heading is renamed to match the new version.
22
+ - `patch` — SemVer patch bump. Drops any `-rc` suffix.
23
+ - `minor` — SemVer minor bump. Drops `-rc`. Resets patch.
24
+ - `major` — SemVer major bump. Drops `-rc`. Resets minor and patch.
25
+
26
+ If missing or invalid, stop and ask the user which one to use (don't guess).
27
+
28
+ ## Repo layout [per-repo]
29
+
30
+ Single-package repo, everything at root:
31
+
32
+ - **`./package.json`** — `version` field is the source of truth.
33
+ - **`./CHANGELOG.md`** — at the root. Topmost `## [X.Y.Z] - YYYY-MM-DD` heading **without** the `[PUBLISHED]` marker is the WIP section.
34
+ - **`./README.md`** — at the root. Carries `## What's New in vX.Y.Z` sections near the top (one per release, the **two most recent** retained).
35
+ - **`./dist/`** — gitignored. Produced by `npm run build` (custom-elements-manifest analyze + Vite). Never staged.
36
+ - **`./custom-elements.json`, `./web-types.json`, `./vscode.html-custom-data.json`, `./vscode.css-custom-data.json`** — generated by `npm run analyze` (part of the build) and **committed** (custom-elements-manifest D-CEM-2 = A).
37
+ - **`./component-variables.manifest.json`** — published with the package. Edit if the variable surface changes; `npm run check:vars` validates it.
38
+ - **`./docs/`** — usage / theming / examples / accessibility, published with the package.
39
+
40
+ ## CHANGELOG convention in this repo [canonical]
41
+
42
+ There is **no `## [Unreleased]` section**. The WIP section is the topmost `## [X.Y.Z] - YYYY-MM-DD` heading without a `[PUBLISHED]` tag. Already-released sections carry `[PUBLISHED]` at the end of their heading:
43
+
44
+ ```
45
+ ## [1.10.0-rc02] - 2026-06-18 ← WIP, the one you're shipping
46
+ ### Added
47
+ - ...
48
+
49
+ ## [1.9.0] - 2026-06-09 [PUBLISHED]
50
+ ### Added
51
+ - ...
52
+ ```
53
+
54
+ Publishing the WIP section means **appending ` [PUBLISHED]`** to its heading — exact format: `## [X.Y.Z] - YYYY-MM-DD [PUBLISHED]`. The next development cycle creates a fresh `## [next-version] - <date>` heading on its first CHANGELOG edit.
55
+
56
+ ## Resolve versions [canonical]
57
+
58
+ Read `./package.json` `version` as `CURRENT_VERSION`.
59
+ Read the topmost `## [X.Y.Z...]` heading from `./CHANGELOG.md` as `WIP_VERSION` (the version the latest WIP section is tagged for).
60
+
61
+ Compute `NEW_VERSION`:
62
+
63
+ | Argument | Logic |
64
+ |---|---|
65
+ | `rc` | If `CURRENT_VERSION` matches `X.Y.Z-rcN`, `NEW_VERSION = CURRENT_VERSION` (no bump — we're shipping what's already in package.json). If `CURRENT_VERSION` is not an rc, stop and ask the user (they probably wanted `release`/`patch`/etc.). |
66
+ | `release` | If `CURRENT_VERSION` matches `X.Y.Z-rcN`, `NEW_VERSION = X.Y.Z`. Otherwise stop. |
67
+ | `patch` | Strip any `-rcN`, then bump patch. |
68
+ | `minor` | Strip any `-rcN`, then bump minor, reset patch. |
69
+ | `major` | Strip any `-rcN`, then bump major, reset minor and patch. |
70
+
71
+ If `WIP_VERSION` ≠ `NEW_VERSION` (e.g. the WIP is `X.Y.Z-rcN` but the user asked for `release`), the CHANGELOG heading rename in step 3 also re-tags the section to `NEW_VERSION` — call this out in the report so the user notices.
72
+
73
+ ## Steps (in order)
74
+
75
+ ### 1. Sanity checks [canonical]
76
+
77
+ - Run `git status`. The repo intentionally keeps `.claude/`, `test-results/`, `playwright-report/` and `dist-examples/` untracked — those are fine. If there are **other** uncommitted changes that aren't `CHANGELOG.md`, `README.md`, or `package.json`, list them and ask the user before continuing. (Typical case: substantive source changes belonging in this release that haven't been committed yet — confirm they're intended for this version before bumping.)
78
+ - **Verify the new version isn't already on npm.** Run `npm view {{PACKAGE_NAME}}@<NEW_VERSION> version 2>/dev/null` — if it returns the version string, that version is already published and **stop**: bumping over it would fail at publish time and pollute the commit.
79
+ - **Verify the registry hasn't drifted past you.** Run `npm view {{PACKAGE_NAME}} version` to fetch the latest published version on the `latest` tag; if it's higher than `NEW_VERSION` (e.g. someone shipped from another machine, or there's a registry-vs-local mismatch from before the [PUBLISHED] convention landed), warn the user and ask before continuing.
80
+ - Confirm the WIP CHANGELOG section has at least one bullet of substantive content under `### Added`, `### Changed`, `### Removed`, `### Fixed`, or `### Internal`. If empty, stop — there's nothing meaningful to release.
81
+ - Confirm `./README.md` has a `## What's New in vWIP_VERSION` section. If it's missing, draft one from the CHANGELOG and present it to the user for approval before continuing:
82
+ - Read the WIP CHANGELOG section, distill it to 5–8 scannable bullets covering the Added/Changed themes (paraphrase, don't copy CHANGELOG bullets verbatim — those are exhaustive; What's New is the highlight reel). Pure internal refactors and Fixed-only entries don't need coverage, though headline bug fixes worth advertising are worth a bullet.
83
+ - **Follow the canonical "What's New" format** defined in the BlissFramework component guidelines (`web-components/readme-structure.md` → "`## What's New in vX.Y.Z` — canonical format"). Auto-check **C-RS-16** in `readme-structure.checks.sh` enforces it. Concretely:
84
+ - **Heading:** `## What's New in vNEW_VERSION` — lowercase `v`, no backticks around the version, no date.
85
+ - **Each bullet:** `- **<area or component> — <one-line headline>** — <engineer-level prose, 3–8 sentences>`. Bold-wrapped lead phrase, then a true em-dash (` — `, U+2014 with surrounding spaces), then a prose body explaining *what changed*, *why* (regression history / motivation), *what surface is affected* (concrete component / prop / file names listed inline), and *the mechanism* (the technique used). Plain hyphens or en-dashes fail the check.
86
+ - **No `### ` sub-headings** inside a What's New section — no `### Added` / `### Fixed` lifted from the CHANGELOG. It's a flat bullet list.
87
+ - **Reference implementation:** the `## What's New in v1.3.3` / `v1.3.2` sections at the top of `svelte-fluentui/README.md` are the canonical shape — mirror that voice and structure.
88
+ - Show the user the proposed draft as plain markdown in your reply. Ask whether to (a) insert as-is, (b) edit, or (c) abort so they can write it themselves.
89
+ - Only proceed past step 1 once the user approves the draft (or supplies their own). On approval, insert the section directly above the current top `## What's New in vX.Y.Z` heading in `./README.md`, then continue.
90
+ - Do not silently insert the draft without confirmation — release highlights are a writing call and the user owns the voice.
91
+
92
+ ### 2. Bump version (if needed) [canonical]
93
+
94
+ If `NEW_VERSION` ≠ `CURRENT_VERSION`, edit `./package.json` and change `"version": "CURRENT_VERSION"` to `"version": "NEW_VERSION"`.
95
+
96
+ For `rc` arg this is normally a no-op — version was bumped earlier in the development cycle.
97
+
98
+ ### 3. Finalize CHANGELOG [canonical]
99
+
100
+ In `./CHANGELOG.md`:
101
+
102
+ - If `WIP_VERSION` ≠ `NEW_VERSION` (e.g. promoting `X.Y.Z-rcN` → `X.Y.Z`), rename the WIP heading from `## [WIP_VERSION] - <date>` to `## [NEW_VERSION] - <today>` (today's date from system context).
103
+ - If `WIP_VERSION` == `NEW_VERSION`, leave the bracketed version alone but update the date to today **if** the existing date is stale (more than a few days old). The WIP date is usually whatever day the section was opened; refresh it so the changelog reflects the actual ship date.
104
+ - In either case, **append ` [PUBLISHED]`** to the heading so it reads exactly: `## [NEW_VERSION] - YYYY-MM-DD [PUBLISHED]`.
105
+ - Leave all bullet content untouched.
106
+ - **Do not** create an empty new WIP section — the next dev cycle's first CHANGELOG edit will create one.
107
+
108
+ ### 4. Update README "What's New" — only if version changed [canonical]
109
+
110
+ In `./README.md`:
111
+
112
+ - If the existing `## What's New in vWIP_VERSION` section's version differs from `NEW_VERSION` (e.g. promoting `X.Y.Z-rcN` → `X.Y.Z`), rename its heading to `## What's New in vNEW_VERSION`. (No content rewrites — the text was already curated for this release.)
113
+ - Then count the `## What's New in vX.Y.Z` headings. If there are more than **two**, delete the oldest ones so only the **two most recent** remain (the just-finalized one plus the one before it).
114
+
115
+ For `rc` arg this is normally a no-op on the heading itself — only trims if someone left an extra-old section behind.
116
+
117
+ ### 5. Validate README reflects the release [canonical]
118
+
119
+ Read both the finalized CHANGELOG section and the matching `What's New in vNEW_VERSION` section. Every **Added** or **Changed** bullet in the CHANGELOG that represents a user-facing feature or behavior change should have a corresponding hit in the What's New section (paraphrased, not verbatim). Pure internal refactors and `Fixed`-only entries don't need coverage, though headline bug fixes worth advertising (e.g. "X used to silently fail; now works") are worth a bullet.
120
+
121
+ If you find a significant CHANGELOG entry that isn't reflected in What's New, add a bullet for it. If the section ends up with more than ~8 bullets after this pass, condense — What's New should be scannable, not exhaustive.
122
+
123
+ ### 6. Validate CHANGELOG entries match recent work [canonical]
124
+
125
+ Find the previous `[PUBLISHED]` tag in CHANGELOG (the version just before NEW_VERSION) and locate the commit that bumped to it — usually a commit whose subject starts with `v<previous-version>` or `- v<previous-version>`. Run `git log --oneline <previous-publish-commit>..HEAD` to list commits since.
126
+
127
+ Also check `git diff` (or `git status`) for any uncommitted source/test work outside the files you're editing in this command.
128
+
129
+ For every substantive commit or uncommitted change, verify the WIP CHANGELOG section mentions it. If something significant is missing, **stop and ask the user** before finalizing — don't invent entries on their behalf. Pure example/doc tweaks and trivial typo fixes don't need entries.
130
+
131
+ ### 7. Run tests [per-repo]
132
+
133
+ Run `npm run test:e2e` (or equivalently `make test-e2e`). e2e tests via Playwright. All specs must pass.
134
+
135
+ If anything fails, **stop and report**. Do not proceed to build/commit. The user fixes the regression (or decides to fixme the spec) before the publish flow can continue.
136
+
137
+ If Playwright complains that chromium isn't installed, suggest `make test-e2e-install` and stop.
138
+
139
+ ### 8. Build the package [per-repo]
140
+
141
+ Run `npm run build` (or `make build`). This:
142
+ - Runs `npm run analyze` — regenerates `custom-elements.json`, `web-types.json` and the VS Code custom-data files.
143
+ - Runs Vite to emit `dist/{{FILE_NAME}}.js`, `dist/{{FILE_NAME}}.umd.js`, `dist/style.css`, and the type declarations.
144
+
145
+ If the build errors, stop and report.
146
+
147
+ After build, do a quick smoke check on the emitted artifacts:
148
+ - `dist/{{FILE_NAME}}.js` and `dist/{{FILE_NAME}}.umd.js` both exist and are non-empty.
149
+ - `dist/style.css` exists.
150
+ - `dist/index.d.ts` (or whatever the `types` field points to) exists.
151
+ - `git status` shows the regenerated CEM files only if the element API changed — stage them in step 10 if so.
152
+
153
+ ### 9. Verify the package contents [per-repo]
154
+
155
+ Run `npm pack --dry-run` and confirm the file list includes:
156
+
157
+ - `dist/` (built JS + d.ts files + style.css)
158
+ - `custom-elements.json`, `web-types.json`, `vscode.html-custom-data.json`, `vscode.css-custom-data.json`
159
+ - `component-variables.manifest.json`
160
+ - `docs/`
161
+ - `README.md`
162
+ - `LICENSE`
163
+ - `package.json`
164
+
165
+ If anything user-facing is missing or anything private leaked in (e.g. `test/`, `e2e/`, `examples-*.html`, `*.spec.ts`, `tsconfig.json`, `vite.config.ts`), stop and report — the `files` field in `package.json` controls this and the leak needs fixing before publish.
166
+
167
+ ### 10. Commit [canonical]
168
+
169
+ Stage:
170
+
171
+ - `./CHANGELOG.md`
172
+ - `./README.md`
173
+ - `./package.json`
174
+ - `./custom-elements.json`, `./web-types.json`, `./vscode.html-custom-data.json`, `./vscode.css-custom-data.json` (if the build regenerated them)
175
+
176
+ Do **not** stage `dist/` — it's gitignored.
177
+
178
+ Commit message format:
179
+
180
+ ```
181
+ vNEW_VERSION - <one-line summary of the headline change>
182
+
183
+ <grouped bullets paraphrased from the CHANGELOG section — split into the same
184
+ groups the CHANGELOG used: Added, Fixed, Changed, Internal, etc. Keep bullets
185
+ terse; full prose lives in the CHANGELOG.>
186
+
187
+ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
188
+ ```
189
+
190
+ ### 11. Report [canonical]
191
+
192
+ Report back with:
193
+
194
+ - The new version number
195
+ - The commit SHA
196
+ - The exact commands to publish. **Pick the right one for the arg type:**
197
+ - For `rc` (publishing a pre-release):
198
+ ```
199
+ npm login # if not already logged in
200
+ npm publish --tag rc
201
+ ```
202
+ The `--tag rc` is critical — without it npm assigns the `latest` dist-tag, which would make the pre-release the default install for everyone running `npm install {{PACKAGE_NAME}}`. With `--tag rc`, the `latest` tag stays put and consumers opt in via `@rc` or pinning the exact version.
203
+ - For `release` / `patch` / `minor` / `major` (publishing a stable release):
204
+ ```
205
+ npm login # if not already logged in
206
+ npm publish
207
+ ```
208
+ Or `make publish`. No `--tag` needed — it correctly lands as `latest`.
209
+ - A reminder that the CHANGELOG `[PUBLISHED]` tag is now in place — if `npm publish` fails, the user should revert both the tag (CHANGELOG heading) and the version bump (`package.json`) before retrying, since the registry will refuse to re-publish the same version.
210
+
211
+ ## Things not to do [canonical]
212
+
213
+ - **Do not run `npm publish`.** The user publishes manually after `npm login`.
214
+ - **Do not push to git remote.** The commit stays local until the user pushes.
215
+ - **Do not create an empty `[Unreleased]` or new WIP heading** in CHANGELOG after finalizing — the next dev cycle's first edit creates the next heading.
216
+ - **Do not retro-fix older CHANGELOG sections** that are missing the `[PUBLISHED]` tag or carry legacy markers — only finalize the section you're shipping.
217
+ - **Do not silently insert a drafted What's New section.** If you draft one in Step 1 because it's missing, you must present it and wait for explicit approval (or edits) before inserting — the writing voice is the user's call, even when you're handing them a starting point.
218
+ - **Do not keep more than two `## What's New in vX.Y.Z` sections in the README.** Step 4 trims older ones; if you see three or more after Step 4, you missed one.
219
+ - **Do not skip the build step** — without it `dist/` is stale and the publish would ship outdated artifacts (or fail entirely if `dist/` was wiped by `make clean`).
220
+ - **Do not skip the test gate** if the repo has one — the gate is what catches regressions before they ship.
221
+ - **Do not invent CHANGELOG entries** to cover commits you find; ask the user if something's missing.
222
+ - **Do not bump if there's nothing meaningful in the WIP section** — stop and explain.
223
+
224
+ ### Repo-specific don'ts
225
+
226
+ - **Do not stage `test/`, `e2e/`, `test-results/`, `playwright-report/` or `dist-examples/`** — they're either ignored or test artifacts.
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## [{{VERSION}}] - {{TODAY}}
4
+
5
+ ### Added
6
+
7
+ - Initial scaffold (`kmwc new`, `@keenmate/web-components-devkit`): `<{{TAG}}>` element (`{{ELEMENT_CLASS}}`) over the `{{LOGIC_CLASS}}` logic class, `label` / `active` inputs, `change` event with the `onChange` property twin.
8
+ - Canonical CSS set (`src/css/`, prefix `{{PREFIX}}`) with `@layer variables, component, overrides`, `--base-*` fallback chains and Strategy-B dark mode.
9
+ - Custom Elements Manifest (`custom-elements.json`, `web-types.json`, VS Code custom data) and `component-variables.manifest.json`.
10
+
11
+ ### Internal
12
+
13
+ - Intake: technology web-component · package `{{PACKAGE_NAME}}` · tag `<{{TAG}}>` · CSS prefix `{{PREFIX}}` (reserve it in BlissFramework `guidelines/web-components/base-variables.md`) · dev port {{DEV_PORT}}.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) {{YEAR}} Keenmate s.r.o.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,71 @@
1
+ # {{PACKAGE_NAME}}
2
+
3
+ > {{TAGLINE}}
4
+
5
+ ## What is it
6
+
7
+ `{{PACKAGE_NAME}}` is a custom element (`<{{TAG}}>`) built on
8
+ [`@keenmate/web-components-core`](https://www.npmjs.com/package/@keenmate/web-components-core).
9
+ It works in any HTML page or framework that can render a custom element —
10
+ React, Vue, Svelte, Blazor, plain HTML.
11
+
12
+ It reads `--base-*` variables from the page when `@keenmate/theme-designer`
13
+ is present, falls back to sensible light/dark defaults otherwise, and supports
14
+ per-instance dark mode via `data-theme`.
15
+
16
+ ## What's New in v{{VERSION}}
17
+
18
+ - **`<{{TAG}}>` — initial release** — First version, scaffolded with `kmwc new` from `@keenmate/web-components-devkit`. Ships the Element / Logic / Side-layer split, the canonical eight-file CSS set with `@layer variables, component, overrides`, light/dark defaults through `light-dark()` fallbacks, a Custom Elements Manifest for editor tooling, and a `change` event with an `onChange` property twin. See [CHANGELOG.md](./CHANGELOG.md).
19
+
20
+ ## Demos & docs
21
+
22
+ - 🚀 [Live demo](https://demos.keenmate.com/{{ID}})
23
+ - 📘 [Usage / API reference](./docs/usage.md)
24
+ - 🎨 [Theming contract](./docs/theming.md)
25
+ - 📚 [Examples / cookbook](./docs/examples.md)
26
+ - ♿ [Accessibility](./docs/accessibility.md)
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install {{PACKAGE_NAME}}
32
+ ```
33
+
34
+ ## Quick start
35
+
36
+ ```html
37
+ <script type="module">
38
+ import '{{PACKAGE_NAME}}';
39
+ </script>
40
+
41
+ <{{TAG}} label="Hello"></{{TAG}}>
42
+ ```
43
+
44
+ Clicking toggles the `active` state and dispatches a `change` event
45
+ (`event.detail.isActive`). See [usage.md](./docs/usage.md) for the full API.
46
+
47
+ ## Browser support
48
+
49
+ Modern evergreen browsers (Chrome, Edge, Firefox, Safari) — anything with
50
+ native `customElements`, CSS `@layer` and `light-dark()` support. No polyfills
51
+ shipped.
52
+
53
+ ## About
54
+
55
+ Authored and maintained by [Keenmate s.r.o.](https://keenmate.com/).
56
+ The component ships standalone with sensible light/dark defaults;
57
+ when mounted inside [Pure Admin](https://pureadmin.io/) — or any
58
+ host that publishes the `--base-*` taxonomy via
59
+ [`@keenmate/theme-designer`](https://www.npmjs.com/package/@keenmate/theme-designer) — it adopts the host's colors,
60
+ typography, and sizing automatically. There is no runtime
61
+ dependency on Pure Admin; the integration is opt-in via CSS
62
+ variables.
63
+
64
+ ## Built with BlissFramework
65
+
66
+ Follows the [BlissFramework component guidelines](https://blissframework.dev/)
67
+ for structure, theming, color-scheme, and accessibility.
68
+
69
+ ## License
70
+
71
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ dist/
3
+ dist-examples/
4
+ test-results/
5
+ playwright-report/
6
+ *.tgz
7
+ .makefile.env
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/keenmate/schemas/main/component-variables.schema.json",
3
+ "component": "{{PACKAGE_NAME}}",
4
+ "prefix": "{{PREFIX}}",
5
+ "baseVariables": [
6
+ { "name": "base-main-bg", "required": false, "usage": "Component surface" },
7
+ { "name": "base-text-color-1", "required": false, "usage": "Label text" },
8
+ { "name": "base-border-color", "required": false, "usage": "Button border" },
9
+ { "name": "base-primary-color", "required": false, "usage": "Active state fill, focus outline" },
10
+ { "name": "base-on-primary-text-color", "required": false, "usage": "Label text in the active state" },
11
+ { "name": "base-hover-bg", "required": false, "usage": "Hover background (falls back to the contract formula on --{{PREFIX}}-bg)" },
12
+ { "name": "base-font-family", "required": false, "usage": "Font family" },
13
+ { "name": "base-font-size-base", "required": false, "usage": "Font size (multiplier × --{{PREFIX}}-rem)" },
14
+ { "name": "base-border-radius-md", "required": false, "usage": "Corner radius (multiplier × --{{PREFIX}}-rem)" },
15
+ { "name": "base-input-size-md-height", "required": false, "usage": "Minimum height (multiplier × --{{PREFIX}}-rem)" }
16
+ ],
17
+ "componentVariables": [
18
+ { "name": "{{PREFIX}}-rem", "category": "spacing", "usage": "Base unit for every size (default 10px; 1rem follows the page font size)" },
19
+ { "name": "{{PREFIX}}-bg", "category": "surface", "usage": "Component surface" },
20
+ { "name": "{{PREFIX}}-text-color-1", "category": "text", "usage": "Label text" },
21
+ { "name": "{{PREFIX}}-border-color", "category": "border", "usage": "Button border" },
22
+ { "name": "{{PREFIX}}-accent-color", "category": "accent", "usage": "Active state fill, focus outline" },
23
+ { "name": "{{PREFIX}}-text-color-on-accent", "category": "text", "usage": "Label text in the active state" },
24
+ { "name": "{{PREFIX}}-hover-bg", "category": "state", "usage": "Hover background" },
25
+ { "name": "{{PREFIX}}-font-family", "category": "typography", "usage": "Font family" },
26
+ { "name": "{{PREFIX}}-font-size-base", "category": "typography", "usage": "Font size" },
27
+ { "name": "{{PREFIX}}-root-border-radius", "category": "radius", "usage": "Corner radius" },
28
+ { "name": "{{PREFIX}}-root-min-height", "category": "spacing", "usage": "Minimum height" },
29
+ { "name": "{{PREFIX}}-root-padding-block", "category": "spacing", "usage": "Vertical padding" },
30
+ { "name": "{{PREFIX}}-root-padding-inline", "category": "spacing", "usage": "Horizontal padding" }
31
+ ]
32
+ }
@@ -0,0 +1,23 @@
1
+ import { blissAnalyzerConfig, cssVariablesFromManifestPlugin } from '@keenmate/web-components-core/cem';
2
+ import { customElementVsCodePlugin } from 'custom-element-vs-code-integration';
3
+ import { customElementJetBrainsPlugin } from 'custom-element-jet-brains-integration';
4
+
5
+ /**
6
+ * Custom Elements Manifest (custom-elements-manifest.md). Core's
7
+ * blissAnalyzerConfig() reads the `static inputs` / `static events` tables
8
+ * (ATTRIBUTE_TABLE) straight from the AST, so attributes, properties and events
9
+ * in custom-elements.json come from the same table the runtime uses.
10
+ * cssVariablesFromManifestPlugin() adds the --{{PREFIX}}-* surface from
11
+ * component-variables.manifest.json; the editor plugins then emit
12
+ * vscode.html-custom-data.json / vscode.css-custom-data.json / web-types.json.
13
+ */
14
+ export default blissAnalyzerConfig({
15
+ globs: ['src/**/*.ts'],
16
+ exclude: ['src/**/*.spec.ts', 'src/**/*.test.ts', 'src/**/*.d.ts', 'src/index.ts'],
17
+ outdir: '.',
18
+ plugins: [
19
+ cssVariablesFromManifestPlugin(),
20
+ customElementVsCodePlugin({ outdir: '.' }),
21
+ customElementJetBrainsPlugin({ outdir: '.', packageJson: false }),
22
+ ],
23
+ });
@@ -0,0 +1,24 @@
1
+ # Accessibility — `<{{TAG}}>`
2
+
3
+ ## Keyboard
4
+
5
+ | Key | Action |
6
+ |---|---|
7
+ | `Tab` / `Shift+Tab` | Move focus to / from the toggle button. |
8
+ | `Enter` / `Space` | Toggle the active state (native `<button>` behaviour). |
9
+
10
+ ## ARIA
11
+
12
+ The toggle is a native `<button>` with `aria-pressed` reflecting the active
13
+ state, so screen readers announce it as a toggle button. The label text is the
14
+ accessible name.
15
+
16
+ ## Focus
17
+
18
+ A visible `:focus-visible` outline uses `--{{PREFIX}}-accent-color`.
19
+
20
+ ## Contrast
21
+
22
+ Default colours meet WCAG AA in both light and dark schemes. When overriding
23
+ `--{{PREFIX}}-accent-color`, keep `--{{PREFIX}}-text-color-on-accent` legible
24
+ on it (4.5:1).
@@ -0,0 +1,33 @@
1
+ # Examples — `<{{TAG}}>`
2
+
3
+ Live examples: run `make dev` and open http://localhost:{{DEV_PORT}}.
4
+
5
+ ## Common patterns
6
+
7
+ ### React to the active state
8
+
9
+ Shows the event API and the `active` attribute reflecting the state.
10
+
11
+ ```html
12
+ <{{TAG}} id="toggle" label="Notifications"></{{TAG}}>
13
+ <script type="module">
14
+ import '{{PACKAGE_NAME}}';
15
+ const toggle = document.getElementById('toggle');
16
+ toggle.addEventListener('change', (event) => {
17
+ console.log('notifications', event.detail.isActive ? 'on' : 'off');
18
+ // the element also reflects the state: toggle.hasAttribute('active')
19
+ });
20
+ </script>
21
+ ```
22
+
23
+ ### A themed instance
24
+
25
+ Combines a `--base-*` override (all components) with a `--{{PREFIX}}-*` one
26
+ (this instance only), forced dark.
27
+
28
+ ```html
29
+ <style>
30
+ :root { --base-primary-color: #10b981; }
31
+ </style>
32
+ <{{TAG}} label="Dark" data-theme="dark" style="--{{PREFIX}}-root-border-radius: 999px"></{{TAG}}>
33
+ ```
@@ -0,0 +1,54 @@
1
+ # Theming — `<{{TAG}}>`
2
+
3
+ ## Container contract
4
+
5
+ The theme container is `:host` — the `<{{TAG}}>` element itself. It paints
6
+ its own surface (`--{{PREFIX}}-bg`), so it is visible standalone. Attach
7
+ `data-theme="dark|light"` to the element (per instance) or to any ancestor; the
8
+ framework conventions `[data-bs-theme]` (Bootstrap) and `.dark` / `.light`
9
+ (Tailwind and friends) are honoured too.
10
+
11
+ ## Variable contract
12
+
13
+ Two layers, never three: `--base-*` is the cross-component hook (set by
14
+ `@keenmate/theme-designer` or by you on `:root`), `--{{PREFIX}}-*` is the
15
+ component-local override. The full list, with categories, is in
16
+ [`component-variables.manifest.json`](../component-variables.manifest.json)
17
+ (exported as `{{PACKAGE_NAME}}/manifest`).
18
+
19
+ All sizes multiply through `--{{PREFIX}}-rem` (default `10px`; set `1rem` to
20
+ follow the page font size).
21
+
22
+ ```css
23
+ /* every Keenmate component on the page */
24
+ :root { --base-primary-color: #10b981; }
25
+
26
+ /* this component only */
27
+ {{TAG}} { --{{PREFIX}}-accent-color: #8b5cf6; }
28
+ ```
29
+
30
+ ## Color-scheme strategy
31
+
32
+ Strategy B (color-scheme.md): every colour falls back through
33
+ `light-dark(<light>, <dark>)`, and the component sets `color-scheme` on the
34
+ host only when you signal a theme (`data-theme`, `data-bs-theme`, `.dark`).
35
+ Without a signal it inherits the page's `color-scheme` — declare
36
+ `color-scheme: light dark` on your page to follow the OS.
37
+
38
+ ## Cascade-layer contract
39
+
40
+ ```css
41
+ @layer variables, component, overrides;
42
+ ```
43
+
44
+ Any unlayered rule of yours beats every rule of the component; any `--base-*`
45
+ on `:root` beats the `variables` layer. If your page ships a universal reset
46
+ (`* { margin: 0; padding: 0 }`), wrap it in a layer — an unlayered reset beats
47
+ the component's layered defaults.
48
+
49
+ ## BEM hooks
50
+
51
+ | Part | Class | `::part()` |
52
+ |---|---|---|
53
+ | The toggle button | `.{{PREFIX}}__root` (`.{{PREFIX}}__root--active` when active) | `::part(root)` |
54
+ | The label | `.{{PREFIX}}__label` | `::part(label)` |
@@ -0,0 +1,51 @@
1
+ # Usage — `<{{TAG}}>`
2
+
3
+ ## Attributes / properties
4
+
5
+ Every input works as an HTML attribute **and** a JS property (core input
6
+ table, `ATTRIBUTE_TABLE` in `src/web-component.ts`).
7
+
8
+ | Attribute | Property | Type | Default | Description |
9
+ |---|---|---|---|---|
10
+ | `label` | `label` | `string` | `"Hello from {{TAG}}"` | Text shown inside the component. |
11
+ | `active` | `isActive` | `boolean` (`bool-default-false`: present = true, `"false"` = false) | `false` | Whether the component is in its active (pressed) state. |
12
+
13
+ ## Events
14
+
15
+ | Event | `detail` (`{{EVENT_DETAIL_TYPE}}`) | Property twin | When |
16
+ |---|---|---|---|
17
+ | `change` | `{ isActive: boolean }` | `onChange` | The active state was toggled by the user. Bubbles, composed, not cancelable. |
18
+
19
+ ```js
20
+ const el = document.querySelector('{{TAG}}');
21
+ el.addEventListener('change', (event) => console.log(event.detail.isActive));
22
+ el.onChange = (event) => console.log(event.detail.isActive); // same event, property form
23
+ ```
24
+
25
+ ## Slots / children
26
+
27
+ None.
28
+
29
+ ## Methods
30
+
31
+ None beyond the core `BlissElement` API (`setAttributes()`, `batch()`, `whenSettled()`).
32
+
33
+ ## Lifecycle notes
34
+
35
+ The element builds its shadow DOM on first connect; attribute and property
36
+ changes are batched into one update per microtask (`await el.whenSettled()`
37
+ before reading rendered state in tests).
38
+
39
+ ## Editor integration
40
+
41
+ The package ships a Custom Elements Manifest plus VS Code custom data. Add to
42
+ `.vscode/settings.json` for attribute autocomplete in HTML:
43
+
44
+ ```json
45
+ {
46
+ "html.customData": ["./node_modules/{{PACKAGE_NAME}}/vscode.html-custom-data.json"],
47
+ "css.customData": ["./node_modules/{{PACKAGE_NAME}}/vscode.css-custom-data.json"]
48
+ }
49
+ ```
50
+
51
+ JetBrains IDEs pick up `web-types.json` automatically.
@@ -0,0 +1,30 @@
1
+ import { test, expect, type Page } from '@playwright/test';
2
+
3
+ // Dark-mode signals (rulebook color-scheme.md): page color-scheme, framework
4
+ // class on an ancestor, and the per-instance data-theme attribute.
5
+ const getHostBackground = (page: Page, selector: string) =>
6
+ page.locator(selector).evaluate((element) => getComputedStyle(element).backgroundColor);
7
+
8
+ test.describe('<{{TAG}}> dark mode', () => {
9
+ test('defaults to light, per-instance data-theme="dark" goes dark', async ({ page }) => {
10
+ await page.goto('/test/dark-mode.html');
11
+ expect(await getHostBackground(page, '#light-default')).toBe('rgb(255, 255, 255)');
12
+ expect(await getHostBackground(page, '#dark-instance')).toBe('rgb(30, 41, 59)');
13
+ });
14
+
15
+ test('an ancestor data-theme="dark" reaches the component', async ({ page }) => {
16
+ await page.goto('/test/dark-mode.html');
17
+ expect(await getHostBackground(page, '#dark-ancestor')).toBe('rgb(30, 41, 59)');
18
+ });
19
+
20
+ test('a page color-scheme of dark is inherited', async ({ page }) => {
21
+ await page.goto('/test/dark-mode.html');
22
+ await page.evaluate(() => (document.documentElement.style.colorScheme = 'dark'));
23
+ expect(await getHostBackground(page, '#light-default')).toBe('rgb(30, 41, 59)');
24
+ });
25
+
26
+ test('--base-* themes the component', async ({ page }) => {
27
+ await page.goto('/test/dark-mode.html');
28
+ expect(await getHostBackground(page, '#base-themed')).toBe('rgb(255, 0, 0)');
29
+ });
30
+ });
@@ -0,0 +1,26 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('<{{TAG}}>', () => {
4
+ test('renders the label and reacts to attribute changes', async ({ page }) => {
5
+ await page.goto('/test/fixture.html');
6
+ const label = page.locator('#subject .{{PREFIX}}__label');
7
+ await expect(label).toHaveText('fixture');
8
+
9
+ await page.evaluate(() => document.getElementById('subject')!.setAttribute('label', 'changed'));
10
+ await expect(label).toHaveText('changed');
11
+ });
12
+
13
+ test('click toggles the active state, reflects it and fires change', async ({ page }) => {
14
+ await page.goto('/test/fixture.html');
15
+ const changeDetails = page.evaluate(
16
+ () =>
17
+ new Promise<unknown>((resolve) =>
18
+ document.getElementById('subject')!.addEventListener('change', (event) => resolve((event as CustomEvent).detail), { once: true })
19
+ )
20
+ );
21
+ await page.locator('#subject .{{PREFIX}}__root').click();
22
+ expect(await changeDetails).toEqual({ isActive: true });
23
+ await expect(page.locator('#subject')).toHaveAttribute('active', '');
24
+ await expect(page.locator('#subject .{{PREFIX}}__root')).toHaveClass(/{{PREFIX}}__root--active/);
25
+ });
26
+ });