@duskmoon-dev/el-base 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,368 @@
1
+ # @duskmoon-dev/el-base
2
+
3
+ Core utilities and base classes for DuskMoon custom elements.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @duskmoon-dev/el-base
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### BaseElement
14
+
15
+ The `BaseElement` class provides a foundation for creating custom elements with:
16
+
17
+ - Shadow DOM setup with `adoptedStyleSheets`
18
+ - Reactive properties with attribute reflection
19
+ - Batched updates using microtask queue
20
+ - Style injection utilities
21
+ - Event emission helpers
22
+
23
+ ```typescript
24
+ import { BaseElement, css } from '@duskmoon-dev/el-base';
25
+
26
+ const styles = css`
27
+ :host {
28
+ display: block;
29
+ }
30
+ .greeting {
31
+ color: var(--dm-primary);
32
+ }
33
+ `;
34
+
35
+ class MyGreeting extends BaseElement {
36
+ static properties = {
37
+ name: { type: String, reflect: true, default: 'World' },
38
+ };
39
+
40
+ declare name: string;
41
+
42
+ constructor() {
43
+ super();
44
+ this.attachStyles(styles);
45
+ }
46
+
47
+ render() {
48
+ return `<div class="greeting">Hello, ${this.name}!</div>`;
49
+ }
50
+ }
51
+
52
+ customElements.define('my-greeting', MyGreeting);
53
+ ```
54
+
55
+ ### CSS Utilities
56
+
57
+ #### `css` Template Tag
58
+
59
+ Creates a `CSSStyleSheet` from a template literal:
60
+
61
+ ```typescript
62
+ import { css } from '@duskmoon-dev/el-base';
63
+
64
+ const styles = css`
65
+ :host {
66
+ display: inline-flex;
67
+ }
68
+ button {
69
+ padding: 0.5rem 1rem;
70
+ }
71
+ `;
72
+ ```
73
+
74
+ #### `combineStyles`
75
+
76
+ Combines multiple stylesheets:
77
+
78
+ ```typescript
79
+ import { combineStyles } from '@duskmoon-dev/el-base';
80
+
81
+ const combinedStyles = combineStyles(baseStyles, themeStyles, componentStyles);
82
+ ```
83
+
84
+ #### `cssVars`
85
+
86
+ Creates CSS custom property declarations:
87
+
88
+ ```typescript
89
+ import { cssVars } from '@duskmoon-dev/el-base';
90
+
91
+ const vars = cssVars({
92
+ 'dm-primary': '#3b82f6',
93
+ 'dm-spacing': '1rem',
94
+ });
95
+ // Returns: '--dm-primary: #3b82f6; --dm-spacing: 1rem;'
96
+ ```
97
+
98
+ ### Default Theme
99
+
100
+ The package includes default CSS custom properties for theming. Import the theme stylesheet:
101
+
102
+ ```typescript
103
+ import { defaultTheme, resetStyles } from '@duskmoon-dev/el-base';
104
+
105
+ // Apply to shadow root
106
+ this.shadowRoot.adoptedStyleSheets = [resetStyles, defaultTheme, componentStyles];
107
+ ```
108
+
109
+ #### Color Tokens
110
+
111
+ | Variable | Description |
112
+ | ---------------- | ---------------------- |
113
+ | `--dm-primary` | Primary brand color |
114
+ | `--dm-secondary` | Secondary color |
115
+ | `--dm-success` | Success/positive color |
116
+ | `--dm-warning` | Warning color |
117
+ | `--dm-error` | Error/danger color |
118
+ | `--dm-info` | Information color |
119
+
120
+ #### Gray Scale
121
+
122
+ | Variable | Description |
123
+ | --------------------------------------- | ------------- |
124
+ | `--dm-gray-50` | Lightest gray |
125
+ | `--dm-gray-100` through `--dm-gray-800` | Gray scale |
126
+ | `--dm-gray-900` | Darkest gray |
127
+
128
+ #### Typography
129
+
130
+ | Variable | Description |
131
+ | ---------------------------------------------------------------------------------------------------------- | ---------------- |
132
+ | `--dm-font-family` | Base font family |
133
+ | `--dm-font-size-xs` through `--dm-font-size-2xl` | Font sizes |
134
+ | `--dm-font-weight-normal`, `--dm-font-weight-medium`, `--dm-font-weight-semibold`, `--dm-font-weight-bold` | Font weights |
135
+ | `--dm-line-height-tight`, `--dm-line-height-normal`, `--dm-line-height-relaxed` | Line heights |
136
+
137
+ #### Spacing
138
+
139
+ | Variable | Description |
140
+ | ----------------- | --------------------- |
141
+ | `--dm-spacing-xs` | Extra small (0.25rem) |
142
+ | `--dm-spacing-sm` | Small (0.5rem) |
143
+ | `--dm-spacing-md` | Medium (1rem) |
144
+ | `--dm-spacing-lg` | Large (1.5rem) |
145
+ | `--dm-spacing-xl` | Extra large (2rem) |
146
+
147
+ #### Border Radius
148
+
149
+ | Variable | Description |
150
+ | ------------------ | ---------------- |
151
+ | `--dm-radius-sm` | Small radius |
152
+ | `--dm-radius-md` | Medium radius |
153
+ | `--dm-radius-lg` | Large radius |
154
+ | `--dm-radius-full` | Full/pill radius |
155
+
156
+ #### Shadows
157
+
158
+ | Variable | Description |
159
+ | ---------------- | ------------- |
160
+ | `--dm-shadow-sm` | Small shadow |
161
+ | `--dm-shadow-md` | Medium shadow |
162
+ | `--dm-shadow-lg` | Large shadow |
163
+
164
+ #### Transitions
165
+
166
+ | Variable | Description |
167
+ | ------------------------ | ------------------------- |
168
+ | `--dm-transition-fast` | Fast transition (150ms) |
169
+ | `--dm-transition-normal` | Normal transition (200ms) |
170
+ | `--dm-transition-slow` | Slow transition (300ms) |
171
+
172
+ ## API
173
+
174
+ ### BaseElement
175
+
176
+ | Method | Description |
177
+ | ---------------------- | ------------------------------------------------ |
178
+ | `attachStyles(styles)` | Attach one or more stylesheets to Shadow DOM |
179
+ | `render()` | Override to return HTML content string |
180
+ | `update()` | Called when reactive properties change (batched) |
181
+ | `emit(name, detail?)` | Emit a CustomEvent from the element |
182
+ | `query(selector)` | Query single element in Shadow DOM |
183
+ | `queryAll(selector)` | Query all matching elements in Shadow DOM |
184
+
185
+ ### Property Definitions
186
+
187
+ Define reactive properties with automatic attribute reflection:
188
+
189
+ ```typescript
190
+ static properties = {
191
+ // Simple string property
192
+ label: { type: String },
193
+
194
+ // Boolean with attribute reflection
195
+ disabled: { type: Boolean, reflect: true },
196
+
197
+ // Number with default value
198
+ count: { type: Number, default: 0 },
199
+
200
+ // Custom attribute name (kebab-case)
201
+ maxItems: { type: Number, attribute: 'max-items' },
202
+
203
+ // Object/Array (not reflected to attributes)
204
+ data: { type: Object },
205
+ items: { type: Array, default: [] },
206
+ };
207
+ ```
208
+
209
+ Property definition options:
210
+
211
+ | Option | Type | Description |
212
+ | ----------- | ---------- | ------------------------------------------------------------------ |
213
+ | `type` | `Function` | Type constructor: `String`, `Number`, `Boolean`, `Object`, `Array` |
214
+ | `reflect` | `boolean` | Whether to reflect property to attribute |
215
+ | `attribute` | `string` | Custom attribute name (defaults to lowercase property name) |
216
+ | `default` | `any` | Default value for the property |
217
+
218
+ ### Lifecycle
219
+
220
+ ```typescript
221
+ class MyElement extends BaseElement {
222
+ // Called when element is added to DOM
223
+ connectedCallback() {
224
+ super.connectedCallback();
225
+ // Setup code
226
+ }
227
+
228
+ // Called when element is removed from DOM
229
+ disconnectedCallback() {
230
+ super.disconnectedCallback();
231
+ // Cleanup code
232
+ }
233
+
234
+ // Called when properties change (batched)
235
+ update() {
236
+ // Re-render or update DOM
237
+ this.shadowRoot.innerHTML = this.render();
238
+ }
239
+
240
+ // Return HTML string for Shadow DOM content
241
+ render() {
242
+ return `<div>Content</div>`;
243
+ }
244
+ }
245
+ ```
246
+
247
+ ### TypeScript Types
248
+
249
+ ```typescript
250
+ import type {
251
+ PropertyDefinition,
252
+ PropertyDefinitions,
253
+ Size,
254
+ Variant,
255
+ ValidationState,
256
+ BaseElementProps,
257
+ SizableProps,
258
+ VariantProps,
259
+ FormElementProps,
260
+ ValidatableProps,
261
+ ValueChangeEventDetail,
262
+ } from '@duskmoon-dev/el-base';
263
+ ```
264
+
265
+ ## Creating Custom Elements
266
+
267
+ Full example of a custom element:
268
+
269
+ ```typescript
270
+ import { BaseElement, css, defaultTheme, resetStyles } from '@duskmoon-dev/el-base';
271
+
272
+ const styles = css`
273
+ :host {
274
+ display: inline-block;
275
+ }
276
+
277
+ .counter {
278
+ display: flex;
279
+ align-items: center;
280
+ gap: var(--dm-spacing-sm);
281
+ }
282
+
283
+ button {
284
+ padding: var(--dm-spacing-xs) var(--dm-spacing-sm);
285
+ border-radius: var(--dm-radius-sm);
286
+ background: var(--dm-primary);
287
+ color: white;
288
+ border: none;
289
+ cursor: pointer;
290
+ transition: opacity var(--dm-transition-fast);
291
+ }
292
+
293
+ button:hover {
294
+ opacity: 0.9;
295
+ }
296
+
297
+ .value {
298
+ min-width: 2rem;
299
+ text-align: center;
300
+ font-weight: var(--dm-font-weight-medium);
301
+ }
302
+ `;
303
+
304
+ export class MyCounter extends BaseElement {
305
+ static properties = {
306
+ value: { type: Number, reflect: true, default: 0 },
307
+ min: { type: Number, default: 0 },
308
+ max: { type: Number, default: 100 },
309
+ };
310
+
311
+ declare value: number;
312
+ declare min: number;
313
+ declare max: number;
314
+
315
+ constructor() {
316
+ super();
317
+ this.attachStyles(resetStyles, defaultTheme, styles);
318
+ }
319
+
320
+ connectedCallback() {
321
+ super.connectedCallback();
322
+ this.shadowRoot?.addEventListener('click', this.handleClick.bind(this));
323
+ }
324
+
325
+ private handleClick(e: Event) {
326
+ const target = e.target as HTMLElement;
327
+ if (target.matches('[data-action="decrement"]')) {
328
+ this.decrement();
329
+ } else if (target.matches('[data-action="increment"]')) {
330
+ this.increment();
331
+ }
332
+ }
333
+
334
+ private decrement() {
335
+ if (this.value > this.min) {
336
+ this.value--;
337
+ this.emit('change', { value: this.value });
338
+ }
339
+ }
340
+
341
+ private increment() {
342
+ if (this.value < this.max) {
343
+ this.value++;
344
+ this.emit('change', { value: this.value });
345
+ }
346
+ }
347
+
348
+ render() {
349
+ return `
350
+ <div class="counter">
351
+ <button data-action="decrement" ${this.value <= this.min ? 'disabled' : ''}>-</button>
352
+ <span class="value">${this.value}</span>
353
+ <button data-action="increment" ${this.value >= this.max ? 'disabled' : ''}>+</button>
354
+ </div>
355
+ `;
356
+ }
357
+ }
358
+
359
+ export function register() {
360
+ if (!customElements.get('my-counter')) {
361
+ customElements.define('my-counter', MyCounter);
362
+ }
363
+ }
364
+ ```
365
+
366
+ ## License
367
+
368
+ MIT