@phcdevworks/spectre-components 0.0.1

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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/dist/button.cjs +288 -0
  4. package/dist/button.cjs.map +1 -0
  5. package/dist/button.d.cts +142 -0
  6. package/dist/button.d.ts +142 -0
  7. package/dist/button.js +282 -0
  8. package/dist/button.js.map +1 -0
  9. package/dist/checkbox.cjs +158 -0
  10. package/dist/checkbox.cjs.map +1 -0
  11. package/dist/checkbox.d.cts +98 -0
  12. package/dist/checkbox.d.ts +98 -0
  13. package/dist/checkbox.js +155 -0
  14. package/dist/checkbox.js.map +1 -0
  15. package/dist/fieldset.cjs +179 -0
  16. package/dist/fieldset.cjs.map +1 -0
  17. package/dist/fieldset.d.cts +63 -0
  18. package/dist/fieldset.d.ts +63 -0
  19. package/dist/fieldset.js +176 -0
  20. package/dist/fieldset.js.map +1 -0
  21. package/dist/index.cjs +1600 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.cts +14 -0
  24. package/dist/index.d.ts +14 -0
  25. package/dist/index.js +1576 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/input.cjs +235 -0
  28. package/dist/input.cjs.map +1 -0
  29. package/dist/input.d.cts +175 -0
  30. package/dist/input.d.ts +175 -0
  31. package/dist/input.js +230 -0
  32. package/dist/input.js.map +1 -0
  33. package/dist/label.cjs +156 -0
  34. package/dist/label.cjs.map +1 -0
  35. package/dist/label.d.cts +39 -0
  36. package/dist/label.d.ts +39 -0
  37. package/dist/label.js +153 -0
  38. package/dist/label.js.map +1 -0
  39. package/dist/radio.cjs +158 -0
  40. package/dist/radio.cjs.map +1 -0
  41. package/dist/radio.d.cts +98 -0
  42. package/dist/radio.d.ts +98 -0
  43. package/dist/radio.js +155 -0
  44. package/dist/radio.js.map +1 -0
  45. package/dist/select.cjs +272 -0
  46. package/dist/select.cjs.map +1 -0
  47. package/dist/select.d.cts +130 -0
  48. package/dist/select.d.ts +130 -0
  49. package/dist/select.js +268 -0
  50. package/dist/select.js.map +1 -0
  51. package/dist/textarea.cjs +423 -0
  52. package/dist/textarea.cjs.map +1 -0
  53. package/dist/textarea.d.cts +156 -0
  54. package/dist/textarea.d.ts +156 -0
  55. package/dist/textarea.js +420 -0
  56. package/dist/textarea.js.map +1 -0
  57. package/package.json +124 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PHCDevworks
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.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # @phcdevworks/spectre-components
2
+
3
+ [![GitHub issues](https://img.shields.io/github/issues/phcdevworks/spectre-components)](https://github.com/phcdevworks/spectre-components/issues)
4
+ [![GitHub pull requests](https://img.shields.io/github/issues-pr/phcdevworks/spectre-components)](https://github.com/phcdevworks/spectre-components/pulls)
5
+ [![License](https://img.shields.io/github/license/phcdevworks/spectre-components)](LICENSE)
6
+
7
+ `@phcdevworks/spectre-components` is the Lit-based component layer of the
8
+ Spectre suite.
9
+
10
+ Maintained by PHCDevworks, it turns Spectre tokens and Spectre UI contracts
11
+ into reusable, accessible, framework-agnostic web components. It is the
12
+ canonical component implementation layer for Spectre, built on platform
13
+ standards and designed to be consumed directly or wrapped later by adapter
14
+ packages.
15
+
16
+ ## Key capabilities
17
+
18
+ - Ships reusable web components implemented with Lit
19
+ - Consumes `@phcdevworks/spectre-tokens` as the source of visual meaning
20
+ - Consumes `@phcdevworks/spectre-ui` as the styling contract layer
21
+ - Keeps component delivery framework-agnostic through custom elements
22
+ - Builds accessibility into the initial component patterns
23
+ - Exposes a small, explicit API surface suitable for long-term growth
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @phcdevworks/spectre-components @phcdevworks/spectre-ui @phcdevworks/spectre-tokens
29
+ ```
30
+
31
+ ## Quick start
32
+
33
+ Import the Spectre CSS layers first, then register the custom elements you want
34
+ to use:
35
+
36
+ ```ts
37
+ import "@phcdevworks/spectre-tokens/index.css";
38
+ import "@phcdevworks/spectre-ui/index.css";
39
+
40
+ import { defineSpectreComponents } from "@phcdevworks/spectre-components";
41
+
42
+ defineSpectreComponents();
43
+ ```
44
+
45
+ Use the components in markup:
46
+
47
+ ```html
48
+ <sp-button variant="primary" size="md">Save</sp-button>
49
+ <sp-button variant="ghost" size="sm">Cancel</sp-button>
50
+ <sp-input name="email" type="email" placeholder="Email address"></sp-input>
51
+ ```
52
+
53
+ Register only the button when you want a narrower entry point:
54
+
55
+ ```ts
56
+ import { defineSpectreButton } from "@phcdevworks/spectre-components/button";
57
+
58
+ defineSpectreButton();
59
+ ```
60
+
61
+ Register only the input when you want the input-only entry point:
62
+
63
+ ```ts
64
+ import { defineSpectreInput } from "@phcdevworks/spectre-components/input";
65
+
66
+ defineSpectreInput();
67
+ ```
68
+
69
+ ## What this package owns
70
+
71
+ - Lit-based web component implementation for Spectre
72
+ - Accessible component behavior and DOM structure
73
+ - Public custom element registration helpers
74
+ - Component-level TypeScript APIs for future adapters to build on
75
+
76
+ Golden rule: implement components from Spectre contracts, do not redefine those
77
+ contracts locally.
78
+
79
+ ## What this package does not own
80
+
81
+ - Design-token values or semantic visual meaning That belongs to
82
+ [`@phcdevworks/spectre-tokens`](https://github.com/phcdevworks/spectre-tokens).
83
+ - CSS utilities, class recipes, or styling contracts That belongs to
84
+ [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui).
85
+ - Framework adapters such as React, Vue, Astro, or app-specific wrappers Those
86
+ belong in downstream adapter packages.
87
+ - Routing, shell logic, manifest behavior, or startup orchestration Those are
88
+ outside the scope of this package.
89
+
90
+ ## Package exports / API surface
91
+
92
+ ### Root package
93
+
94
+ `@phcdevworks/spectre-components` exports:
95
+
96
+ - `defineSpectreComponents()`
97
+ - `defineSpectreButton()`
98
+ - `defineSpectreInput()`
99
+ - `SpectreButtonElement`
100
+ - `SpectreInputElement`
101
+ - `spectreButtonVariants`
102
+ - `spectreButtonSizes`
103
+ - `spectreButtonTypes`
104
+ - `spectreInputTypes`
105
+ - `spectreInputSizes`
106
+ - `SpectreButtonProps` and related button types
107
+ - `SpectreInputProps` and related input types
108
+
109
+ ### Button entry point
110
+
111
+ `@phcdevworks/spectre-components/button` exports the button-only API so
112
+ consumers can register a single component without importing the full package
113
+ entry.
114
+
115
+ ### Input entry point
116
+
117
+ `@phcdevworks/spectre-components/input` exports the input-only API so
118
+ consumers can register just `sp-input` when they do not need the broader
119
+ component bundle.
120
+
121
+ ## Relationship to the rest of Spectre
122
+
123
+ Spectre keeps responsibilities separate:
124
+
125
+ - [`@phcdevworks/spectre-tokens`](https://github.com/phcdevworks/spectre-tokens)
126
+ defines design values and semantic meaning
127
+ - [`@phcdevworks/spectre-ui`](https://github.com/phcdevworks/spectre-ui)
128
+ provides CSS, Tailwind helpers, recipes, and styling contracts
129
+ - `@phcdevworks/spectre-components` turns those layers into Lit-based reusable
130
+ web components
131
+
132
+ That separation keeps visual meaning centralized, styling contracts stable, and
133
+ component behavior reusable across frameworks.
134
+
135
+ ## Current foundation
136
+
137
+ This v0 foundation includes:
138
+
139
+ - a publishable TypeScript package build
140
+ - root and subpath exports
141
+ - a Lit-based `sp-button` starter component
142
+ - a Lit-based `sp-input` foundation component
143
+ - explicit registration helpers instead of implicit global side effects
144
+ - Vitest coverage for baseline rendering and accessibility behavior
145
+
146
+ ## Development
147
+
148
+ Install dependencies, then run the package checks:
149
+
150
+ ```bash
151
+ npm run build
152
+ npm test
153
+ npm run lint
154
+ ```
155
+
156
+ Key source areas:
157
+
158
+ - `src/components/` for custom elements
159
+ - `src/index.ts` for the root public API
160
+ - `tests/` for component behavior coverage
161
+
162
+ ## Contributing
163
+
164
+ PHCDevworks maintains this package as part of the Spectre suite.
165
+
166
+ When contributing:
167
+
168
+ - treat `@phcdevworks/spectre-tokens` as the source of visual meaning
169
+ - treat `@phcdevworks/spectre-ui` as the styling contract layer
170
+ - avoid redefining visual primitives locally
171
+ - keep components accessible by default
172
+ - run `npm run build`, `npm test`, and `npm run lint` before opening a pull
173
+ request
174
+
175
+ ## License
176
+
177
+ MIT © PHCDevworks. See [LICENSE](LICENSE).
@@ -0,0 +1,288 @@
1
+ 'use strict';
2
+
3
+ var lit = require('lit');
4
+ var ifDefined_js = require('lit/directives/if-defined.js');
5
+ var spectreUi = require('@phcdevworks/spectre-ui');
6
+
7
+ // src/components/button/sp-button.ts
8
+ var spectreButtonVariants = [
9
+ "primary",
10
+ "secondary",
11
+ "ghost",
12
+ "danger",
13
+ "success",
14
+ "cta",
15
+ "accent"
16
+ ];
17
+ var spectreButtonSizes = ["sm", "md", "lg"];
18
+ var spectreButtonTypes = ["button", "submit", "reset"];
19
+ function isButtonVariant(value) {
20
+ return spectreButtonVariants.includes(value);
21
+ }
22
+ function isButtonSize(value) {
23
+ return spectreButtonSizes.includes(value);
24
+ }
25
+ function isButtonType(value) {
26
+ return spectreButtonTypes.includes(value);
27
+ }
28
+ function hasMeaningfulContent(nodes) {
29
+ return nodes.some((node) => {
30
+ if (node.nodeType === Node.ELEMENT_NODE) {
31
+ return true;
32
+ }
33
+ if (node.nodeType === Node.TEXT_NODE) {
34
+ return (node.textContent?.trim().length ?? 0) > 0;
35
+ }
36
+ return false;
37
+ });
38
+ }
39
+ var SpectreButtonElement = class extends lit.LitElement {
40
+ constructor() {
41
+ super(...arguments);
42
+ this.ariaLabel = null;
43
+ this.ariaLabelledBy = null;
44
+ this.ariaDescribedBy = null;
45
+ this.autofocus = false;
46
+ this.disabled = false;
47
+ this.fullWidth = false;
48
+ this.loading = false;
49
+ this.loadingLabel = "Loading";
50
+ this.pill = false;
51
+ this.size = "md";
52
+ this.title = "";
53
+ this.type = "button";
54
+ this.variant = "primary";
55
+ // Native slot projection is a Shadow DOM feature, so in light DOM we keep
56
+ // host-provided content reactive by tracking and reusing the host nodes.
57
+ this.projectedContent = [];
58
+ }
59
+ get id() {
60
+ return this._id ?? "";
61
+ }
62
+ set id(value) {
63
+ if ((this._id ?? "") === value) {
64
+ return;
65
+ }
66
+ this._id = value;
67
+ const host = this;
68
+ if (HTMLElement.prototype.hasAttribute.call(host, "id")) {
69
+ HTMLElement.prototype.removeAttribute.call(host, "id");
70
+ }
71
+ this.requestUpdate();
72
+ }
73
+ createRenderRoot() {
74
+ return this;
75
+ }
76
+ connectedCallback() {
77
+ super.connectedCallback();
78
+ const hostId = super.getAttribute("id");
79
+ if (hostId !== null) {
80
+ this.id = hostId;
81
+ }
82
+ this.syncProjectedContent();
83
+ this.startContentObserver();
84
+ }
85
+ getAttribute(qualifiedName) {
86
+ if (qualifiedName === "id") {
87
+ return this.id || null;
88
+ }
89
+ return super.getAttribute(qualifiedName);
90
+ }
91
+ hasAttribute(qualifiedName) {
92
+ if (qualifiedName === "id") {
93
+ return this.id !== "";
94
+ }
95
+ return super.hasAttribute(qualifiedName);
96
+ }
97
+ setAttribute(qualifiedName, value) {
98
+ if (qualifiedName === "id") {
99
+ this.id = value;
100
+ return;
101
+ }
102
+ super.setAttribute(qualifiedName, value);
103
+ }
104
+ removeAttribute(qualifiedName) {
105
+ if (qualifiedName === "id") {
106
+ this.id = "";
107
+ return;
108
+ }
109
+ super.removeAttribute(qualifiedName);
110
+ }
111
+ disconnectedCallback() {
112
+ this.stopContentObserver();
113
+ super.disconnectedCallback();
114
+ }
115
+ willUpdate(changedProperties) {
116
+ if (changedProperties.has("variant") && !isButtonVariant(this.variant)) {
117
+ this.variant = "primary";
118
+ }
119
+ if (changedProperties.has("size") && !isButtonSize(this.size)) {
120
+ this.size = "md";
121
+ }
122
+ if (changedProperties.has("type") && !isButtonType(this.type)) {
123
+ this.type = "button";
124
+ }
125
+ if (changedProperties.has("loadingLabel")) {
126
+ if (this.loadingLabel == null || this.loadingLabel.trim() === "") {
127
+ this.loadingLabel = "Loading";
128
+ }
129
+ }
130
+ }
131
+ update(changedProperties) {
132
+ this.stopContentObserver();
133
+ super.update(changedProperties);
134
+ this.startContentObserver();
135
+ }
136
+ get buttonClasses() {
137
+ return spectreUi.getButtonClasses({
138
+ disabled: this.isDisabled,
139
+ fullWidth: this.fullWidth,
140
+ loading: this.loading,
141
+ pill: this.pill,
142
+ size: this.size,
143
+ variant: this.variant
144
+ });
145
+ }
146
+ get isDisabled() {
147
+ return this.disabled || this.loading;
148
+ }
149
+ get hasProjectedContent() {
150
+ return hasMeaningfulContent(this.projectedContent);
151
+ }
152
+ get visibleLabelFallback() {
153
+ const trimmedLabel = this.label?.trim();
154
+ return trimmedLabel ? trimmedLabel : void 0;
155
+ }
156
+ get forwardedAriaLabel() {
157
+ const ariaLabel = this.ariaLabel?.trim();
158
+ return ariaLabel ? ariaLabel : void 0;
159
+ }
160
+ get forwardedAriaLabelledBy() {
161
+ const ariaLabelledBy = this.ariaLabelledBy?.trim();
162
+ return ariaLabelledBy ? ariaLabelledBy : void 0;
163
+ }
164
+ get forwardedAriaDescribedBy() {
165
+ const ariaDescribedBy = this.ariaDescribedBy?.trim();
166
+ return ariaDescribedBy ? ariaDescribedBy : void 0;
167
+ }
168
+ startContentObserver() {
169
+ if (this.contentObserver) {
170
+ return;
171
+ }
172
+ this.contentObserver = new MutationObserver((mutations) => {
173
+ const isInternalMovement = mutations.every((mutation) => {
174
+ return Array.from(mutation.removedNodes).every(
175
+ (node) => this.isInternalButtonNode(node) || this.contains(node)
176
+ ) && Array.from(mutation.addedNodes).every((node) => this.isInternalButtonNode(node));
177
+ });
178
+ if (isInternalMovement) {
179
+ return;
180
+ }
181
+ if (this.syncProjectedContent()) {
182
+ this.requestUpdate();
183
+ }
184
+ });
185
+ this.contentObserver.observe(this, {
186
+ childList: true
187
+ });
188
+ }
189
+ stopContentObserver() {
190
+ this.contentObserver?.disconnect();
191
+ this.contentObserver = void 0;
192
+ }
193
+ syncProjectedContent() {
194
+ const nextProjectedContent = [];
195
+ Array.from(this.childNodes).forEach((node) => {
196
+ if (this.isInternalButtonNode(node)) {
197
+ this.projectedContent.forEach((pNode) => {
198
+ if (pNode.parentNode !== this && this.contains(pNode)) {
199
+ nextProjectedContent.push(pNode);
200
+ }
201
+ });
202
+ } else {
203
+ nextProjectedContent.push(node);
204
+ }
205
+ });
206
+ const hasChanged = nextProjectedContent.length !== this.projectedContent.length || nextProjectedContent.some((node, index) => node !== this.projectedContent[index]);
207
+ if (hasChanged) {
208
+ this.projectedContent = nextProjectedContent;
209
+ }
210
+ return hasChanged;
211
+ }
212
+ isInternalButtonNode(node) {
213
+ return node.nodeType === Node.ELEMENT_NODE && node.hasAttribute("data-sp-button-native");
214
+ }
215
+ get nativeButton() {
216
+ return this.querySelector("[data-sp-button-native]");
217
+ }
218
+ focus(options) {
219
+ this.nativeButton?.focus(options);
220
+ }
221
+ blur() {
222
+ this.nativeButton?.blur();
223
+ }
224
+ renderButtonContent() {
225
+ if (this.loading) {
226
+ return this.loadingLabel;
227
+ }
228
+ if (this.hasProjectedContent) {
229
+ return this.projectedContent;
230
+ }
231
+ return this.visibleLabelFallback ?? "";
232
+ }
233
+ render() {
234
+ return lit.html`
235
+ <button
236
+ aria-busy=${this.loading ? "true" : "false"}
237
+ aria-describedby=${ifDefined_js.ifDefined(this.forwardedAriaDescribedBy)}
238
+ aria-label=${ifDefined_js.ifDefined(this.forwardedAriaLabel)}
239
+ aria-labelledby=${ifDefined_js.ifDefined(this.forwardedAriaLabelledBy)}
240
+ ?autofocus=${this.autofocus}
241
+ class=${this.buttonClasses}
242
+ data-sp-button-native
243
+ ?disabled=${this.isDisabled}
244
+ id=${ifDefined_js.ifDefined(this.id || void 0)}
245
+ name=${ifDefined_js.ifDefined(this.name)}
246
+ title=${ifDefined_js.ifDefined(this.title || void 0)}
247
+ type=${this.type}
248
+ value=${ifDefined_js.ifDefined(this.value)}
249
+ >
250
+ ${this.renderButtonContent() || lit.nothing}
251
+ </button>
252
+ `;
253
+ }
254
+ };
255
+ SpectreButtonElement.properties = {
256
+ ariaLabel: { attribute: "aria-label", type: String },
257
+ ariaLabelledBy: { attribute: "aria-labelledby", type: String },
258
+ ariaDescribedBy: { attribute: "aria-describedby", type: String },
259
+ autofocus: { type: Boolean, reflect: true },
260
+ disabled: { type: Boolean, reflect: true },
261
+ fullWidth: { attribute: "full-width", type: Boolean, reflect: true },
262
+ label: { type: String, reflect: true },
263
+ loading: { type: Boolean, reflect: true },
264
+ loadingLabel: { attribute: "loading-label", type: String, reflect: true },
265
+ name: { type: String, reflect: true },
266
+ pill: { type: Boolean, reflect: true },
267
+ size: { type: String, reflect: true },
268
+ title: { type: String, reflect: true },
269
+ type: { type: String, reflect: true },
270
+ variant: { type: String, reflect: true },
271
+ value: { type: String, reflect: true }
272
+ };
273
+ function defineSpectreButton(tagName = "sp-button") {
274
+ const existingElement = customElements.get(tagName);
275
+ if (existingElement) {
276
+ return existingElement;
277
+ }
278
+ customElements.define(tagName, SpectreButtonElement);
279
+ return SpectreButtonElement;
280
+ }
281
+
282
+ exports.SpectreButtonElement = SpectreButtonElement;
283
+ exports.defineSpectreButton = defineSpectreButton;
284
+ exports.spectreButtonSizes = spectreButtonSizes;
285
+ exports.spectreButtonTypes = spectreButtonTypes;
286
+ exports.spectreButtonVariants = spectreButtonVariants;
287
+ //# sourceMappingURL=button.cjs.map
288
+ //# sourceMappingURL=button.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/button/sp-button.ts"],"names":["LitElement","getButtonClasses","html","ifDefined","nothing"],"mappings":"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","file":"button.cjs","sourcesContent":["import { LitElement, html, nothing, type TemplateResult } from 'lit';\nimport { ifDefined } from 'lit/directives/if-defined.js';\n\nimport {\n getButtonClasses,\n type ButtonSize,\n type ButtonVariant,\n} from '@phcdevworks/spectre-ui';\n\nexport const spectreButtonVariants = [\n 'primary',\n 'secondary',\n 'ghost',\n 'danger',\n 'success',\n 'cta',\n 'accent',\n] as const;\n\nexport const spectreButtonSizes = ['sm', 'md', 'lg'] as const;\nexport const spectreButtonTypes = ['button', 'submit', 'reset'] as const;\n\nexport type SpectreButtonVariant = (typeof spectreButtonVariants)[number];\nexport type SpectreButtonSize = (typeof spectreButtonSizes)[number];\nexport type SpectreButtonType = (typeof spectreButtonTypes)[number];\n\nexport interface SpectreButtonProps {\n autofocus?: boolean;\n disabled?: boolean;\n fullWidth?: boolean;\n label?: string;\n loading?: boolean;\n loadingLabel?: string;\n name?: string;\n pill?: boolean;\n size?: SpectreButtonSize;\n title?: string;\n type?: SpectreButtonType;\n variant?: SpectreButtonVariant;\n value?: string;\n}\n\nfunction isButtonVariant(value: string): value is ButtonVariant {\n return (spectreButtonVariants as readonly string[]).includes(value);\n}\n\nfunction isButtonSize(value: string): value is ButtonSize {\n return (spectreButtonSizes as readonly string[]).includes(value);\n}\n\nfunction isButtonType(value: string): value is SpectreButtonType {\n return (spectreButtonTypes as readonly string[]).includes(value);\n}\n\nfunction hasMeaningfulContent(nodes: readonly Node[]): boolean {\n return nodes.some((node) => {\n if (node.nodeType === Node.ELEMENT_NODE) {\n return true;\n }\n\n if (node.nodeType === Node.TEXT_NODE) {\n return (node.textContent?.trim().length ?? 0) > 0;\n }\n\n return false;\n });\n}\n\nexport class SpectreButtonElement extends LitElement implements SpectreButtonProps {\n static properties = {\n ariaLabel: { attribute: 'aria-label', type: String },\n ariaLabelledBy: { attribute: 'aria-labelledby', type: String },\n ariaDescribedBy: { attribute: 'aria-describedby', type: String },\n autofocus: { type: Boolean, reflect: true },\n disabled: { type: Boolean, reflect: true },\n fullWidth: { attribute: 'full-width', type: Boolean, reflect: true },\n label: { type: String, reflect: true },\n loading: { type: Boolean, reflect: true },\n loadingLabel: { attribute: 'loading-label', type: String, reflect: true },\n name: { type: String, reflect: true },\n pill: { type: Boolean, reflect: true },\n size: { type: String, reflect: true },\n title: { type: String, reflect: true },\n type: { type: String, reflect: true },\n variant: { type: String, reflect: true },\n value: { type: String, reflect: true },\n };\n\n ariaLabel: string | null = null;\n ariaLabelledBy: string | null = null;\n ariaDescribedBy: string | null = null;\n autofocus = false;\n disabled = false;\n fullWidth = false;\n label?: string;\n loading = false;\n loadingLabel = 'Loading';\n name?: string;\n pill = false;\n size: SpectreButtonSize = 'md';\n override title = '';\n type: SpectreButtonType = 'button';\n variant: SpectreButtonVariant = 'primary';\n value?: string;\n private _id?: string;\n\n override get id(): string {\n return this._id ?? '';\n }\n\n override set id(value: string) {\n if ((this._id ?? '') === value) {\n return;\n }\n\n this._id = value;\n\n const host = this as unknown as HTMLElement;\n if (HTMLElement.prototype.hasAttribute.call(host, 'id')) {\n HTMLElement.prototype.removeAttribute.call(host, 'id');\n }\n\n this.requestUpdate();\n }\n\n // Native slot projection is a Shadow DOM feature, so in light DOM we keep\n // host-provided content reactive by tracking and reusing the host nodes.\n private projectedContent: Node[] = [];\n private contentObserver?: MutationObserver | undefined;\n\n createRenderRoot(): this {\n // Spectre components intentionally render in light DOM so the global\n // `@phcdevworks/spectre-ui` styling contract can apply directly.\n return this;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n\n const hostId = super.getAttribute('id');\n\n if (hostId !== null) {\n this.id = hostId;\n }\n\n this.syncProjectedContent();\n this.startContentObserver();\n }\n\n override getAttribute(qualifiedName: string): string | null {\n if (qualifiedName === 'id') {\n return this.id || null;\n }\n\n return super.getAttribute(qualifiedName);\n }\n\n override hasAttribute(qualifiedName: string): boolean {\n if (qualifiedName === 'id') {\n return this.id !== '';\n }\n\n return super.hasAttribute(qualifiedName);\n }\n\n override setAttribute(qualifiedName: string, value: string): void {\n if (qualifiedName === 'id') {\n this.id = value;\n return;\n }\n\n super.setAttribute(qualifiedName, value);\n }\n\n override removeAttribute(qualifiedName: string): void {\n if (qualifiedName === 'id') {\n this.id = '';\n return;\n }\n\n super.removeAttribute(qualifiedName);\n }\n\n override disconnectedCallback(): void {\n this.stopContentObserver();\n super.disconnectedCallback();\n }\n\n protected override willUpdate(changedProperties: Map<PropertyKey, unknown>): void {\n if (changedProperties.has('variant') && !isButtonVariant(this.variant)) {\n this.variant = 'primary';\n }\n\n if (changedProperties.has('size') && !isButtonSize(this.size)) {\n this.size = 'md';\n }\n\n if (changedProperties.has('type') && !isButtonType(this.type)) {\n this.type = 'button';\n }\n\n if (changedProperties.has('loadingLabel')) {\n if (this.loadingLabel == null || this.loadingLabel.trim() === '') {\n this.loadingLabel = 'Loading';\n }\n }\n }\n\n protected override update(changedProperties: Map<PropertyKey, unknown>): void {\n this.stopContentObserver();\n super.update(changedProperties);\n this.startContentObserver();\n }\n\n private get buttonClasses(): string {\n return getButtonClasses({\n disabled: this.isDisabled,\n fullWidth: this.fullWidth,\n loading: this.loading,\n pill: this.pill,\n size: this.size,\n variant: this.variant,\n });\n }\n\n private get isDisabled(): boolean {\n return this.disabled || this.loading;\n }\n\n private get hasProjectedContent(): boolean {\n return hasMeaningfulContent(this.projectedContent);\n }\n\n private get visibleLabelFallback(): string | undefined {\n const trimmedLabel = this.label?.trim();\n return trimmedLabel ? trimmedLabel : undefined;\n }\n\n private get forwardedAriaLabel(): string | undefined {\n const ariaLabel = this.ariaLabel?.trim();\n return ariaLabel ? ariaLabel : undefined;\n }\n\n private get forwardedAriaLabelledBy(): string | undefined {\n const ariaLabelledBy = this.ariaLabelledBy?.trim();\n return ariaLabelledBy ? ariaLabelledBy : undefined;\n }\n\n private get forwardedAriaDescribedBy(): string | undefined {\n const ariaDescribedBy = this.ariaDescribedBy?.trim();\n return ariaDescribedBy ? ariaDescribedBy : undefined;\n }\n\n private startContentObserver(): void {\n if (this.contentObserver) {\n return;\n }\n\n this.contentObserver = new MutationObserver((mutations) => {\n const isInternalMovement = mutations.every((mutation) => {\n return (\n Array.from(mutation.removedNodes).every(\n (node) => this.isInternalButtonNode(node) || this.contains(node),\n ) &&\n Array.from(mutation.addedNodes).every((node) => this.isInternalButtonNode(node))\n );\n });\n\n if (isInternalMovement) {\n return;\n }\n\n if (this.syncProjectedContent()) {\n this.requestUpdate();\n }\n });\n\n this.contentObserver.observe(this, {\n childList: true,\n });\n }\n\n private stopContentObserver(): void {\n this.contentObserver?.disconnect();\n this.contentObserver = undefined;\n }\n\n private syncProjectedContent(): boolean {\n const nextProjectedContent: Node[] = [];\n\n Array.from(this.childNodes).forEach((node) => {\n if (this.isInternalButtonNode(node)) {\n this.projectedContent.forEach((pNode) => {\n if (pNode.parentNode !== this && this.contains(pNode)) {\n nextProjectedContent.push(pNode);\n }\n });\n } else {\n nextProjectedContent.push(node);\n }\n });\n\n const hasChanged =\n nextProjectedContent.length !== this.projectedContent.length ||\n nextProjectedContent.some((node, index) => node !== this.projectedContent[index]);\n\n if (hasChanged) {\n this.projectedContent = nextProjectedContent;\n }\n\n return hasChanged;\n }\n\n private isInternalButtonNode(node: Node): boolean {\n return (\n node.nodeType === Node.ELEMENT_NODE && (node as Element).hasAttribute('data-sp-button-native')\n );\n }\n\n private get nativeButton(): HTMLButtonElement | null {\n return this.querySelector('[data-sp-button-native]');\n }\n\n override focus(options?: FocusOptions): void {\n this.nativeButton?.focus(options);\n }\n\n override blur(): void {\n this.nativeButton?.blur();\n }\n\n private renderButtonContent(): TemplateResult | Node[] | string {\n if (this.loading) {\n return this.loadingLabel;\n }\n\n if (this.hasProjectedContent) {\n return this.projectedContent;\n }\n\n return this.visibleLabelFallback ?? '';\n }\n\n override render() {\n return html`\n <button\n aria-busy=${this.loading ? 'true' : 'false'}\n aria-describedby=${ifDefined(this.forwardedAriaDescribedBy)}\n aria-label=${ifDefined(this.forwardedAriaLabel)}\n aria-labelledby=${ifDefined(this.forwardedAriaLabelledBy)}\n ?autofocus=${this.autofocus}\n class=${this.buttonClasses}\n data-sp-button-native\n ?disabled=${this.isDisabled}\n id=${ifDefined(this.id || undefined)}\n name=${ifDefined(this.name)}\n title=${ifDefined(this.title || undefined)}\n type=${this.type}\n value=${ifDefined(this.value)}\n >\n ${this.renderButtonContent() || nothing}\n </button>\n `;\n }\n}\n\nexport function defineSpectreButton(tagName = 'sp-button'): typeof SpectreButtonElement {\n const existingElement = customElements.get(tagName);\n\n if (existingElement) {\n return existingElement as unknown as typeof SpectreButtonElement;\n }\n\n customElements.define(tagName, SpectreButtonElement);\n return SpectreButtonElement;\n}\n"]}
@@ -0,0 +1,142 @@
1
+ import { LitElement, TemplateResult } from 'lit';
2
+
3
+ declare const spectreButtonVariants: readonly ["primary", "secondary", "ghost", "danger", "success", "cta", "accent"];
4
+ declare const spectreButtonSizes: readonly ["sm", "md", "lg"];
5
+ declare const spectreButtonTypes: readonly ["button", "submit", "reset"];
6
+ type SpectreButtonVariant = (typeof spectreButtonVariants)[number];
7
+ type SpectreButtonSize = (typeof spectreButtonSizes)[number];
8
+ type SpectreButtonType = (typeof spectreButtonTypes)[number];
9
+ interface SpectreButtonProps {
10
+ autofocus?: boolean;
11
+ disabled?: boolean;
12
+ fullWidth?: boolean;
13
+ label?: string;
14
+ loading?: boolean;
15
+ loadingLabel?: string;
16
+ name?: string;
17
+ pill?: boolean;
18
+ size?: SpectreButtonSize;
19
+ title?: string;
20
+ type?: SpectreButtonType;
21
+ variant?: SpectreButtonVariant;
22
+ value?: string;
23
+ }
24
+ declare class SpectreButtonElement extends LitElement implements SpectreButtonProps {
25
+ static properties: {
26
+ ariaLabel: {
27
+ attribute: string;
28
+ type: StringConstructor;
29
+ };
30
+ ariaLabelledBy: {
31
+ attribute: string;
32
+ type: StringConstructor;
33
+ };
34
+ ariaDescribedBy: {
35
+ attribute: string;
36
+ type: StringConstructor;
37
+ };
38
+ autofocus: {
39
+ type: BooleanConstructor;
40
+ reflect: boolean;
41
+ };
42
+ disabled: {
43
+ type: BooleanConstructor;
44
+ reflect: boolean;
45
+ };
46
+ fullWidth: {
47
+ attribute: string;
48
+ type: BooleanConstructor;
49
+ reflect: boolean;
50
+ };
51
+ label: {
52
+ type: StringConstructor;
53
+ reflect: boolean;
54
+ };
55
+ loading: {
56
+ type: BooleanConstructor;
57
+ reflect: boolean;
58
+ };
59
+ loadingLabel: {
60
+ attribute: string;
61
+ type: StringConstructor;
62
+ reflect: boolean;
63
+ };
64
+ name: {
65
+ type: StringConstructor;
66
+ reflect: boolean;
67
+ };
68
+ pill: {
69
+ type: BooleanConstructor;
70
+ reflect: boolean;
71
+ };
72
+ size: {
73
+ type: StringConstructor;
74
+ reflect: boolean;
75
+ };
76
+ title: {
77
+ type: StringConstructor;
78
+ reflect: boolean;
79
+ };
80
+ type: {
81
+ type: StringConstructor;
82
+ reflect: boolean;
83
+ };
84
+ variant: {
85
+ type: StringConstructor;
86
+ reflect: boolean;
87
+ };
88
+ value: {
89
+ type: StringConstructor;
90
+ reflect: boolean;
91
+ };
92
+ };
93
+ ariaLabel: string | null;
94
+ ariaLabelledBy: string | null;
95
+ ariaDescribedBy: string | null;
96
+ autofocus: boolean;
97
+ disabled: boolean;
98
+ fullWidth: boolean;
99
+ label?: string;
100
+ loading: boolean;
101
+ loadingLabel: string;
102
+ name?: string;
103
+ pill: boolean;
104
+ size: SpectreButtonSize;
105
+ title: string;
106
+ type: SpectreButtonType;
107
+ variant: SpectreButtonVariant;
108
+ value?: string;
109
+ private _id?;
110
+ get id(): string;
111
+ set id(value: string);
112
+ private projectedContent;
113
+ private contentObserver?;
114
+ createRenderRoot(): this;
115
+ connectedCallback(): void;
116
+ getAttribute(qualifiedName: string): string | null;
117
+ hasAttribute(qualifiedName: string): boolean;
118
+ setAttribute(qualifiedName: string, value: string): void;
119
+ removeAttribute(qualifiedName: string): void;
120
+ disconnectedCallback(): void;
121
+ protected willUpdate(changedProperties: Map<PropertyKey, unknown>): void;
122
+ protected update(changedProperties: Map<PropertyKey, unknown>): void;
123
+ private get buttonClasses();
124
+ private get isDisabled();
125
+ private get hasProjectedContent();
126
+ private get visibleLabelFallback();
127
+ private get forwardedAriaLabel();
128
+ private get forwardedAriaLabelledBy();
129
+ private get forwardedAriaDescribedBy();
130
+ private startContentObserver;
131
+ private stopContentObserver;
132
+ private syncProjectedContent;
133
+ private isInternalButtonNode;
134
+ private get nativeButton();
135
+ focus(options?: FocusOptions): void;
136
+ blur(): void;
137
+ private renderButtonContent;
138
+ render(): TemplateResult<1>;
139
+ }
140
+ declare function defineSpectreButton(tagName?: string): typeof SpectreButtonElement;
141
+
142
+ export { SpectreButtonElement, type SpectreButtonProps, type SpectreButtonSize, type SpectreButtonType, type SpectreButtonVariant, defineSpectreButton, spectreButtonSizes, spectreButtonTypes, spectreButtonVariants };