sigpro 1.0.14 → 1.2.39

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/Readme.md +164 -1008
  2. package/dist/sigpro.editor.js +1 -0
  3. package/dist/sigpro.grid.js +78 -0
  4. package/dist/sigpro.js +1 -0
  5. package/dist/sigpro.ui.css +2 -0
  6. package/dist/sigpro.ui.js +1 -0
  7. package/dist/sigpro.utils.js +1 -0
  8. package/dist/sigpro.vite.js +4 -0
  9. package/package.json +64 -14
  10. package/sigpro.d.ts +395 -0
  11. package/.github/workflows/publish.yml +0 -25
  12. package/bun.lock +0 -385
  13. package/docs/404.html +0 -22
  14. package/docs/api/components.html +0 -595
  15. package/docs/api/effects.html +0 -787
  16. package/docs/api/fetch.html +0 -873
  17. package/docs/api/pages.html +0 -405
  18. package/docs/api/quick.html +0 -217
  19. package/docs/api/routing.html +0 -628
  20. package/docs/api/signals.html +0 -683
  21. package/docs/api/storage.html +0 -820
  22. package/docs/assets/api_components.md.BlFwj17l.js +0 -571
  23. package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
  24. package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
  25. package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
  26. package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
  27. package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
  28. package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
  29. package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
  30. package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
  31. package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
  32. package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
  33. package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
  34. package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
  35. package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
  36. package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
  37. package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
  38. package/docs/assets/app.DtmzNmNl.js +0 -1
  39. package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
  40. package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
  41. package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
  42. package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
  43. package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
  44. package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
  45. package/docs/assets/index.md.uvMJmU4o.js +0 -1
  46. package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
  47. package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  48. package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  49. package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  50. package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  51. package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  52. package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  53. package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  54. package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  55. package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  56. package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  57. package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  58. package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  59. package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  60. package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  61. package/docs/assets/style.DJRheFKp.css +0 -1
  62. package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
  63. package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
  64. package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
  65. package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
  66. package/docs/guide/getting-started.html +0 -196
  67. package/docs/guide/why.html +0 -47
  68. package/docs/hashmap.json +0 -1
  69. package/docs/index.html +0 -25
  70. package/docs/logo.svg +0 -118
  71. package/docs/ui/intro.html +0 -25
  72. package/docs/vite/plugin.html +0 -249
  73. package/docs/vp-icons.css +0 -1
  74. package/index.js +0 -3
  75. package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  76. package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  77. package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
  78. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
  79. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
  80. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
  81. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
  82. package/packages/docs/.vitepress/cache/deps/package.json +0 -3
  83. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
  84. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  85. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  86. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  87. package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
  88. package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
  89. package/packages/docs/.vitepress/config.js +0 -68
  90. package/packages/docs/api/components.md +0 -760
  91. package/packages/docs/api/effects.md +0 -1039
  92. package/packages/docs/api/fetch.md +0 -998
  93. package/packages/docs/api/pages.md +0 -497
  94. package/packages/docs/api/quick.md +0 -436
  95. package/packages/docs/api/routing.md +0 -784
  96. package/packages/docs/api/signals.md +0 -899
  97. package/packages/docs/api/storage.md +0 -952
  98. package/packages/docs/guide/getting-started.md +0 -308
  99. package/packages/docs/guide/why.md +0 -135
  100. package/packages/docs/index.md +0 -84
  101. package/packages/docs/logo.svg +0 -118
  102. package/packages/docs/public/logo.svg +0 -118
  103. package/packages/docs/ui/intro.md +0 -16
  104. package/packages/docs/vite/plugin.md +0 -423
  105. package/packages/sigpro/plugin.js +0 -91
  106. package/packages/sigpro/plugin.min.js +0 -1
  107. package/packages/sigpro/sigpro.js +0 -631
  108. package/packages/sigpro/sigpro.min.js +0 -1
  109. package/vite.config.js +0 -24
@@ -1,760 +0,0 @@
1
- # Components API 🧩
2
-
3
- Components in SigPro are native Web Components built on the Custom Elements standard. They provide a way to create reusable, encapsulated pieces of UI with reactive properties and automatic cleanup.
4
-
5
- ## `$.component(tagName, setupFunction, observedAttributes, useShadowDOM)`
6
-
7
- Creates a custom element with reactive properties and automatic dependency tracking.
8
-
9
- ```javascript
10
- import { $, html } from 'sigpro';
11
-
12
- $.component('my-button', (props, { slot, emit }) => {
13
- return html`
14
- <button
15
- class="btn"
16
- @click=${() => emit('click')}
17
- >
18
- ${slot()}
19
- </button>
20
- `;
21
- }, ['variant']); // Observe the 'variant' attribute
22
- ```
23
-
24
- ## 📋 API Reference
25
-
26
- ### Parameters
27
-
28
- | Parameter | Type | Default | Description |
29
- |-----------|------|---------|-------------|
30
- | `tagName` | `string` | required | Custom element tag name (must include a hyphen, e.g., `my-button`) |
31
- | `setupFunction` | `Function` | required | Function that returns the component's template |
32
- | `observedAttributes` | `string[]` | `[]` | Attributes to observe for changes (become reactive props) |
33
- | `useShadowDOM` | `boolean` | `false` | `true` = Shadow DOM (encapsulated), `false` = Light DOM (inherits styles) |
34
-
35
- ### Setup Function Parameters
36
-
37
- The setup function receives two arguments:
38
-
39
- 1. **`props`** - Object containing reactive signals for each observed attribute
40
- 2. **`context`** - Object with helper methods and properties
41
-
42
- #### Context Object Properties
43
-
44
- | Property | Type | Description |
45
- |----------|------|-------------|
46
- | `slot(name)` | `Function` | Returns array of child nodes for the specified slot |
47
- | `emit(name, detail)` | `Function` | Dispatches a custom event |
48
- | `select(selector)` | `Function` | Query selector within component's root |
49
- | `selectAll(selector)` | `Function` | Query selector all within component's root |
50
- | `host` | `HTMLElement` | Reference to the custom element instance |
51
- | `root` | `Node` | Component's root (shadow root or element itself) |
52
- | `onUnmount(callback)` | `Function` | Register cleanup function |
53
-
54
- ## 🏠 Light DOM vs Shadow DOM
55
-
56
- ### Light DOM (`useShadowDOM = false`) - Default
57
-
58
- The component **inherits global styles** from the application. Perfect for components that should integrate with your site's design system.
59
-
60
- ```javascript
61
- // Button that uses global Tailwind CSS
62
- $.component('tw-button', (props, { slot, emit }) => {
63
- const variant = props.variant() || 'primary';
64
-
65
- const variants = {
66
- primary: 'bg-blue-500 hover:bg-blue-600 text-white',
67
- secondary: 'bg-gray-500 hover:bg-gray-600 text-white',
68
- outline: 'border border-blue-500 text-blue-500 hover:bg-blue-50'
69
- };
70
-
71
- return html`
72
- <button
73
- class="px-4 py-2 rounded font-semibold transition-colors ${variants[variant]}"
74
- @click=${() => emit('click')}
75
- >
76
- ${slot()}
77
- </button>
78
- `;
79
- }, ['variant']);
80
- ```
81
-
82
- ### Shadow DOM (`useShadowDOM = true`) - Encapsulated
83
-
84
- The component **encapsulates its styles** completely. External styles don't affect it, and its styles don't leak out.
85
-
86
- ```javascript
87
- // Calendar with encapsulated styles
88
- $.component('ui-calendar', (props) => {
89
- return html`
90
- <style>
91
- /* These styles won't affect the rest of the page */
92
- .calendar {
93
- font-family: system-ui, sans-serif;
94
- background: white;
95
- border-radius: 12px;
96
- padding: 20px;
97
- box-shadow: 0 4px 12px rgba(0,0,0,0.1);
98
- }
99
- .day {
100
- aspect-ratio: 1;
101
- display: flex;
102
- align-items: center;
103
- justify-content: center;
104
- cursor: pointer;
105
- border-radius: 50%;
106
- }
107
- .day.selected {
108
- background: #2196f3;
109
- color: white;
110
- }
111
- </style>
112
-
113
- <div class="calendar">
114
- ${renderCalendar(props.date())}
115
- </div>
116
- `;
117
- }, ['date'], true); // true = use Shadow DOM
118
- ```
119
-
120
- ## 🎯 Basic Examples
121
-
122
- ### Simple Counter Component
123
-
124
- ```javascript
125
- // counter.js
126
- $.component('my-counter', (props) => {
127
- const count = $(0);
128
-
129
- return html`
130
- <div class="counter">
131
- <p>Count: ${count}</p>
132
- <button @click=${() => count(c => c + 1)}>+</button>
133
- <button @click=${() => count(c => c - 1)}>-</button>
134
- <button @click=${() => count(0)}>Reset</button>
135
- </div>
136
- `;
137
- });
138
- ```
139
-
140
- **Usage:**
141
- ```html
142
- <my-counter></my-counter>
143
- ```
144
-
145
- ### Component with Props
146
-
147
- ```javascript
148
- // greeting.js
149
- $.component('my-greeting', (props) => {
150
- const name = props.name() || 'World';
151
- const greeting = $(() => `Hello, ${name}!`);
152
-
153
- return html`
154
- <div class="greeting">
155
- <h1>${greeting}</h1>
156
- <p>This is a greeting component.</p>
157
- </div>
158
- `;
159
- }, ['name']); // Observe the 'name' attribute
160
- ```
161
-
162
- **Usage:**
163
- ```html
164
- <my-greeting name="John"></my-greeting>
165
- <my-greeting name="Jane"></my-greeting>
166
- ```
167
-
168
- ### Component with Events
169
-
170
- ```javascript
171
- // toggle.js
172
- $.component('my-toggle', (props, { emit }) => {
173
- const isOn = $(props.initial() === 'on');
174
-
175
- const toggle = () => {
176
- isOn(!isOn());
177
- emit('toggle', { isOn: isOn() });
178
- emit(isOn() ? 'on' : 'off');
179
- };
180
-
181
- return html`
182
- <button
183
- class="toggle ${() => isOn() ? 'active' : ''}"
184
- @click=${toggle}
185
- >
186
- ${() => isOn() ? 'ON' : 'OFF'}
187
- </button>
188
- `;
189
- }, ['initial']);
190
- ```
191
-
192
- **Usage:**
193
- ```html
194
- <my-toggle
195
- initial="off"
196
- @toggle=${(e) => console.log('Toggled:', e.detail)}
197
- @on=${() => console.log('Turned on')}
198
- @off=${() => console.log('Turned off')}
199
- ></my-toggle>
200
- ```
201
-
202
- ## 🎨 Advanced Examples
203
-
204
- ### Form Input Component
205
-
206
- ```javascript
207
- // form-input.js
208
- $.component('form-input', (props, { emit }) => {
209
- const value = $(props.value() || '');
210
- const error = $(null);
211
- const touched = $(false);
212
-
213
- // Validation effect
214
- $.effect(() => {
215
- if (props.pattern() && touched()) {
216
- const regex = new RegExp(props.pattern());
217
- const isValid = regex.test(value());
218
- error(isValid ? null : props.errorMessage() || 'Invalid input');
219
- emit('validate', { isValid, value: value() });
220
- }
221
- });
222
-
223
- const handleInput = (e) => {
224
- value(e.target.value);
225
- emit('update', e.target.value);
226
- };
227
-
228
- const handleBlur = () => {
229
- touched(true);
230
- };
231
-
232
- return html`
233
- <div class="form-group">
234
- ${props.label() ? html`
235
- <label class="form-label">
236
- ${props.label()}
237
- ${props.required() ? html`<span class="required">*</span>` : ''}
238
- </label>
239
- ` : ''}
240
-
241
- <input
242
- type="${props.type() || 'text'}"
243
- class="form-control ${() => error() ? 'is-invalid' : ''}"
244
- :value=${value}
245
- @input=${handleInput}
246
- @blur=${handleBlur}
247
- placeholder="${props.placeholder() || ''}"
248
- ?disabled=${props.disabled}
249
- ?required=${props.required}
250
- />
251
-
252
- ${() => error() ? html`
253
- <div class="error-message">${error()}</div>
254
- ` : ''}
255
-
256
- ${props.helpText() ? html`
257
- <small class="help-text">${props.helpText()}</small>
258
- ` : ''}
259
- </div>
260
- `;
261
- }, ['label', 'type', 'value', 'placeholder', 'disabled', 'required', 'pattern', 'errorMessage', 'helpText']);
262
- ```
263
-
264
- **Usage:**
265
- ```html
266
- <form-input
267
- label="Email"
268
- type="email"
269
- required
270
- pattern="^[^\s@]+@[^\s@]+\.[^\s@]+$"
271
- errorMessage="Please enter a valid email"
272
- @update=${(e) => formData.email = e.detail}
273
- @validate=${(e) => setEmailValid(e.detail.isValid)}
274
- >
275
- </form-input>
276
- ```
277
-
278
- ### Modal/Dialog Component
279
-
280
- ```javascript
281
- // modal.js
282
- $.component('my-modal', (props, { slot, emit, onUnmount }) => {
283
- const isOpen = $(false);
284
-
285
- // Handle escape key
286
- const handleKeydown = (e) => {
287
- if (e.key === 'Escape' && isOpen()) {
288
- close();
289
- }
290
- };
291
-
292
- $.effect(() => {
293
- if (isOpen()) {
294
- document.addEventListener('keydown', handleKeydown);
295
- document.body.style.overflow = 'hidden';
296
- } else {
297
- document.removeEventListener('keydown', handleKeydown);
298
- document.body.style.overflow = '';
299
- }
300
- });
301
-
302
- // Cleanup on unmount
303
- onUnmount(() => {
304
- document.removeEventListener('keydown', handleKeydown);
305
- document.body.style.overflow = '';
306
- });
307
-
308
- const open = () => {
309
- isOpen(true);
310
- emit('open');
311
- };
312
-
313
- const close = () => {
314
- isOpen(false);
315
- emit('close');
316
- };
317
-
318
- // Expose methods to parent
319
- props.open = open;
320
- props.close = close;
321
-
322
- return html`
323
- <div>
324
- <!-- Trigger button -->
325
- <button
326
- class="modal-trigger"
327
- @click=${open}
328
- >
329
- ${slot('trigger') || 'Open Modal'}
330
- </button>
331
-
332
- <!-- Modal overlay -->
333
- ${() => isOpen() ? html`
334
- <div class="modal-overlay" @click=${close}>
335
- <div class="modal-content" @click.stop>
336
- <div class="modal-header">
337
- <h3>${props.title() || 'Modal'}</h3>
338
- <button class="close-btn" @click=${close}>&times;</button>
339
- </div>
340
- <div class="modal-body">
341
- ${slot('body')}
342
- </div>
343
- <div class="modal-footer">
344
- ${slot('footer') || html`
345
- <button @click=${close}>Close</button>
346
- `}
347
- </div>
348
- </div>
349
- </div>
350
- ` : ''}
351
- </div>
352
- `;
353
- }, ['title'], false);
354
- ```
355
-
356
- **Usage:**
357
- ```html
358
- <my-modal title="Confirm Delete">
359
- <button slot="trigger">Delete Item</button>
360
-
361
- <div slot="body">
362
- <p>Are you sure you want to delete this item?</p>
363
- <p class="warning">This action cannot be undone.</p>
364
- </div>
365
-
366
- <div slot="footer">
367
- <button class="cancel" @click=${close}>Cancel</button>
368
- <button class="delete" @click=${handleDelete}>Delete</button>
369
- </div>
370
- </my-modal>
371
- ```
372
-
373
- ### Data Table Component
374
-
375
- ```javascript
376
- // data-table.js
377
- $.component('data-table', (props, { emit }) => {
378
- const data = $(props.data() || []);
379
- const columns = $(props.columns() || []);
380
- const sortColumn = $(null);
381
- const sortDirection = $('asc');
382
- const filterText = $('');
383
-
384
- // Computed: filtered and sorted data
385
- const processedData = $(() => {
386
- let result = [...data()];
387
-
388
- // Filter
389
- if (filterText()) {
390
- const search = filterText().toLowerCase();
391
- result = result.filter(row =>
392
- Object.values(row).some(val =>
393
- String(val).toLowerCase().includes(search)
394
- )
395
- );
396
- }
397
-
398
- // Sort
399
- if (sortColumn()) {
400
- const col = sortColumn();
401
- const direction = sortDirection() === 'asc' ? 1 : -1;
402
-
403
- result.sort((a, b) => {
404
- if (a[col] < b[col]) return -direction;
405
- if (a[col] > b[col]) return direction;
406
- return 0;
407
- });
408
- }
409
-
410
- return result;
411
- });
412
-
413
- const handleSort = (col) => {
414
- if (sortColumn() === col) {
415
- sortDirection(sortDirection() === 'asc' ? 'desc' : 'asc');
416
- } else {
417
- sortColumn(col);
418
- sortDirection('asc');
419
- }
420
- emit('sort', { column: col, direction: sortDirection() });
421
- };
422
-
423
- return html`
424
- <div class="data-table">
425
- <!-- Search input -->
426
- <div class="table-toolbar">
427
- <input
428
- type="search"
429
- :value=${filterText}
430
- placeholder="Search..."
431
- class="search-input"
432
- />
433
- <span class="record-count">
434
- ${() => `${processedData().length} of ${data().length} records`}
435
- </span>
436
- </div>
437
-
438
- <!-- Table -->
439
- <table>
440
- <thead>
441
- <tr>
442
- ${columns().map(col => html`
443
- <th
444
- @click=${() => handleSort(col.field)}
445
- class:sortable=${true}
446
- class:sorted=${() => sortColumn() === col.field}
447
- >
448
- ${col.label}
449
- ${() => sortColumn() === col.field ? html`
450
- <span class="sort-icon">
451
- ${sortDirection() === 'asc' ? '↑' : '↓'}
452
- </span>
453
- ` : ''}
454
- </th>
455
- `)}
456
- </tr>
457
- </thead>
458
- <tbody>
459
- ${() => processedData().map(row => html`
460
- <tr @click=${() => emit('row-click', row)}>
461
- ${columns().map(col => html`
462
- <td>${row[col.field]}</td>
463
- `)}
464
- </tr>
465
- `)}
466
- </tbody>
467
- </table>
468
-
469
- <!-- Empty state -->
470
- ${() => processedData().length === 0 ? html`
471
- <div class="empty-state">
472
- No data found
473
- </div>
474
- ` : ''}
475
- </div>
476
- `;
477
- }, ['data', 'columns']);
478
- ```
479
-
480
- **Usage:**
481
- ```javascript
482
- const userColumns = [
483
- { field: 'id', label: 'ID' },
484
- { field: 'name', label: 'Name' },
485
- { field: 'email', label: 'Email' },
486
- { field: 'role', label: 'Role' }
487
- ];
488
-
489
- const userData = [
490
- { id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
491
- { id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
492
- ];
493
- ```
494
-
495
- ```html
496
- <data-table
497
- .data=${userData}
498
- .columns=${userColumns}
499
- @row-click=${(e) => console.log('Row clicked:', e.detail)}
500
- >
501
- </data-table>
502
- ```
503
-
504
- ### Tabs Component
505
-
506
- ```javascript
507
- // tabs.js
508
- $.component('my-tabs', (props, { slot, emit }) => {
509
- const activeTab = $(props.active() || 0);
510
-
511
- // Get all tab headers from slots
512
- const tabs = $(() => {
513
- const headers = slot('tab');
514
- return headers.map((node, index) => ({
515
- index,
516
- title: node.textContent,
517
- content: slot(`panel-${index}`)[0]
518
- }));
519
- });
520
-
521
- $.effect(() => {
522
- emit('change', { index: activeTab(), tab: tabs()[activeTab()] });
523
- });
524
-
525
- return html`
526
- <div class="tabs">
527
- <div class="tab-headers">
528
- ${tabs().map(tab => html`
529
- <button
530
- class="tab-header ${() => activeTab() === tab.index ? 'active' : ''}"
531
- @click=${() => activeTab(tab.index)}
532
- >
533
- ${tab.title}
534
- </button>
535
- `)}
536
- </div>
537
-
538
- <div class="tab-panels">
539
- ${tabs().map(tab => html`
540
- <div
541
- class="tab-panel"
542
- style="display: ${() => activeTab() === tab.index ? 'block' : 'none'}"
543
- >
544
- ${tab.content}
545
- </div>
546
- `)}
547
- </div>
548
- </div>
549
- `;
550
- }, ['active']);
551
- ```
552
-
553
- **Usage:**
554
- ```html
555
- <my-tabs @change=${(e) => console.log('Tab changed:', e.detail)}>
556
- <div slot="tab">Profile</div>
557
- <div slot="panel-0">
558
- <h3>Profile Settings</h3>
559
- <form>...</form>
560
- </div>
561
-
562
- <div slot="tab">Security</div>
563
- <div slot="panel-1">
564
- <h3>Security Settings</h3>
565
- <form>...</form>
566
- </div>
567
-
568
- <div slot="tab">Notifications</div>
569
- <div slot="panel-2">
570
- <h3>Notification Preferences</h3>
571
- <form>...</form>
572
- </div>
573
- </my-tabs>
574
- ```
575
-
576
- ### Component with External Data
577
-
578
- ```javascript
579
- // user-profile.js
580
- $.component('user-profile', (props, { emit, onUnmount }) => {
581
- const user = $(null);
582
- const loading = $(false);
583
- const error = $(null);
584
-
585
- // Fetch user data when userId changes
586
- $.effect(() => {
587
- const userId = props.userId();
588
- if (!userId) return;
589
-
590
- loading(true);
591
- error(null);
592
-
593
- const controller = new AbortController();
594
-
595
- fetch(`/api/users/${userId}`, { signal: controller.signal })
596
- .then(res => res.json())
597
- .then(data => {
598
- user(data);
599
- emit('loaded', data);
600
- })
601
- .catch(err => {
602
- if (err.name !== 'AbortError') {
603
- error(err.message);
604
- emit('error', err);
605
- }
606
- })
607
- .finally(() => loading(false));
608
-
609
- // Cleanup: abort fetch if component unmounts or userId changes
610
- onUnmount(() => controller.abort());
611
- });
612
-
613
- return html`
614
- <div class="user-profile">
615
- ${() => loading() ? html`
616
- <div class="spinner">Loading...</div>
617
- ` : error() ? html`
618
- <div class="error">Error: ${error()}</div>
619
- ` : user() ? html`
620
- <div class="user-info">
621
- <img src="${user().avatar}" class="avatar" />
622
- <h2>${user().name}</h2>
623
- <p>${user().email}</p>
624
- <p>Member since: ${new Date(user().joined).toLocaleDateString()}</p>
625
- </div>
626
- ` : html`
627
- <div class="no-user">No user selected</div>
628
- `}
629
- </div>
630
- `;
631
- }, ['user-id']);
632
- ```
633
-
634
- ## 📦 Component Libraries
635
-
636
- ### Building a Reusable Component Library
637
-
638
- ```javascript
639
- // components/index.js
640
- import { $, html } from 'sigpro';
641
-
642
- // Button component
643
- export const Button = $.component('ui-button', (props, { slot, emit }) => {
644
- const variant = props.variant() || 'primary';
645
- const size = props.size() || 'md';
646
-
647
- const sizes = {
648
- sm: 'px-2 py-1 text-sm',
649
- md: 'px-4 py-2',
650
- lg: 'px-6 py-3 text-lg'
651
- };
652
-
653
- const variants = {
654
- primary: 'bg-blue-500 hover:bg-blue-600 text-white',
655
- secondary: 'bg-gray-500 hover:bg-gray-600 text-white',
656
- danger: 'bg-red-500 hover:bg-red-600 text-white'
657
- };
658
-
659
- return html`
660
- <button
661
- class="rounded font-semibold transition-colors ${sizes[size]} ${variants[variant]}"
662
- ?disabled=${props.disabled}
663
- @click=${() => emit('click')}
664
- >
665
- ${slot()}
666
- </button>
667
- `;
668
- }, ['variant', 'size', 'disabled']);
669
-
670
- // Card component
671
- export const Card = $.component('ui-card', (props, { slot }) => {
672
- return html`
673
- <div class="card border rounded-lg shadow-sm overflow-hidden">
674
- ${props.title() ? html`
675
- <div class="card-header bg-gray-50 px-4 py-3 border-b">
676
- <h3 class="font-semibold">${props.title()}</h3>
677
- </div>
678
- ` : ''}
679
-
680
- <div class="card-body p-4">
681
- ${slot()}
682
- </div>
683
-
684
- ${props.footer() ? html`
685
- <div class="card-footer bg-gray-50 px-4 py-3 border-t">
686
- ${slot('footer')}
687
- </div>
688
- ` : ''}
689
- </div>
690
- `;
691
- }, ['title']);
692
-
693
- // Badge component
694
- export const Badge = $.component('ui-badge', (props, { slot }) => {
695
- const type = props.type() || 'default';
696
-
697
- const types = {
698
- default: 'bg-gray-100 text-gray-800',
699
- success: 'bg-green-100 text-green-800',
700
- warning: 'bg-yellow-100 text-yellow-800',
701
- error: 'bg-red-100 text-red-800',
702
- info: 'bg-blue-100 text-blue-800'
703
- };
704
-
705
- return html`
706
- <span class="inline-block px-2 py-1 text-xs font-semibold rounded ${types[type]}">
707
- ${slot()}
708
- </span>
709
- `;
710
- }, ['type']);
711
-
712
- export { $, html };
713
- ```
714
-
715
- **Usage:**
716
- ```javascript
717
- import { Button, Card, Badge } from './components/index.js';
718
-
719
- // Use components anywhere
720
- const app = html`
721
- <div>
722
- <Card title="Welcome">
723
- <p>This is a card component</p>
724
- <div slot="footer">
725
- <Button variant="primary" @click=${handleClick}>
726
- Save Changes
727
- </Button>
728
- <Badge type="success">New</Badge>
729
- </div>
730
- </Card>
731
- </div>
732
- `;
733
- ```
734
-
735
- ## 🎯 Decision Guide: Light DOM vs Shadow DOM
736
-
737
- | Use Light DOM (`false`) when... | Use Shadow DOM (`true`) when... |
738
- |--------------------------------|-------------------------------|
739
- | Component is part of your main app | Building a UI library for others |
740
- | Using global CSS (Tailwind, Bootstrap) | Creating embeddable widgets |
741
- | Need to inherit theme variables | Styles must be pixel-perfect everywhere |
742
- | Working with existing design system | Component has complex, specific styles |
743
- | Quick prototyping | Distributing to different projects |
744
- | Form elements that should match site | Need style isolation/encapsulation |
745
-
746
- ## 📊 Summary
747
-
748
- | Feature | Description |
749
- |---------|-------------|
750
- | **Native Web Components** | Built on Custom Elements standard |
751
- | **Reactive Props** | Observed attributes become signals |
752
- | **Two Rendering Modes** | Light DOM (default) or Shadow DOM |
753
- | **Automatic Cleanup** | Effects and listeners cleaned up on disconnect |
754
- | **Event System** | Custom events with `emit()` |
755
- | **Slot Support** | Full slot API for content projection |
756
- | **Zero Dependencies** | Pure vanilla JavaScript |
757
-
758
- ---
759
-
760
- > **Pro Tip:** Start with Light DOM components for app-specific UI, and use Shadow DOM when building components that need to work identically across different projects or websites.