snice 4.12.0 → 4.14.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.
Files changed (253) hide show
  1. package/dist/cdn/accordion/snice-accordion.js +1 -1
  2. package/dist/cdn/accordion/snice-accordion.min.js +1 -1
  3. package/dist/cdn/alert/snice-alert.js +1 -1
  4. package/dist/cdn/alert/snice-alert.min.js +1 -1
  5. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  6. package/dist/cdn/app-tiles/snice-app-tiles.min.js +1 -1
  7. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  8. package/dist/cdn/audio-recorder/snice-audio-recorder.min.js +1 -1
  9. package/dist/cdn/avatar/snice-avatar.js +1 -1
  10. package/dist/cdn/avatar/snice-avatar.min.js +1 -1
  11. package/dist/cdn/badge/snice-badge.js +1 -1
  12. package/dist/cdn/badge/snice-badge.min.js +1 -1
  13. package/dist/cdn/banner/snice-banner.js +1 -1
  14. package/dist/cdn/banner/snice-banner.min.js +1 -1
  15. package/dist/cdn/book/snice-book.js +1 -1
  16. package/dist/cdn/book/snice-book.min.js +1 -1
  17. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  18. package/dist/cdn/breadcrumbs/snice-breadcrumbs.min.js +1 -1
  19. package/dist/cdn/button/snice-button.js +1 -1
  20. package/dist/cdn/button/snice-button.min.js +1 -1
  21. package/dist/cdn/calendar/snice-calendar.js +1 -1
  22. package/dist/cdn/calendar/snice-calendar.min.js +1 -1
  23. package/dist/cdn/camera/snice-camera.js +1 -1
  24. package/dist/cdn/camera/snice-camera.min.js +1 -1
  25. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  26. package/dist/cdn/camera-annotate/snice-camera-annotate.min.js +1 -1
  27. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  28. package/dist/cdn/candlestick/snice-candlestick.min.js +1 -1
  29. package/dist/cdn/card/snice-card.js +1 -1
  30. package/dist/cdn/card/snice-card.min.js +1 -1
  31. package/dist/cdn/carousel/snice-carousel.js +1 -1
  32. package/dist/cdn/carousel/snice-carousel.min.js +1 -1
  33. package/dist/cdn/chart/snice-chart.js +1 -1
  34. package/dist/cdn/chart/snice-chart.min.js +1 -1
  35. package/dist/cdn/chat/snice-chat.js +1 -1
  36. package/dist/cdn/chat/snice-chat.min.js +1 -1
  37. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  38. package/dist/cdn/checkbox/snice-checkbox.min.js +1 -1
  39. package/dist/cdn/chip/snice-chip.js +1 -1
  40. package/dist/cdn/chip/snice-chip.min.js +1 -1
  41. package/dist/cdn/code-block/snice-code-block.js +2 -2
  42. package/dist/cdn/code-block/snice-code-block.js.map +1 -1
  43. package/dist/cdn/code-block/snice-code-block.min.js +2 -2
  44. package/dist/cdn/code-block/snice-code-block.min.js.map +1 -1
  45. package/dist/cdn/color-display/snice-color-display.js +1 -1
  46. package/dist/cdn/color-display/snice-color-display.min.js +1 -1
  47. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  48. package/dist/cdn/color-picker/snice-color-picker.min.js +1 -1
  49. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  50. package/dist/cdn/command-palette/snice-command-palette.min.js +1 -1
  51. package/dist/cdn/comments/snice-comments.js +1 -1
  52. package/dist/cdn/comments/snice-comments.min.js +1 -1
  53. package/dist/cdn/countdown/snice-countdown.js +1 -1
  54. package/dist/cdn/countdown/snice-countdown.min.js +1 -1
  55. package/dist/cdn/cropper/snice-cropper.js +1 -1
  56. package/dist/cdn/cropper/snice-cropper.min.js +1 -1
  57. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  58. package/dist/cdn/date-picker/snice-date-picker.min.js +1 -1
  59. package/dist/cdn/diff/snice-diff.js +1 -1
  60. package/dist/cdn/diff/snice-diff.min.js +1 -1
  61. package/dist/cdn/divider/snice-divider.js +1 -1
  62. package/dist/cdn/divider/snice-divider.min.js +1 -1
  63. package/dist/cdn/doc/snice-doc.js +1 -1
  64. package/dist/cdn/doc/snice-doc.min.js +1 -1
  65. package/dist/cdn/draw/snice-draw.js +1 -1
  66. package/dist/cdn/draw/snice-draw.min.js +1 -1
  67. package/dist/cdn/drawer/snice-drawer.js +1 -1
  68. package/dist/cdn/drawer/snice-drawer.min.js +1 -1
  69. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  70. package/dist/cdn/empty-state/snice-empty-state.min.js +1 -1
  71. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  72. package/dist/cdn/file-gallery/snice-file-gallery.min.js +1 -1
  73. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  74. package/dist/cdn/file-upload/snice-file-upload.min.js +1 -1
  75. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  76. package/dist/cdn/flip-card/snice-flip-card.min.js +1 -1
  77. package/dist/cdn/flow/snice-flow.js +1 -1
  78. package/dist/cdn/flow/snice-flow.min.js +1 -1
  79. package/dist/cdn/funnel/snice-funnel.js +1 -1
  80. package/dist/cdn/funnel/snice-funnel.min.js +1 -1
  81. package/dist/cdn/gantt/snice-gantt.js +1 -1
  82. package/dist/cdn/gantt/snice-gantt.min.js +1 -1
  83. package/dist/cdn/gauge/snice-gauge.js +1 -1
  84. package/dist/cdn/gauge/snice-gauge.min.js +1 -1
  85. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  86. package/dist/cdn/heatmap/snice-heatmap.min.js +1 -1
  87. package/dist/cdn/image/snice-image.js +1 -1
  88. package/dist/cdn/image/snice-image.min.js +1 -1
  89. package/dist/cdn/input/snice-input.js +1 -1
  90. package/dist/cdn/input/snice-input.min.js +1 -1
  91. package/dist/cdn/kanban/snice-kanban.js +1 -1
  92. package/dist/cdn/kanban/snice-kanban.min.js +1 -1
  93. package/dist/cdn/kpi/snice-kpi.js +1 -1
  94. package/dist/cdn/kpi/snice-kpi.min.js +1 -1
  95. package/dist/cdn/layout/snice-layout.js +1 -1
  96. package/dist/cdn/layout/snice-layout.min.js +1 -1
  97. package/dist/cdn/link/snice-link.js +1 -1
  98. package/dist/cdn/link/snice-link.min.js +1 -1
  99. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  100. package/dist/cdn/link-preview/snice-link-preview.min.js +1 -1
  101. package/dist/cdn/list/snice-list.js +1 -1
  102. package/dist/cdn/list/snice-list.min.js +1 -1
  103. package/dist/cdn/location/snice-location.js +1 -1
  104. package/dist/cdn/location/snice-location.min.js +1 -1
  105. package/dist/cdn/login/snice-login.js +1 -1
  106. package/dist/cdn/login/snice-login.min.js +1 -1
  107. package/dist/cdn/map/snice-map.js +1 -1
  108. package/dist/cdn/map/snice-map.min.js +1 -1
  109. package/dist/cdn/markdown/snice-markdown.js +1 -1
  110. package/dist/cdn/markdown/snice-markdown.min.js +1 -1
  111. package/dist/cdn/masonry/snice-masonry.js +1 -1
  112. package/dist/cdn/masonry/snice-masonry.min.js +1 -1
  113. package/dist/cdn/menu/snice-menu.js +1 -1
  114. package/dist/cdn/menu/snice-menu.min.js +1 -1
  115. package/dist/cdn/modal/snice-modal.js +1 -1
  116. package/dist/cdn/modal/snice-modal.min.js +1 -1
  117. package/dist/cdn/music-player/snice-music-player.js +1 -1
  118. package/dist/cdn/music-player/snice-music-player.min.js +1 -1
  119. package/dist/cdn/nav/snice-nav.js +1 -1
  120. package/dist/cdn/nav/snice-nav.min.js +1 -1
  121. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  122. package/dist/cdn/network-graph/snice-network-graph.min.js +1 -1
  123. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  124. package/dist/cdn/notification-center/snice-notification-center.min.js +1 -1
  125. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  126. package/dist/cdn/org-chart/snice-org-chart.min.js +1 -1
  127. package/dist/cdn/pagination/snice-pagination.js +1 -1
  128. package/dist/cdn/pagination/snice-pagination.min.js +1 -1
  129. package/dist/cdn/paint/snice-paint.js +1 -1
  130. package/dist/cdn/paint/snice-paint.min.js +1 -1
  131. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  132. package/dist/cdn/pdf-viewer/snice-pdf-viewer.min.js +1 -1
  133. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  134. package/dist/cdn/podcast-player/snice-podcast-player.min.js +1 -1
  135. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  136. package/dist/cdn/pricing-table/snice-pricing-table.min.js +1 -1
  137. package/dist/cdn/progress/snice-progress.js +1 -1
  138. package/dist/cdn/progress/snice-progress.min.js +1 -1
  139. package/dist/cdn/qr-code/README.md +2 -2
  140. package/dist/cdn/qr-code/snice-qr-code.js +149 -20
  141. package/dist/cdn/qr-code/snice-qr-code.js.map +1 -1
  142. package/dist/cdn/qr-code/snice-qr-code.min.js +3 -3
  143. package/dist/cdn/qr-code/snice-qr-code.min.js.map +1 -1
  144. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  145. package/dist/cdn/qr-reader/snice-qr-reader.min.js +1 -1
  146. package/dist/cdn/radio/snice-radio.js +1 -1
  147. package/dist/cdn/radio/snice-radio.min.js +1 -1
  148. package/dist/cdn/rating/snice-rating.js +1 -1
  149. package/dist/cdn/rating/snice-rating.min.js +1 -1
  150. package/dist/cdn/recipe/snice-recipe.js +1 -1
  151. package/dist/cdn/recipe/snice-recipe.min.js +1 -1
  152. package/dist/cdn/runtime/snice-runtime.esm.js +4 -4
  153. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  154. package/dist/cdn/runtime/snice-runtime.esm.min.js +3 -3
  155. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  156. package/dist/cdn/runtime/snice-runtime.js +4 -4
  157. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  158. package/dist/cdn/runtime/snice-runtime.min.js +3 -3
  159. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  160. package/dist/cdn/sankey/snice-sankey.js +1 -1
  161. package/dist/cdn/sankey/snice-sankey.min.js +1 -1
  162. package/dist/cdn/select/snice-select.js +1 -1
  163. package/dist/cdn/select/snice-select.min.js +1 -1
  164. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  165. package/dist/cdn/skeleton/snice-skeleton.min.js +1 -1
  166. package/dist/cdn/slider/snice-slider.js +2 -2
  167. package/dist/cdn/slider/snice-slider.js.map +1 -1
  168. package/dist/cdn/slider/snice-slider.min.js +5 -5
  169. package/dist/cdn/slider/snice-slider.min.js.map +1 -1
  170. package/dist/cdn/sortable/snice-sortable.js +1 -1
  171. package/dist/cdn/sortable/snice-sortable.min.js +1 -1
  172. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  173. package/dist/cdn/sparkline/snice-sparkline.min.js +1 -1
  174. package/dist/cdn/spinner/snice-spinner.js +1 -1
  175. package/dist/cdn/spinner/snice-spinner.min.js +1 -1
  176. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  177. package/dist/cdn/split-pane/snice-split-pane.min.js +1 -1
  178. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  179. package/dist/cdn/spotlight/snice-spotlight.min.js +1 -1
  180. package/dist/cdn/spreadsheet/snice-spreadsheet.js +1 -1
  181. package/dist/cdn/spreadsheet/snice-spreadsheet.min.js +1 -1
  182. package/dist/cdn/stepper/snice-stepper.js +1 -1
  183. package/dist/cdn/stepper/snice-stepper.min.js +1 -1
  184. package/dist/cdn/switch/snice-switch.js +1 -1
  185. package/dist/cdn/switch/snice-switch.min.js +1 -1
  186. package/dist/cdn/table/snice-table.js +1 -1
  187. package/dist/cdn/table/snice-table.min.js +1 -1
  188. package/dist/cdn/tabs/snice-tabs.js +1 -1
  189. package/dist/cdn/tabs/snice-tabs.min.js +1 -1
  190. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  191. package/dist/cdn/tag-input/snice-tag-input.min.js +1 -1
  192. package/dist/cdn/terminal/snice-terminal.js +1 -1
  193. package/dist/cdn/terminal/snice-terminal.min.js +1 -1
  194. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  195. package/dist/cdn/testimonial/snice-testimonial.min.js +1 -1
  196. package/dist/cdn/textarea/snice-textarea.js +1 -1
  197. package/dist/cdn/textarea/snice-textarea.min.js +1 -1
  198. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  199. package/dist/cdn/time-range-picker/snice-time-range-picker.min.js +1 -1
  200. package/dist/cdn/timeline/snice-timeline.js +1 -1
  201. package/dist/cdn/timeline/snice-timeline.min.js +1 -1
  202. package/dist/cdn/timer/snice-timer.js +1 -1
  203. package/dist/cdn/timer/snice-timer.min.js +1 -1
  204. package/dist/cdn/toast/snice-toast.js +1 -1
  205. package/dist/cdn/toast/snice-toast.min.js +1 -1
  206. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  207. package/dist/cdn/tooltip/snice-tooltip.min.js +1 -1
  208. package/dist/cdn/tree/snice-tree.js +1 -1
  209. package/dist/cdn/tree/snice-tree.min.js +1 -1
  210. package/dist/cdn/treemap/snice-treemap.js +1 -1
  211. package/dist/cdn/treemap/snice-treemap.min.js +1 -1
  212. package/dist/cdn/video-player/snice-video-player.js +1 -1
  213. package/dist/cdn/video-player/snice-video-player.min.js +1 -1
  214. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  215. package/dist/cdn/virtual-scroller/snice-virtual-scroller.min.js +1 -1
  216. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  217. package/dist/cdn/waterfall/snice-waterfall.min.js +1 -1
  218. package/dist/cdn/weather/snice-weather.js +1 -1
  219. package/dist/cdn/weather/snice-weather.min.js +1 -1
  220. package/dist/components/code-block/snice-code-block.js +1 -1
  221. package/dist/components/code-block/snice-code-block.js.map +1 -1
  222. package/dist/components/qr-code/qrcode.d.ts +1 -0
  223. package/dist/components/qr-code/qrcode.js +16 -8
  224. package/dist/components/qr-code/qrcode.js.map +1 -1
  225. package/dist/components/qr-code/snice-qr-code.d.ts +5 -2
  226. package/dist/components/qr-code/snice-qr-code.js +132 -11
  227. package/dist/components/qr-code/snice-qr-code.js.map +1 -1
  228. package/dist/components/qr-code/snice-qr-code.types.d.ts +3 -2
  229. package/dist/components/slider/snice-slider.js +1 -1
  230. package/dist/components/slider/snice-slider.js.map +1 -1
  231. package/dist/index.cjs +2 -2
  232. package/dist/index.esm.js +2 -2
  233. package/dist/index.iife.js +2 -2
  234. package/dist/symbols.cjs +1 -1
  235. package/dist/symbols.esm.js +1 -1
  236. package/dist/transitions.cjs +1 -1
  237. package/dist/transitions.esm.js +1 -1
  238. package/dist/types/request-options.d.ts +1 -1
  239. package/docs/ai/api.md +1 -1
  240. package/docs/ai/components/code-block.md +1 -1
  241. package/docs/ai/decorators.md +2 -2
  242. package/docs/ai/patterns.md +17 -6
  243. package/docs/code-block.md +1 -3
  244. package/docs/controllers.md +98 -391
  245. package/docs/elements.md +131 -117
  246. package/docs/events.md +74 -83
  247. package/docs/fetcher.md +64 -76
  248. package/docs/observe.md +13 -33
  249. package/docs/placards.md +6 -16
  250. package/docs/request-response.md +171 -693
  251. package/docs/routing.md +67 -136
  252. package/package.json +1 -1
  253. package/docs/migration-v2-to-v3.md +0 -569
package/docs/elements.md CHANGED
@@ -10,7 +10,7 @@ Elements are the core building blocks of Snice components. They define custom HT
10
10
  - [Queries](#queries)
11
11
  - [Styling](#styling)
12
12
  - [Template Events](#template-events)
13
- - [Advanced Examples](#advanced-examples)
13
+ - [Advanced Examples](#advanced-examples) (Watch, Context, Conditionals)
14
14
 
15
15
  ## Basic Usage
16
16
 
@@ -30,8 +30,10 @@ class MyButton extends HTMLElement {
30
30
 
31
31
  ### Element Decorator Options
32
32
 
33
- The `@element` decorator accepts a single parameter:
33
+ The `@element` decorator accepts:
34
34
  - `tagName: string` - The custom element tag name (must contain a hyphen)
35
+ - `options?: ElementOptions` - Optional configuration
36
+ - `formAssociated?: boolean` - Enable form association (default: false)
35
37
 
36
38
  ## Lifecycle Methods
37
39
 
@@ -106,7 +108,7 @@ class SimpleList extends HTMLElement {
106
108
  - Avoiding differential rendering issues with dynamic attributes
107
109
  - Simple components where full re-render is acceptable
108
110
 
109
- **Note:** When `differential: false`, the render method must return a string (not `html\`...\``) and still honors `<if>` and `<switch>/<case>` meta elements.
111
+ **Note:** When `differential: false`, the render method must return a string (not `html\`...\``). Conditional rendering (`<if>`, `<switch>/<case>`) is NOT available — use ternary operators in the string template instead.
110
112
 
111
113
  ### Imperative Rendering
112
114
 
@@ -132,7 +134,7 @@ class UserCard extends HTMLElement {
132
134
  }
133
135
 
134
136
  @watch('name', 'role')
135
- update() {
137
+ update(oldVal: any, newVal: any, prop: string) {
136
138
  if (!this.$name) return;
137
139
  this.$name.textContent = this.name;
138
140
  this.$role.textContent = this.role;
@@ -195,44 +197,39 @@ class StyledCard extends HTMLElement {
195
197
  }
196
198
  ```
197
199
 
198
- **Multiple Style Methods:**
200
+ **Note:** Only one `@styles()` method is supported per element. If multiple are declared, only the last one is used. Combine all styles in a single method:
201
+
199
202
  ```typescript
200
203
  @styles()
201
- baseStyles() {
204
+ componentStyles() {
202
205
  return css`
203
206
  :host { display: block; }
204
- // ...
205
- `;
206
- }
207
-
208
- @styles()
209
- themeStyles() {
210
- return css`
211
207
  .card { background: var(--bg-color); }
212
- // ...
213
208
  `;
214
209
  }
215
210
  ```
216
211
 
217
212
  ### Lifecycle Decorators
218
213
 
219
- **@ready()** - Called after shadow DOM is ready and initial render completes:
214
+ **@ready()** - Called after styles are applied and event handlers are set up. The initial render may still be completing in a microtask — use `@query` (which re-queries each access) to safely access rendered DOM:
220
215
 
221
216
  ```typescript
222
- import { element, ready, render, html } from 'snice';
217
+ import { element, ready, query, render, html } from 'snice';
218
+
219
+ @element('auto-resize-textarea')
220
+ class AutoResizeTextarea extends HTMLElement {
221
+ @query('textarea') textarea?: HTMLTextAreaElement;
223
222
 
224
- @element('data-loader')
225
- class DataLoader extends HTMLElement {
226
223
  @ready()
227
- async loadData() {
228
- // Called after element is fully initialized
229
- const data = await fetch('/api/data').then(r => r.json());
230
- this.data = data;
224
+ adjustHeight() {
225
+ if (this.textarea) {
226
+ this.textarea.style.height = `${this.textarea.scrollHeight}px`;
227
+ }
231
228
  }
232
229
 
233
230
  @render()
234
231
  renderContent() {
235
- return html`<div>Loading...</div>`;
232
+ return html`<textarea @input=${this.adjustHeight}></textarea>`;
236
233
  }
237
234
  }
238
235
  ```
@@ -240,27 +237,29 @@ class DataLoader extends HTMLElement {
240
237
  **@dispose()** - Called when element is removed from DOM:
241
238
 
242
239
  ```typescript
243
- @element('polling-element')
244
- class PollingElement extends HTMLElement {
245
- private intervalId?: number;
240
+ @element('animated-element')
241
+ class AnimatedElement extends HTMLElement {
242
+ private rafId?: number;
246
243
 
247
244
  @ready()
248
- startPolling() {
249
- this.intervalId = setInterval(() => {
250
- this.updateData();
251
- }, 5000);
245
+ startAnimation() {
246
+ const animate = () => {
247
+ // Update animation frame
248
+ this.rafId = requestAnimationFrame(animate);
249
+ };
250
+ this.rafId = requestAnimationFrame(animate);
252
251
  }
253
252
 
254
253
  @dispose()
255
- stopPolling() {
256
- if (this.intervalId) {
257
- clearInterval(this.intervalId);
254
+ stopAnimation() {
255
+ if (this.rafId) {
256
+ cancelAnimationFrame(this.rafId);
258
257
  }
259
258
  }
260
259
 
261
260
  @render()
262
261
  renderContent() {
263
- return html`<div>Polling...</div>`;
262
+ return html`<canvas width="300" height="200"></canvas>`;
264
263
  }
265
264
  }
266
265
  ```
@@ -279,20 +278,23 @@ await (el as any).ready; // Wait for element to be ready
279
278
 
280
279
  All elements automatically use Shadow DOM for style encapsulation.
281
280
 
282
- ### Accessing Shadow Root
281
+ ### Accessing Shadow DOM Elements
282
+
283
+ Use `@query` instead of manual `shadowRoot.querySelector`:
283
284
 
284
285
  ```typescript
285
286
  @element('shadow-demo')
286
287
  class ShadowDemo extends HTMLElement {
288
+ @query('#content') content?: HTMLElement;
289
+
287
290
  @render()
288
291
  renderContent() {
289
292
  return html`<div id="content">Hello</div>`;
290
293
  }
291
294
 
292
295
  updateContent(text: string) {
293
- const content = this.shadowRoot?.getElementById('content');
294
- if (content) {
295
- content.textContent = text;
296
+ if (this.content) {
297
+ this.content.textContent = text;
296
298
  }
297
299
  }
298
300
  }
@@ -341,7 +343,7 @@ Usage:
341
343
  ```typescript
342
344
  interface PropertyOptions {
343
345
  type?: String | Number | Boolean | Array | Object | Date | BigInt | SimpleArray;
344
- attribute?: string; // Custom attribute name
346
+ attribute?: string | boolean; // Custom attribute name, or false to disable attribute sync
345
347
  converter?: PropertyConverter; // Custom converter
346
348
  hasChanged?: (value: any, oldValue: any) => boolean;
347
349
  }
@@ -351,10 +353,12 @@ interface PropertyOptions {
351
353
 
352
354
  All properties automatically:
353
355
  - Read from DOM attributes when present
354
- - Reflect changes to corresponding attributes
356
+ - Reflect property setter changes to corresponding attributes
355
357
  - Convert between string attributes and typed properties
356
358
  - Trigger re-renders when changed
357
359
 
360
+ **Note:** Initial field values (defaults like `name = 'Anonymous'`) are NOT reflected to attributes. Only changes made via the property setter are reflected. Set `attribute: false` to disable attribute sync entirely.
361
+
358
362
  ```typescript
359
363
  @element('reflected-props')
360
364
  class ReflectedProps extends HTMLElement {
@@ -568,26 +572,28 @@ class ScopedStyles extends HTMLElement {
568
572
 
569
573
  ### Dynamic Styles
570
574
 
575
+ `@styles()` is called **once** during initialization and does not update on property changes. For dynamic styling, use CSS custom properties set in the template:
576
+
571
577
  ```typescript
572
578
  @element('theme-component')
573
579
  class ThemeComponent extends HTMLElement {
574
580
  @property()
575
- primaryColor = '#007bff';
576
-
577
- @property({ type: Number })
578
- fontSize = 16;
581
+ accentColor = '#007bff';
579
582
 
580
583
  @render()
581
584
  renderContent() {
582
- return html`<div class="themed">Themed content</div>`;
585
+ return html`
586
+ <div class="themed" style="--accent: ${this.accentColor}">
587
+ Themed content
588
+ </div>
589
+ `;
583
590
  }
584
591
 
585
592
  @styles()
586
593
  themeStyles() {
587
594
  return css`
588
595
  .themed {
589
- color: ${this.primaryColor};
590
- font-size: ${this.fontSize}px;
596
+ color: var(--accent);
591
597
  }
592
598
  `;
593
599
  }
@@ -722,35 +728,38 @@ class FormHandler extends HTMLElement {
722
728
 
723
729
  ## Advanced Examples
724
730
 
725
- ### Complex Form Component
731
+ ### Form Component
732
+
733
+ Elements handle visual behavior — they render the form and emit events. Business logic (API calls, validation) belongs in controllers:
726
734
 
727
735
  ```typescript
728
- import { element, property, query, watch, render, styles, html, css } from 'snice';
736
+ import { element, property, query, dispatch, render, styles, html, css } from 'snice';
729
737
 
730
738
  @element('registration-form')
731
739
  class RegistrationForm extends HTMLElement {
732
740
  @property({ type: Boolean })
733
741
  loading = false;
734
742
 
735
- @query('form')
736
- form?: HTMLFormElement;
743
+ @query('form') form?: HTMLFormElement;
744
+
745
+ @dispatch('register-submit')
746
+ handleSubmit(event: Event) {
747
+ event.preventDefault();
748
+ return Object.fromEntries(new FormData(this.form!));
749
+ }
737
750
 
738
751
  @render()
739
752
  renderContent() {
740
753
  return html`
741
754
  <form @submit=${this.handleSubmit}>
742
- <h2>Register</h2>
743
-
744
755
  <div class="field">
745
756
  <label>Username</label>
746
757
  <input type="text" name="username" required>
747
758
  </div>
748
-
749
759
  <div class="field">
750
760
  <label>Email</label>
751
761
  <input type="email" name="email" required>
752
762
  </div>
753
-
754
763
  <button type="submit" ?disabled=${this.loading}>
755
764
  ${this.loading ? 'Registering...' : 'Register'}
756
765
  </button>
@@ -764,78 +773,37 @@ class RegistrationForm extends HTMLElement {
764
773
  :host {
765
774
  display: block;
766
775
  max-width: 400px;
767
- margin: 0 auto;
768
- }
769
-
770
- form {
771
- padding: 20px;
772
- background: white;
773
- border-radius: 8px;
774
776
  }
775
777
 
776
778
  .field {
777
- margin-bottom: 20px;
779
+ margin-bottom: 1rem;
778
780
  }
779
781
 
780
782
  label {
781
783
  display: block;
782
- margin-bottom: 5px;
784
+ margin-bottom: 0.25rem;
783
785
  font-weight: bold;
784
786
  }
785
787
 
786
788
  input {
787
789
  width: 100%;
788
- padding: 8px 12px;
789
- border: 1px solid #ddd;
790
+ padding: 0.5rem;
791
+ border: 1px solid var(--snice-color-border, #ddd);
790
792
  border-radius: 4px;
791
793
  }
792
794
 
793
- button {
794
- width: 100%;
795
- padding: 10px;
796
- background: #007bff;
797
- color: white;
798
- border: none;
799
- border-radius: 4px;
800
- cursor: pointer;
801
- }
802
-
803
795
  button:disabled {
804
796
  opacity: 0.6;
805
797
  cursor: not-allowed;
806
798
  }
807
799
  `;
808
800
  }
809
-
810
- async handleSubmit(event: Event) {
811
- event.preventDefault();
812
-
813
- this.loading = true;
814
-
815
- try {
816
- const formData = new FormData(this.form!);
817
-
818
- // Simulate API call
819
- await new Promise(resolve => setTimeout(resolve, 2000));
820
-
821
- this.dispatchEvent(new CustomEvent('registration-success', {
822
- detail: Object.fromEntries(formData),
823
- bubbles: true
824
- }));
825
-
826
- this.form?.reset();
827
- } catch (error) {
828
- console.error('Registration failed:', error);
829
- } finally {
830
- this.loading = false;
831
- }
832
- }
833
801
  }
834
802
  ```
835
803
 
836
804
  ### Watch Decorator
837
805
 
838
- Use `@watch` to react to property changes:
806
+ Use `@watch` to react to property changes. Handlers receive three arguments: `(oldValue, newValue, propertyName)`.
839
807
 
840
808
  ```typescript
841
809
  @element('reactive-component')
@@ -847,8 +815,8 @@ class ReactiveComponent extends HTMLElement {
847
815
  score = 0;
848
816
 
849
817
  @watch('userName')
850
- onUserNameChange(oldVal: string, newVal: string) {
851
- console.log(`Name changed from ${oldVal} to ${newVal}`);
818
+ onUserNameChange(oldVal: string, newVal: string, prop: string) {
819
+ console.log(`${prop} changed from ${oldVal} to ${newVal}`);
852
820
  }
853
821
 
854
822
  @watch('score')
@@ -858,6 +826,12 @@ class ReactiveComponent extends HTMLElement {
858
826
  }
859
827
  }
860
828
 
829
+ // Wildcard watcher — fires on any @property change
830
+ @watch('*')
831
+ onAnyChange(oldVal: any, newVal: any, prop: string) {
832
+ console.log(`${prop}: ${oldVal} → ${newVal}`);
833
+ }
834
+
861
835
  @render()
862
836
  renderContent() {
863
837
  return html`
@@ -870,6 +844,60 @@ class ReactiveComponent extends HTMLElement {
870
844
  }
871
845
  ```
872
846
 
847
+ ### @context() Decorator
848
+
849
+ Receive router context updates. The decorated method is called whenever the router context changes (navigation, app context update, etc.):
850
+
851
+ ```typescript
852
+ import { element, context, property, render, html } from 'snice';
853
+ import type { Context, Placard } from 'snice';
854
+
855
+ @element('nav-bar')
856
+ class NavBar extends HTMLElement {
857
+ @property({ type: Array })
858
+ placards: Placard[] = [];
859
+
860
+ @property()
861
+ currentRoute = '';
862
+
863
+ @context()
864
+ onContextUpdate(ctx: Context) {
865
+ this.placards = ctx.navigation.placards;
866
+ this.currentRoute = ctx.navigation.route;
867
+ }
868
+
869
+ @render()
870
+ renderContent() {
871
+ return html`
872
+ <nav>
873
+ ${this.placards
874
+ .filter(p => p.show !== false)
875
+ .map(p => html`
876
+ <a href="#/${p.name}" class="${this.currentRoute === p.name ? 'active' : ''}">
877
+ ${p.icon} ${p.title}
878
+ </a>
879
+ `)}
880
+ </nav>
881
+ `;
882
+ }
883
+ }
884
+ ```
885
+
886
+ **Context Options:**
887
+
888
+ ```typescript
889
+ @context({ debounce: 300 }) // Wait 300ms after last change
890
+ @context({ throttle: 500 }) // At most once per 500ms
891
+ @context({ once: true }) // Only called once, then auto-unregisters
892
+ ```
893
+
894
+ The `Context` object provides:
895
+ - `ctx.application` — App context (theme, auth, config, etc.)
896
+ - `ctx.navigation.route` — Current route path
897
+ - `ctx.navigation.params` — Route parameters
898
+ - `ctx.navigation.placards` — All registered page placards
899
+ - `ctx.fetch` — Fetch with middleware support (see Fetcher docs)
900
+
873
901
  ### Conditional Rendering
874
902
 
875
903
  ```typescript
@@ -898,18 +926,4 @@ class ConditionalContent extends HTMLElement {
898
926
  }
899
927
  ```
900
928
 
901
- ## Best Practices
902
-
903
- 1. **Use @render() for templates**: Always return `html\`...\`` tagged templates
904
- 2. **Use @styles() for CSS**: Always return `css\`...\`` tagged templates
905
- 3. **Leverage auto-rendering**: Properties automatically trigger re-renders
906
- 4. **Use template events**: Handle events with `@event=${handler}` in templates
907
- 5. **Clean up resources**: Use @dispose() for cleanup tasks
908
- 6. **Type your queries**: Use proper TypeScript types for queried elements
909
- 7. **Handle errors**: Wrap async operations in try-catch blocks
910
-
911
- ## Removed in v3.0.0
912
929
 
913
- - **@part decorator**: Removed in favor of differential rendering. Use `@render()` with templates instead.
914
- - **html() method**: Replaced with `@render()` decorator
915
- - **css() method**: Replaced with `@styles()` decorator
package/docs/events.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # Events API Documentation
2
2
 
3
- Event handling in Snice provides two powerful approaches: **template event syntax** and the **`@on` decorator** (fully restored from v2.5.4!). The `@on` decorator now works in **both elements AND controllers** with full event delegation, keyboard modifiers, debounce/throttle, and more. Additionally, the `@dispatch` decorator enables automatic custom event dispatching.
3
+ Event handling in Snice provides two powerful approaches: **template event syntax** and the **`@on` decorator**. The `@on` decorator works in **both elements AND controllers** with full event delegation, keyboard modifiers, debounce/throttle, and more. Additionally, the `@dispatch` decorator enables automatic custom event dispatching.
4
4
 
5
5
  ## Table of Contents
6
6
  - [Template Event Syntax](#template-event-syntax)
7
- - [@on Decorator (v2.5.4 RESTORED!)](#on-decorator-v254-restored)
7
+ - [@on Decorator](#on-decorator)
8
8
  - [@dispatch Decorator](#dispatch-decorator)
9
9
  - [Custom Events](#custom-events)
10
10
  - [Event Delegation](#event-delegation)
@@ -247,15 +247,15 @@ class TaskList extends HTMLElement {
247
247
  }
248
248
  ```
249
249
 
250
- ## @on Decorator (v2.5.4 RESTORED!)
250
+ ## @on Decorator
251
251
 
252
- The `@on` decorator is **fully restored from v2.5.4** and now works in **both elements AND controllers**! It provides powerful event delegation, keyboard modifiers, debounce/throttle, and automatic event handling features.
252
+ The `@on` decorator works in **both elements AND controllers**. It provides powerful event delegation, keyboard modifiers, debounce/throttle, and automatic event handling features.
253
253
 
254
254
  **Use `@on` when you need:**
255
255
  - Event delegation with CSS selectors
256
256
  - Keyboard modifier matching (`Enter`, `ctrl+s`, etc.)
257
257
  - Debounce or throttle
258
- - Multiple events on one handler
258
+ - Multiple events on one handler (accepts `string[]`: `@on(['mouseenter', 'focus'])`)
259
259
  - Automatic preventDefault or stopPropagation
260
260
 
261
261
  ### Basic Controller Usage
@@ -378,12 +378,9 @@ interface OnOptions {
378
378
  preventDefault?: boolean; // Automatically call preventDefault on the event
379
379
  stopPropagation?: boolean; // Automatically call stopPropagation on the event
380
380
 
381
- // Timing controls (v3.0.0 enhancements!)
381
+ // Timing controls
382
382
  debounce?: number; // Debounce the handler by specified milliseconds
383
383
  throttle?: number; // Throttle the handler by specified milliseconds
384
-
385
- // Event delegation
386
- target?: string; // CSS selector to target specific elements within shadow root
387
384
  }
388
385
  ```
389
386
 
@@ -437,8 +434,8 @@ While template syntax is preferred, `@on` can also be used in elements:
437
434
  ```typescript
438
435
  import { element, on, render, html } from 'snice';
439
436
 
440
- @element('legacy-button')
441
- class LegacyButton extends HTMLElement {
437
+ @element('my-button')
438
+ class MyButton extends HTMLElement {
442
439
  @render()
443
440
  renderContent() {
444
441
  return html`<button class="btn">Click me</button>`;
@@ -505,6 +502,8 @@ input.addEventListener('value-changed', (e: CustomEvent) => {
505
502
 
506
503
  ### Event Options
507
504
 
505
+ Events dispatched by `@dispatch` default to `bubbles: true` and `composed: true` (crosses shadow DOM boundaries). Override if needed:
506
+
508
507
  ```typescript
509
508
  @element('status-indicator')
510
509
  class StatusIndicator extends HTMLElement {
@@ -513,7 +512,8 @@ class StatusIndicator extends HTMLElement {
513
512
  return html`<div>Status</div>`;
514
513
  }
515
514
 
516
- @dispatch('status-changed', { bubbles: true, composed: true })
515
+ // Defaults: bubbles: true, composed: true
516
+ @dispatch('status-changed')
517
517
  updateStatus(status: string) {
518
518
  return {
519
519
  status,
@@ -523,36 +523,77 @@ class StatusIndicator extends HTMLElement {
523
523
  }
524
524
  ```
525
525
 
526
- ### Multiple Dispatches
526
+ ### DispatchOptions
527
527
 
528
528
  ```typescript
529
- @element('data-manager')
530
- class DataManager extends HTMLElement {
531
- private data: any[] = [];
529
+ interface DispatchOptions extends EventInit {
530
+ dispatchOnUndefined?: boolean; // Skip dispatch when return is undefined (default: true)
531
+ debounce?: number; // Debounce dispatch by ms
532
+ throttle?: number; // Throttle dispatch by ms
533
+ }
534
+ ```
535
+
536
+ ### Debounce/Throttle
532
537
 
538
+ ```typescript
539
+ @element('search-box')
540
+ class SearchBox extends HTMLElement {
533
541
  @render()
534
542
  renderContent() {
535
- return html`<div>Data Manager</div>`;
543
+ return html`<input @input=${this.handleInput}>`;
536
544
  }
537
545
 
538
- @dispatch('item-added')
539
- addItem(item: any) {
540
- this.data.push(item);
541
- return { item, total: this.data.length };
546
+ handleInput(e: Event) {
547
+ this.emitSearch((e.target as HTMLInputElement).value);
542
548
  }
543
549
 
544
- @dispatch('item-removed')
545
- removeItem(id: string) {
546
- const item = this.data.find(i => i.id === id);
547
- this.data = this.data.filter(i => i.id !== id);
548
- return { id, item, total: this.data.length };
550
+ @dispatch('search-query', { debounce: 300 })
551
+ emitSearch(query: string) {
552
+ return { query };
549
553
  }
554
+ }
555
+ ```
556
+
557
+ ### Async Methods
558
+
559
+ `@dispatch` works with async methods — the event dispatches after the promise resolves:
550
560
 
551
- @dispatch('data-cleared')
552
- clearData() {
553
- const count = this.data.length;
554
- this.data = [];
555
- return { clearedCount: count };
561
+ ```typescript
562
+ @dispatch('validation-complete')
563
+ async validate() {
564
+ const result = await this.runValidation();
565
+ return { valid: result.isValid, errors: result.errors };
566
+ }
567
+ ```
568
+
569
+ ### Multiple Events
570
+
571
+ ```typescript
572
+ @element('color-picker')
573
+ class ColorPicker extends HTMLElement {
574
+ @property() color = '#000000';
575
+
576
+ @render()
577
+ renderContent() {
578
+ return html`
579
+ <input type="color" .value=${this.color} @input=${this.handleInput}>
580
+ <button @click=${this.confirm}>OK</button>
581
+ `;
582
+ }
583
+
584
+ handleInput(e: Event) {
585
+ this.changeColor((e.target as HTMLInputElement).value);
586
+ }
587
+
588
+ @dispatch('color-preview')
589
+ changeColor(color: string) {
590
+ this.color = color;
591
+ return { color };
592
+ }
593
+
594
+ @dispatch('color-selected')
595
+ confirm() {
596
+ return { color: this.color };
556
597
  }
557
598
  }
558
599
  ```
@@ -615,8 +656,8 @@ class TableController implements IController {
615
656
  // Single event listener handles all rows
616
657
  @on('click', 'tr')
617
658
  handleRowClick(event: MouseEvent) {
618
- const row = event.currentTarget as HTMLTableRowElement;
619
- console.log('Row clicked:', row.dataset.id);
659
+ const row = (event.target as HTMLElement).closest('tr');
660
+ console.log('Row clicked:', row?.dataset.id);
620
661
  }
621
662
 
622
663
  // Handle button clicks in cells
@@ -754,54 +795,4 @@ class KeyboardController implements IController {
754
795
  }
755
796
  ```
756
797
 
757
- ## Best Practices
758
-
759
- ### For Elements
760
-
761
- 1. **Prefer template syntax**: Use `@event=${handler}` in templates
762
- 2. **Use arrow functions for parameters**: `@click=${() => this.delete(id)}`
763
- 3. **Handle events at appropriate level**: Use event delegation for dynamic content
764
- 4. **Prevent default when needed**: Call `e.preventDefault()` in handlers
765
- 5. **Use keyboard shortcuts**: Leverage `@keydown.ctrl+s` syntax
766
-
767
- ### For Controllers
768
-
769
- 1. **Use @on decorator**: Controllers should use `@on` for event handling
770
- 2. **Leverage event delegation**: Use selectors to handle dynamic content
771
- 3. **Throttle/debounce high-frequency events**: Use `{ throttle }` or `{ debounce }` options
772
- 4. **Clean up automatically**: Event listeners are cleaned up on detach
773
- 5. **Handle keyboard shortcuts**: Use `@on('keydown:Ctrl+S')` syntax
774
-
775
- ### General
776
-
777
- 1. **Type your events**: Use TypeScript event types for type safety
778
- 2. **Stop propagation carefully**: Only use `stopPropagation()` when necessary
779
- 3. **Use custom events**: Dispatch custom events for component communication
780
- 4. **Compose events**: Use `{ composed: true }` for cross-shadow-DOM events
781
- 5. **Test event handlers**: Write tests for all event handling logic
782
-
783
- ## Event Types Reference
784
-
785
- **Mouse Events:**
786
- - `click`, `dblclick`, `mousedown`, `mouseup`
787
- - `mouseenter`, `mouseleave`, `mousemove`, `mouseover`, `mouseout`
788
- - `contextmenu`
789
-
790
- **Keyboard Events:**
791
- - `keydown`, `keyup`, `keypress`
792
-
793
- **Form Events:**
794
- - `input`, `change`, `submit`, `reset`
795
- - `focus`, `blur`, `focusin`, `focusout`
796
-
797
- **Drag Events:**
798
- - `drag`, `dragstart`, `dragend`
799
- - `dragenter`, `dragover`, `dragleave`, `drop`
800
-
801
- **Touch Events:**
802
- - `touchstart`, `touchmove`, `touchend`, `touchcancel`
803
798
 
804
- **Other Events:**
805
- - `scroll`, `resize`, `load`, `error`
806
- - `animationstart`, `animationend`, `animationiteration`
807
- - `transitionstart`, `transitionend`