@mhmo91/schmancy 0.9.27 → 0.9.28

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.
@@ -102,18 +102,6 @@
102
102
  }
103
103
  ]
104
104
  },
105
- {
106
- "kind": "javascript-module",
107
- "path": "agent/schmancy-skill.ts",
108
- "declarations": [
109
- {
110
- "kind": "class",
111
- "name": "SchmancySkill",
112
- "tagName": "schmancy-skill",
113
- "description": "Self-describing runtime helper. Drop `<schmancy-skill></schmancy-skill>` once on a page and `window.schmancy.help('schmancy-button')` returns the machine-readable entry for any tag. Renders nothing."
114
- }
115
- ]
116
- },
117
105
  {
118
106
  "kind": "javascript-module",
119
107
  "path": "area/area.component.ts",
@@ -203,7 +203,7 @@ The manifest already has everything needed; the package is just the JSON-RPC wra
203
203
 
204
204
  **Problem.** `handover/agent-runtime-v1.md` had `<PENDING>` placeholders that we manually replaced with `0.9.13` after the first publish. Future handover docs will have the same issue.
205
205
 
206
- **Fix.** A build step that substitutes `0.9.27` in `handover/**/*.md` against `package.json`'s `version` field on every build. `dist/handover/**/*.md` gets the rendered version; the source stays templated.
206
+ **Fix.** A build step that substitutes `0.9.28` in `handover/**/*.md` against `package.json`'s `version` field on every build. `dist/handover/**/*.md` gets the rendered version; the source stays templated.
207
207
 
208
208
  **Effort:** ~30 min (one sed step or a tiny script).
209
209
 
@@ -7,8 +7,8 @@
7
7
  ## The URLs you asked for
8
8
 
9
9
  ```
10
- https://esm.sh/@mhmo91/schmancy/agent@0.9.27
11
- https://esm.sh/@mhmo91/schmancy/agent/manifest@0.9.27
10
+ https://esm.sh/@mhmo91/schmancy/agent@0.9.28
11
+ https://esm.sh/@mhmo91/schmancy/agent/manifest@0.9.28
12
12
  ```
13
13
 
14
14
  `0.9.13` is the first release containing `/agent`; every subsequent publish serves the same subpath. `npm view @mhmo91/schmancy version` always returns the current pin if you want to float forward.
@@ -20,7 +20,7 @@ One script tag. No bundler, no bare specifiers, no npm install.
20
20
  ```html
21
21
  <!doctype html>
22
22
  <script type="module">
23
- import { $dialog, theme } from 'https://esm.sh/@mhmo91/schmancy/agent@0.9.27';
23
+ import { $dialog, theme } from 'https://esm.sh/@mhmo91/schmancy/agent@0.9.28';
24
24
  </script>
25
25
  <schmancy-theme root scheme="dark">
26
26
  <schmancy-surface type="solid" fill="all">
@@ -12,7 +12,7 @@ Four separable PRs, each shippable on its own:
12
12
  | # | Title | Branch | Impact on your probe |
13
13
  |---|---|---|---|
14
14
  | 2 | CI smoke-test gate | `feat/ci-gate-and-version-templating` | None user-facing. `window.schmancy.help()` regressions now fail-closed at publish time instead of shipping silently. |
15
- | 9 | `0.9.27` templating for handover docs | same branch as #2 | None user-facing. Future handover docs will have live esm.sh URLs instead of `<PENDING>` placeholders. |
15
+ | 9 | `0.9.28` templating for handover docs | same branch as #2 | None user-facing. Future handover docs will have live esm.sh URLs instead of `<PENDING>` placeholders. |
16
16
  | 3 | JSDoc backfill (46 components) | `feat/jsdoc-batch-{1,2,3}-*` | **This is what you'll notice.** Every form-control, container, and overlay/nav component now ships `@summary`, `@example`, and `@platform` tags in its manifest entry. `window.schmancy.help('schmancy-button')` returns a non-empty `summary`, a copy-pastable `examples[]`, and a `platformPrimitive` hint for graceful degradation. |
17
17
  | 1 | Lazy vendor chunks | `feat/lazy-{typewriter,code-highlight,qr-scanner}` | Pages that don't render `<schmancy-code>`, `<schmancy-qr-scanner>`, or `<schmancy-typewriter>` no longer fetch `vendor-highlight`, `vendor-jsqr`, or the typewriter chunk on first paint. ~68 KB gzipped saved on cold starts for typical prototypes. |
18
18
 
@@ -21,18 +21,18 @@ The only one that changes the shape of `window.schmancy` is **#3**. The others a
21
21
  ## Pinned URLs (live once the PRs merge)
22
22
 
23
23
  ```
24
- https://esm.sh/@mhmo91/schmancy/agent@0.9.27
25
- https://esm.sh/@mhmo91/schmancy/agent/manifest@0.9.27
24
+ https://esm.sh/@mhmo91/schmancy/agent@0.9.28
25
+ https://esm.sh/@mhmo91/schmancy/agent/manifest@0.9.28
26
26
  ```
27
27
 
28
- `0.9.27` is substituted at publish time — see [`agent-runtime-followups.md`](./agent-runtime-followups.md) #9. Until the PRs land, continue pinning `@0.9.14` (the last published version at time of writing).
28
+ `0.9.28` is substituted at publish time — see [`agent-runtime-followups.md`](./agent-runtime-followups.md) #9. Until the PRs land, continue pinning `@0.9.14` (the last published version at time of writing).
29
29
 
30
30
  ## Minimum loop-back test
31
31
 
32
32
  ```html
33
33
  <!doctype html>
34
34
  <script type="module">
35
- import 'https://esm.sh/@mhmo91/schmancy/agent@0.9.27';
35
+ import 'https://esm.sh/@mhmo91/schmancy/agent@0.9.28';
36
36
  </script>
37
37
 
38
38
  <schmancy-theme root scheme="dark">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhmo91/schmancy",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
4
4
  "description": "UI library build with web components",
5
5
  "main": "./dist/index.js",
6
6
  "customElements": "custom-elements.json",
@@ -2,140 +2,74 @@ import { describe, expect, it } from 'vitest'
2
2
  import './agent-entry'
3
3
 
4
4
  /**
5
- * Acceptance test per the Claude Design handover §4.4:
5
+ * Agent bundle = distribution test suite.
6
6
  *
7
- * > "CI check: load the bundle in a headless browser, assert
8
- * > `customElements.get('schmancy-button')` resolves, assert
9
- * > `window.schmancy.help()` returns non-empty."
7
+ * The bundle exists so consumers can drop a single `<script type="module">`
8
+ * tag and get every `<schmancy-*>` element registered. These tests verify
9
+ * that contract, no more.
10
10
  *
11
- * This test is the runtime smoke test. It imports the agent bundle entry
12
- * (which side-effect registers every <schmancy-*> tag plus <schmancy-skill>)
13
- * and then verifies the self-describing surface is installed.
11
+ * Earlier versions of this suite tested a `window.schmancy.help()` /
12
+ * `tokens()` / `findFor()` runtime introspection surface that targeted a
13
+ * hypothetical live-in-browser agent a consumer that never materialised
14
+ * in any shipping product. That surface was removed; AI consumers read the
15
+ * static manifest at `dist/agent/schmancy.manifest.json` instead, which is
16
+ * the real integration point.
14
17
  */
15
- describe('agent bundle — handover §4.4 acceptance', () => {
18
+ describe('agent bundle — distribution', () => {
16
19
  it('registers `schmancy-button` on import', () => {
17
20
  expect(customElements.get('schmancy-button')).toBeDefined()
18
21
  })
19
22
 
20
- it('registers `schmancy-skill` on import', () => {
21
- expect(customElements.get('schmancy-skill')).toBeDefined()
23
+ it('registers `schmancy-theme` on import', () => {
24
+ expect(customElements.get('schmancy-theme')).toBeDefined()
22
25
  })
23
26
 
24
- it('installs `window.schmancy` when a <schmancy-skill> connects', async () => {
25
- const host = document.createElement('div')
26
- const skill = document.createElement('schmancy-skill')
27
- host.appendChild(skill)
28
- document.body.appendChild(host)
29
-
30
- await customElements.whenDefined('schmancy-skill')
31
- // One microtask for connectedCallback to finish installing globals.
32
- await new Promise(requestAnimationFrame)
33
-
34
- expect(typeof window.schmancy).toBe('object')
35
- expect(typeof window.schmancy?.help).toBe('function')
36
- expect(typeof window.schmancy?.tokens).toBe('function')
37
- expect(typeof window.schmancy?.capabilities).toBe('function')
38
-
39
- host.remove()
27
+ it('registers `schmancy-surface` on import', () => {
28
+ expect(customElements.get('schmancy-surface')).toBeDefined()
40
29
  })
41
30
 
42
- it('`window.schmancy.help()` returns a non-empty manifest summary', async () => {
43
- const skill = document.createElement('schmancy-skill')
44
- document.body.appendChild(skill)
45
- await customElements.whenDefined('schmancy-skill')
46
- await new Promise(requestAnimationFrame)
47
-
48
- const help = window.schmancy!.help() as { elements: unknown[]; services: unknown[] }
49
- expect(Array.isArray(help.elements)).toBe(true)
50
- expect(help.elements.length).toBeGreaterThan(50)
51
-
52
- skill.remove()
31
+ it('registers a substantive set of components on import', () => {
32
+ // Spot-check 20 commonly-used tags. If anything below this threshold
33
+ // regresses, the side-effect registration chain is broken.
34
+ const sampleTags = [
35
+ 'schmancy-button',
36
+ 'schmancy-input',
37
+ 'schmancy-card',
38
+ 'schmancy-dialog',
39
+ 'schmancy-sheet',
40
+ 'schmancy-list',
41
+ 'schmancy-list-item',
42
+ 'schmancy-typography',
43
+ 'schmancy-icon',
44
+ 'schmancy-icon-button',
45
+ 'schmancy-form',
46
+ 'schmancy-checkbox',
47
+ 'schmancy-select',
48
+ 'schmancy-autocomplete',
49
+ 'schmancy-radio-group',
50
+ 'schmancy-textarea',
51
+ 'schmancy-switch',
52
+ 'schmancy-divider',
53
+ 'schmancy-details',
54
+ 'schmancy-card-content',
55
+ ]
56
+ const registered = sampleTags.filter(tag => customElements.get(tag) !== undefined)
57
+ expect(registered).toEqual(sampleTags)
53
58
  })
54
59
 
55
- it('`window.schmancy.help("schmancy-button")` returns attribute enum values', async () => {
56
- const skill = document.createElement('schmancy-skill')
57
- document.body.appendChild(skill)
58
- await customElements.whenDefined('schmancy-skill')
59
- await new Promise(requestAnimationFrame)
60
-
61
- const btn = window.schmancy!.help('schmancy-button') as {
62
- attributes?: Array<{ name: string; values?: string[] }>
63
- }
64
- expect(btn).toBeTruthy()
65
- expect(btn.attributes?.length ?? 0).toBeGreaterThan(0)
66
- const variant = btn.attributes?.find(a => a.name === 'variant')
67
- expect(variant?.values).toContain('filled')
68
-
69
- skill.remove()
70
- })
71
-
72
- it('`window.schmancy.capabilities()` reports every probed feature', async () => {
73
- const skill = document.createElement('schmancy-skill')
74
- document.body.appendChild(skill)
75
- await customElements.whenDefined('schmancy-skill')
76
- await new Promise(requestAnimationFrame)
77
-
78
- const caps = window.schmancy!.capabilities()
79
- expect(caps).toMatchObject({
80
- popover: expect.any(Boolean),
81
- declarativeShadowDom: expect.any(Boolean),
82
- scopedRegistries: expect.any(Boolean),
83
- trustedTypes: expect.any(Boolean),
84
- cssRegisteredProperties: expect.any(Boolean),
85
- elementInternalsAria: expect.any(Boolean),
86
- formAssociated: expect.any(Boolean),
87
- adoptedStyleSheets: expect.any(Boolean),
88
- })
89
-
90
- skill.remove()
60
+ it('exports the imperative service surface', async () => {
61
+ const mod = await import('./agent-entry')
62
+ expect(typeof mod.$dialog).toBe('object')
63
+ expect(typeof mod.$notify).toBe('object')
64
+ expect(typeof mod.sheet).toBe('object')
65
+ expect(typeof mod.theme).toBe('object')
66
+ expect(typeof mod.area).toBe('object')
91
67
  })
92
68
 
93
- /**
94
- * findFor() is the discovery-failure backstop. Phase 1 of the manifest-as-
95
- * validator-data plan: agents that reach for a "missing" component should
96
- * call this first and find what already ships.
97
- */
98
- it('`window.schmancy.findFor("badge")` returns schmancy-badge first', async () => {
99
- const skill = document.createElement('schmancy-skill')
100
- document.body.appendChild(skill)
101
- await customElements.whenDefined('schmancy-skill')
102
- await new Promise(requestAnimationFrame)
103
-
104
- const results = window.schmancy!.findFor('badge') as Array<{
105
- tag: string
106
- score: number
107
- summary?: string
108
- }>
109
- expect(results.length).toBeGreaterThan(0)
110
- expect(results[0].tag).toBe('schmancy-badge')
111
- expect(results[0].score).toBeGreaterThan(0)
112
-
113
- skill.remove()
114
- })
115
-
116
- it('`window.schmancy.findFor("avatar")` returns schmancy-avatar first', async () => {
117
- const skill = document.createElement('schmancy-skill')
118
- document.body.appendChild(skill)
119
- await customElements.whenDefined('schmancy-skill')
120
- await new Promise(requestAnimationFrame)
121
-
122
- const results = window.schmancy!.findFor('avatar') as Array<{ tag: string; score: number }>
123
- expect(results.length).toBeGreaterThan(0)
124
- expect(results[0].tag).toBe('schmancy-avatar')
125
-
126
- skill.remove()
127
- })
128
-
129
- it('`window.schmancy.findFor("xyz123nonexistent")` returns empty array', async () => {
130
- const skill = document.createElement('schmancy-skill')
131
- document.body.appendChild(skill)
132
- await customElements.whenDefined('schmancy-skill')
133
- await new Promise(requestAnimationFrame)
134
-
135
- const results = window.schmancy!.findFor('xyz123nonexistent') as unknown[]
136
- expect(Array.isArray(results)).toBe(true)
137
- expect(results.length).toBe(0)
138
-
139
- skill.remove()
69
+ it('does not install the legacy `window.schmancy` runtime surface', () => {
70
+ // Negative test the runtime introspection layer was removed
71
+ // because no shipping consumer used it. If it comes back unintentionally,
72
+ // flag it here so the regression is visible.
73
+ expect((window as { schmancy?: unknown }).schmancy).toBeUndefined()
140
74
  })
141
75
  })
@@ -1,5 +1,22 @@
1
+ /**
2
+ * Agent bundle entry — the single-URL ESM distribution.
3
+ *
4
+ * Importing this module side-effect-registers every `<schmancy-*>` custom
5
+ * element and exposes the imperative service surface ($dialog, sheet,
6
+ * $notify, etc.). The import is meant for standalone HTML files and
7
+ * one-page demos that want schmancy without a bundler.
8
+ *
9
+ * **Not** an agent-introspection runtime. Earlier versions installed a
10
+ * `window.schmancy` object with `help()` / `tokens()` / `findFor()` for
11
+ * a hypothetical "live agent in a browser" consumer that never materialised
12
+ * in any shipping product. AI consumers (Claude Design's source ingestion,
13
+ * IDE tooling, build pipelines) read the static manifest at
14
+ * `dist/agent/schmancy.manifest.json` — that's the actual integration point.
15
+ *
16
+ * The bundle stays as a distribution convenience; the runtime introspection
17
+ * surface was removed in favour of static-data consumption.
18
+ */
1
19
  import '../index'
2
- import './schmancy-skill'
3
20
 
4
21
  export {
5
22
  $dialog,
@@ -1,4 +1,21 @@
1
+ /**
2
+ * Agent bundle entry — the single-URL ESM distribution.
3
+ *
4
+ * Importing this module side-effect-registers every `<schmancy-*>` custom
5
+ * element and exposes the imperative service surface ($dialog, sheet,
6
+ * $notify, etc.). The import is meant for standalone HTML files and
7
+ * one-page demos that want schmancy without a bundler.
8
+ *
9
+ * **Not** an agent-introspection runtime. Earlier versions installed a
10
+ * `window.schmancy` object with `help()` / `tokens()` / `findFor()` for
11
+ * a hypothetical "live agent in a browser" consumer that never materialised
12
+ * in any shipping product. AI consumers (Claude Design's source ingestion,
13
+ * IDE tooling, build pipelines) read the static manifest at
14
+ * `dist/agent/schmancy.manifest.json` — that's the actual integration point.
15
+ *
16
+ * The bundle stays as a distribution convenience; the runtime introspection
17
+ * surface was removed in favour of static-data consumption.
18
+ */
1
19
  import '../index';
2
- import './schmancy-skill';
3
20
  export { $dialog, $notify, sheet, SchmancySheetPosition, schmancyContentDrawer, theme, area, lazy, createContext, select, selectItem, } from '../index';
4
21
  export { $LitElement } from '../../mixins/index';
@@ -1,255 +0,0 @@
1
- import manifest from 'virtual:schmancy-manifest'
2
-
3
- export type Manifest = typeof manifest
4
-
5
- export type ElementEntry = {
6
- kind: 'class'
7
- name: string
8
- tagName?: string
9
- description?: string
10
- summary?: string
11
- attributes?: Array<{
12
- name: string
13
- description?: string
14
- type?: { text?: string }
15
- default?: string
16
- values?: string[]
17
- }>
18
- events?: Array<{ name: string; description?: string; type?: { text?: string } }>
19
- slots?: Array<{ name: string; description?: string }>
20
- cssProperties?: Array<{ name: string; description?: string }>
21
- cssParts?: Array<{ name: string; description?: string }>
22
- examples?: string[]
23
- whenToUse?: string
24
- platformPrimitive?: { tag: string; mode?: string; note?: string }
25
- contexts?: { provides?: string[]; consumes?: string[] }
26
- }
27
-
28
- export type ServiceEntry = {
29
- kind: 'variable'
30
- name: string
31
- description?: string
32
- summary?: string
33
- service: true
34
- methods?: Array<{ signature: string; summary?: string }>
35
- }
36
-
37
- type Declaration = ElementEntry | ServiceEntry | { kind: string; name: string; [key: string]: unknown }
38
-
39
- function allDeclarations(): Declaration[] {
40
- const out: Declaration[] = []
41
- for (const mod of manifest.modules ?? []) {
42
- const decls = (mod as unknown as { declarations?: Declaration[] }).declarations ?? []
43
- for (const decl of decls) out.push(decl)
44
- }
45
- return out
46
- }
47
-
48
- function elements(): ElementEntry[] {
49
- return allDeclarations().filter(
50
- (d): d is ElementEntry => d.kind === 'class' && typeof (d as ElementEntry).tagName === 'string',
51
- )
52
- }
53
-
54
- function services(): ServiceEntry[] {
55
- return allDeclarations().filter((d): d is ServiceEntry => d.kind === 'variable' && (d as ServiceEntry).service === true)
56
- }
57
-
58
- export function help(tag?: string): unknown {
59
- if (!tag) {
60
- return {
61
- elements: elements().map(e => ({ tag: e.tagName, summary: e.summary ?? e.description })),
62
- services: services().map(s => ({ name: s.name, summary: s.summary ?? s.description })),
63
- }
64
- }
65
- const el = elements().find(e => e.tagName === tag)
66
- if (el) return el
67
- const svc = services().find(s => s.name === tag)
68
- if (svc) return svc
69
- return null
70
- }
71
-
72
- export function tokens(): string[] {
73
- return (manifest as { tokens?: string[] }).tokens ?? []
74
- }
75
-
76
- // --- findFor: keyword search over the manifest -------------------------------
77
-
78
- /**
79
- * Stopwords stripped from both query and document tokens. Kept short on
80
- * purpose — over-aggressive stopword lists hurt recall on short queries
81
- * like "use". Word "use" stays in because of "use case" matches.
82
- */
83
- const STOPWORDS = new Set([
84
- 'a', 'an', 'and', 'or', 'the', 'of', 'in', 'on', 'at', 'to', 'for', 'with',
85
- 'is', 'it', 'this', 'that', 'these', 'those', 'be', 'by', 'as', 'are', 'was',
86
- 'i', 'you', 'we', 'my', 'your',
87
- ])
88
-
89
- function tokenize(text: string): Set<string> {
90
- const out = new Set<string>()
91
- for (const t of text.toLowerCase().match(/[a-z][a-z0-9]+/g) ?? []) {
92
- if (t.length >= 2 && !STOPWORDS.has(t)) out.add(t)
93
- }
94
- return out
95
- }
96
-
97
- type IndexEntry = {
98
- entry: ElementEntry
99
- body: Set<string> // tokens from summary + description + examples
100
- tagTokens: Set<string> // tokens derived from the tag name itself
101
- }
102
-
103
- let _searchIndex: IndexEntry[] | null = null
104
-
105
- function buildSearchIndex(): IndexEntry[] {
106
- return elements().map(entry => {
107
- const tagTokens = tokenize((entry.tagName ?? '').replace(/-/g, ' '))
108
- const body = tokenize(
109
- [
110
- entry.tagName ?? '',
111
- entry.summary ?? '',
112
- entry.description ?? '',
113
- entry.whenToUse ?? '',
114
- (entry.examples ?? []).join(' '),
115
- ].join(' '),
116
- )
117
- return { entry, body, tagTokens }
118
- })
119
- }
120
-
121
- export type FindForResult = {
122
- tag: string
123
- score: number
124
- summary?: string
125
- examples?: string[]
126
- }
127
-
128
- /**
129
- * Keyword search over the component manifest. Tokenizes the query the same
130
- * way each component's `summary + description + examples` were tokenized,
131
- * and returns the components with the most overlap. Tag-name token matches
132
- * count for 3× a body-text match.
133
- *
134
- * Designed to catch the "I'm reaching for a custom component, what does
135
- * schmancy ship?" gap without bringing in a vector-embedding dependency.
136
- *
137
- * @example
138
- * window.schmancy.findFor('status pill')
139
- * // → [{ tag: 'schmancy-badge', score: 4, summary: '…', examples: [...] }]
140
- *
141
- * window.schmancy.findFor('initials avatar')
142
- * // → [{ tag: 'schmancy-avatar', score: 5, ... }]
143
- *
144
- * window.schmancy.findFor('xyz123nonexistent') // → []
145
- */
146
- export function findFor(query: string, limit = 5): FindForResult[] {
147
- if (!_searchIndex) _searchIndex = buildSearchIndex()
148
- const queryTokens = tokenize(query)
149
- if (queryTokens.size === 0) return []
150
-
151
- const scored: Array<{ entry: ElementEntry; score: number }> = []
152
- for (const ix of _searchIndex) {
153
- let score = 0
154
- for (const qt of queryTokens) {
155
- if (ix.tagTokens.has(qt)) score += 3
156
- else if (ix.body.has(qt)) score += 1
157
- }
158
- if (score > 0) scored.push({ entry: ix.entry, score })
159
- }
160
- scored.sort((a, b) => b.score - a.score)
161
- return scored.slice(0, limit).map(({ entry, score }) => ({
162
- tag: entry.tagName ?? '',
163
- score,
164
- summary: entry.summary,
165
- examples: entry.examples,
166
- }))
167
- }
168
-
169
- export function platformPrimitive(tag: string): ElementEntry['platformPrimitive'] | null {
170
- return elements().find(e => e.tagName === tag)?.platformPrimitive ?? null
171
- }
172
-
173
- export function registeredTags(): string[] {
174
- return elements()
175
- .map(e => e.tagName!)
176
- .filter(tag => typeof customElements.get(tag) !== 'undefined')
177
- }
178
-
179
- export function a11yAudit(): Array<{
180
- tag: string
181
- role: string | null
182
- ariaLabel: string | null
183
- hasShadowRoot: boolean
184
- formAssociated: boolean
185
- }> {
186
- const tags = new Set(elements().map(e => e.tagName!))
187
- const report: Array<{
188
- tag: string
189
- role: string | null
190
- ariaLabel: string | null
191
- hasShadowRoot: boolean
192
- formAssociated: boolean
193
- }> = []
194
- const all = document.querySelectorAll('*')
195
- for (const node of Array.from(all)) {
196
- const tag = node.tagName.toLowerCase()
197
- if (!tags.has(tag)) continue
198
- const ctor = customElements.get(tag) as (CustomElementConstructor & { formAssociated?: boolean }) | undefined
199
- report.push({
200
- tag,
201
- role: node.getAttribute('role'),
202
- ariaLabel: node.getAttribute('aria-label'),
203
- hasShadowRoot: Boolean((node as Element & { shadowRoot?: ShadowRoot | null }).shadowRoot),
204
- formAssociated: Boolean(ctor?.formAssociated),
205
- })
206
- }
207
- return report
208
- }
209
-
210
- export type Capabilities = {
211
- popover: boolean
212
- declarativeShadowDom: boolean
213
- scopedRegistries: boolean
214
- trustedTypes: boolean
215
- cssRegisteredProperties: boolean
216
- elementInternalsAria: boolean
217
- formAssociated: boolean
218
- adoptedStyleSheets: boolean
219
- }
220
-
221
- /**
222
- * Runtime feature probe. Tells an agent which platform capabilities the
223
- * current sandbox exposes, so it can adapt without assuming the sandbox CSP
224
- * or browser version. Every check is feature-detect, not UA-sniff.
225
- */
226
- export function capabilities(): Capabilities {
227
- const anyTemplate = typeof HTMLTemplateElement !== 'undefined' ? HTMLTemplateElement.prototype : null
228
- const anyElInternals = typeof ElementInternals !== 'undefined' ? ElementInternals.prototype : null
229
- return {
230
- popover: typeof HTMLElement !== 'undefined' && 'popover' in HTMLElement.prototype,
231
- declarativeShadowDom: Boolean(anyTemplate && 'shadowRootMode' in anyTemplate),
232
- scopedRegistries: (() => {
233
- try {
234
- // Native scoped registries require a constructible CustomElementRegistry.
235
- // Pre-Safari-26/Chrome-146, the constructor throws Illegal constructor.
236
- new (window as { CustomElementRegistry?: new () => unknown }).CustomElementRegistry!()
237
- return true
238
- } catch {
239
- return false
240
- }
241
- })(),
242
- trustedTypes: typeof (globalThis as { trustedTypes?: unknown }).trustedTypes !== 'undefined',
243
- cssRegisteredProperties: typeof (window as { CSS?: { registerProperty?: unknown } }).CSS?.registerProperty === 'function',
244
- elementInternalsAria: Boolean(anyElInternals && 'role' in anyElInternals),
245
- formAssociated: typeof ElementInternals !== 'undefined',
246
- adoptedStyleSheets: typeof Document !== 'undefined' && 'adoptedStyleSheets' in Document.prototype,
247
- }
248
- }
249
-
250
- export function manifestUrl(): string {
251
- const blob = new Blob([JSON.stringify(manifest)], { type: 'application/json' })
252
- return URL.createObjectURL(blob)
253
- }
254
-
255
- export { manifest }
@@ -1 +0,0 @@
1
- export * from './schmancy-skill'
@@ -1,74 +0,0 @@
1
- import { $LitElement } from '../../mixins/index'
2
- import { html } from 'lit'
3
- import { customElement } from 'lit/decorators.js'
4
- import {
5
- a11yAudit,
6
- capabilities,
7
- findFor,
8
- help,
9
- manifest,
10
- manifestUrl,
11
- platformPrimitive,
12
- registeredTags,
13
- tokens,
14
- } from './helpers'
15
-
16
- declare global {
17
- interface Window {
18
- schmancy?: {
19
- manifest: typeof manifest
20
- manifestUrl: string
21
- help: typeof help
22
- tokens: typeof tokens
23
- platformPrimitive: typeof platformPrimitive
24
- registeredTags: typeof registeredTags
25
- a11yAudit: typeof a11yAudit
26
- capabilities: typeof capabilities
27
- findFor: typeof findFor
28
- }
29
- }
30
- }
31
-
32
- let installedUrl: string | null = null
33
-
34
- function install() {
35
- if (typeof window === 'undefined') return
36
- if (installedUrl) return
37
- installedUrl = manifestUrl()
38
- window.schmancy = {
39
- manifest,
40
- manifestUrl: installedUrl,
41
- help,
42
- tokens,
43
- platformPrimitive,
44
- registeredTags,
45
- a11yAudit,
46
- capabilities,
47
- findFor,
48
- }
49
- }
50
-
51
- /**
52
- * Self-describing runtime helper. Drop `<schmancy-skill></schmancy-skill>`
53
- * once on a page and `window.schmancy.help('schmancy-button')` returns the
54
- * machine-readable entry for any tag. Renders nothing.
55
- *
56
- * @element schmancy-skill
57
- */
58
- @customElement('schmancy-skill')
59
- export class SchmancySkill extends $LitElement() {
60
- connectedCallback() {
61
- super.connectedCallback()
62
- install()
63
- }
64
-
65
- render() {
66
- return html``
67
- }
68
- }
69
-
70
- declare global {
71
- interface HTMLElementTagNameMap {
72
- 'schmancy-skill': SchmancySkill
73
- }
74
- }