sigpro 1.0.14 → 1.2.40

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 (112) hide show
  1. package/Readme.md +164 -1008
  2. package/dist/sigpro.db.js +1 -0
  3. package/dist/sigpro.editor.js +1 -0
  4. package/dist/sigpro.grid.js +78 -0
  5. package/dist/sigpro.js +1 -0
  6. package/dist/sigpro.locale.js +1 -0
  7. package/dist/sigpro.router.js +1 -0
  8. package/dist/sigpro.ui.css +2 -0
  9. package/dist/sigpro.ui.js +1 -0
  10. package/dist/sigpro.vite.js +4 -0
  11. package/package.json +94 -13
  12. package/sigpro.d.ts +395 -0
  13. package/sigpro.ui.d.ts +308 -0
  14. package/.github/workflows/publish.yml +0 -25
  15. package/bun.lock +0 -385
  16. package/docs/404.html +0 -22
  17. package/docs/api/components.html +0 -595
  18. package/docs/api/effects.html +0 -787
  19. package/docs/api/fetch.html +0 -873
  20. package/docs/api/pages.html +0 -405
  21. package/docs/api/quick.html +0 -217
  22. package/docs/api/routing.html +0 -628
  23. package/docs/api/signals.html +0 -683
  24. package/docs/api/storage.html +0 -820
  25. package/docs/assets/api_components.md.BlFwj17l.js +0 -571
  26. package/docs/assets/api_components.md.BlFwj17l.lean.js +0 -1
  27. package/docs/assets/api_effects.md.Br_yStBS.js +0 -763
  28. package/docs/assets/api_effects.md.Br_yStBS.lean.js +0 -1
  29. package/docs/assets/api_fetch.md.DQLBJSoq.js +0 -849
  30. package/docs/assets/api_fetch.md.DQLBJSoq.lean.js +0 -1
  31. package/docs/assets/api_pages.md.BP19nHXw.js +0 -381
  32. package/docs/assets/api_pages.md.BP19nHXw.lean.js +0 -1
  33. package/docs/assets/api_quick.md.BDS3ttnt.js +0 -193
  34. package/docs/assets/api_quick.md.BDS3ttnt.lean.js +0 -1
  35. package/docs/assets/api_routing.md.7SNAZXtp.js +0 -604
  36. package/docs/assets/api_routing.md.7SNAZXtp.lean.js +0 -1
  37. package/docs/assets/api_signals.md.CrW68-BA.js +0 -659
  38. package/docs/assets/api_signals.md.CrW68-BA.lean.js +0 -1
  39. package/docs/assets/api_storage.md.COEWBXHk.js +0 -796
  40. package/docs/assets/api_storage.md.COEWBXHk.lean.js +0 -1
  41. package/docs/assets/app.DtmzNmNl.js +0 -1
  42. package/docs/assets/chunks/framework.C8AWLET_.js +0 -19
  43. package/docs/assets/chunks/theme.yfWKMLQM.js +0 -1
  44. package/docs/assets/guide_getting-started.md.BeQpK3vd.js +0 -172
  45. package/docs/assets/guide_getting-started.md.BeQpK3vd.lean.js +0 -1
  46. package/docs/assets/guide_why.md.DXchYMN-.js +0 -23
  47. package/docs/assets/guide_why.md.DXchYMN-.lean.js +0 -1
  48. package/docs/assets/index.md.uvMJmU4o.js +0 -1
  49. package/docs/assets/index.md.uvMJmU4o.lean.js +0 -1
  50. package/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
  51. package/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
  52. package/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
  53. package/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
  54. package/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
  55. package/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
  56. package/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
  57. package/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
  58. package/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
  59. package/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
  60. package/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
  61. package/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
  62. package/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
  63. package/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
  64. package/docs/assets/style.DJRheFKp.css +0 -1
  65. package/docs/assets/ui_intro.md.gZ21GFqo.js +0 -1
  66. package/docs/assets/ui_intro.md.gZ21GFqo.lean.js +0 -1
  67. package/docs/assets/vite_plugin.md.gDWEi8f0.js +0 -225
  68. package/docs/assets/vite_plugin.md.gDWEi8f0.lean.js +0 -1
  69. package/docs/guide/getting-started.html +0 -196
  70. package/docs/guide/why.html +0 -47
  71. package/docs/hashmap.json +0 -1
  72. package/docs/index.html +0 -25
  73. package/docs/logo.svg +0 -118
  74. package/docs/ui/intro.html +0 -25
  75. package/docs/vite/plugin.html +0 -249
  76. package/docs/vp-icons.css +0 -1
  77. package/index.js +0 -3
  78. package/packages/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  79. package/packages/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  80. package/packages/docs/.vitepress/cache/deps/_metadata.json +0 -40
  81. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js +0 -12951
  82. package/packages/docs/.vitepress/cache/deps/chunk-3S55Y3P7.js.map +0 -7
  83. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js +0 -9719
  84. package/packages/docs/.vitepress/cache/deps/chunk-RLEUDPPB.js.map +0 -7
  85. package/packages/docs/.vitepress/cache/deps/package.json +0 -3
  86. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4505
  87. package/packages/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  88. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  89. package/packages/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  90. package/packages/docs/.vitepress/cache/deps/vue.js +0 -347
  91. package/packages/docs/.vitepress/cache/deps/vue.js.map +0 -7
  92. package/packages/docs/.vitepress/config.js +0 -68
  93. package/packages/docs/api/components.md +0 -760
  94. package/packages/docs/api/effects.md +0 -1039
  95. package/packages/docs/api/fetch.md +0 -998
  96. package/packages/docs/api/pages.md +0 -497
  97. package/packages/docs/api/quick.md +0 -436
  98. package/packages/docs/api/routing.md +0 -784
  99. package/packages/docs/api/signals.md +0 -899
  100. package/packages/docs/api/storage.md +0 -952
  101. package/packages/docs/guide/getting-started.md +0 -308
  102. package/packages/docs/guide/why.md +0 -135
  103. package/packages/docs/index.md +0 -84
  104. package/packages/docs/logo.svg +0 -118
  105. package/packages/docs/public/logo.svg +0 -118
  106. package/packages/docs/ui/intro.md +0 -16
  107. package/packages/docs/vite/plugin.md +0 -423
  108. package/packages/sigpro/plugin.js +0 -91
  109. package/packages/sigpro/plugin.min.js +0 -1
  110. package/packages/sigpro/sigpro.js +0 -631
  111. package/packages/sigpro/sigpro.min.js +0 -1
  112. 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.