snice 4.13.0 → 4.15.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 (391) hide show
  1. package/adapters/react/doc.d.ts +1 -0
  2. package/adapters/react/doc.d.ts.map +1 -1
  3. package/adapters/react/doc.js +1 -1
  4. package/adapters/react/doc.js.map +1 -1
  5. package/adapters/react/doc.tsx +2 -1
  6. package/adapters/react/modal.d.ts +2 -0
  7. package/adapters/react/modal.d.ts.map +1 -1
  8. package/adapters/react/modal.js +1 -1
  9. package/adapters/react/modal.js.map +1 -1
  10. package/adapters/react/modal.tsx +3 -1
  11. package/dist/cdn/accordion/snice-accordion.js +1 -1
  12. package/dist/cdn/accordion/snice-accordion.min.js +1 -1
  13. package/dist/cdn/alert/snice-alert.js +1 -1
  14. package/dist/cdn/alert/snice-alert.min.js +1 -1
  15. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  16. package/dist/cdn/app-tiles/snice-app-tiles.min.js +1 -1
  17. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  18. package/dist/cdn/audio-recorder/snice-audio-recorder.min.js +1 -1
  19. package/dist/cdn/avatar/snice-avatar.js +1 -1
  20. package/dist/cdn/avatar/snice-avatar.min.js +1 -1
  21. package/dist/cdn/badge/snice-badge.js +1 -1
  22. package/dist/cdn/badge/snice-badge.min.js +1 -1
  23. package/dist/cdn/banner/snice-banner.js +1 -1
  24. package/dist/cdn/banner/snice-banner.min.js +1 -1
  25. package/dist/cdn/book/README.md +2 -2
  26. package/dist/cdn/book/snice-book.js +29 -8
  27. package/dist/cdn/book/snice-book.js.map +1 -1
  28. package/dist/cdn/book/snice-book.min.js +3 -3
  29. package/dist/cdn/book/snice-book.min.js.map +1 -1
  30. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  31. package/dist/cdn/breadcrumbs/snice-breadcrumbs.min.js +1 -1
  32. package/dist/cdn/button/snice-button.js +1 -1
  33. package/dist/cdn/button/snice-button.min.js +1 -1
  34. package/dist/cdn/calendar/snice-calendar.js +1 -1
  35. package/dist/cdn/calendar/snice-calendar.min.js +1 -1
  36. package/dist/cdn/camera/snice-camera.js +1 -1
  37. package/dist/cdn/camera/snice-camera.min.js +1 -1
  38. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  39. package/dist/cdn/camera-annotate/snice-camera-annotate.min.js +1 -1
  40. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  41. package/dist/cdn/candlestick/snice-candlestick.min.js +1 -1
  42. package/dist/cdn/card/snice-card.js +1 -1
  43. package/dist/cdn/card/snice-card.min.js +1 -1
  44. package/dist/cdn/carousel/snice-carousel.js +1 -1
  45. package/dist/cdn/carousel/snice-carousel.min.js +1 -1
  46. package/dist/cdn/chart/snice-chart.js +1 -1
  47. package/dist/cdn/chart/snice-chart.min.js +1 -1
  48. package/dist/cdn/chat/snice-chat.js +1 -1
  49. package/dist/cdn/chat/snice-chat.min.js +1 -1
  50. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  51. package/dist/cdn/checkbox/snice-checkbox.min.js +1 -1
  52. package/dist/cdn/chip/snice-chip.js +1 -1
  53. package/dist/cdn/chip/snice-chip.min.js +1 -1
  54. package/dist/cdn/code-block/snice-code-block.js +5 -5
  55. package/dist/cdn/code-block/snice-code-block.js.map +1 -1
  56. package/dist/cdn/code-block/snice-code-block.min.js +3 -3
  57. package/dist/cdn/code-block/snice-code-block.min.js.map +1 -1
  58. package/dist/cdn/color-display/snice-color-display.js +1 -1
  59. package/dist/cdn/color-display/snice-color-display.min.js +1 -1
  60. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  61. package/dist/cdn/color-picker/snice-color-picker.min.js +1 -1
  62. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  63. package/dist/cdn/command-palette/snice-command-palette.min.js +1 -1
  64. package/dist/cdn/comments/snice-comments.js +1 -1
  65. package/dist/cdn/comments/snice-comments.min.js +1 -1
  66. package/dist/cdn/countdown/snice-countdown.js +1 -1
  67. package/dist/cdn/countdown/snice-countdown.min.js +1 -1
  68. package/dist/cdn/cropper/snice-cropper.js +1 -1
  69. package/dist/cdn/cropper/snice-cropper.min.js +1 -1
  70. package/dist/cdn/date-picker/README.md +1 -1
  71. package/dist/cdn/date-picker/snice-date-picker.js +49 -39
  72. package/dist/cdn/date-picker/snice-date-picker.js.map +1 -1
  73. package/dist/cdn/date-picker/snice-date-picker.min.js +4 -4
  74. package/dist/cdn/date-picker/snice-date-picker.min.js.map +1 -1
  75. package/dist/cdn/diff/snice-diff.js +1 -1
  76. package/dist/cdn/diff/snice-diff.min.js +1 -1
  77. package/dist/cdn/divider/snice-divider.js +1 -1
  78. package/dist/cdn/divider/snice-divider.min.js +1 -1
  79. package/dist/cdn/doc/README.md +2 -2
  80. package/dist/cdn/doc/snice-doc.js +221 -35
  81. package/dist/cdn/doc/snice-doc.js.map +1 -1
  82. package/dist/cdn/doc/snice-doc.min.js +2 -2
  83. package/dist/cdn/doc/snice-doc.min.js.map +1 -1
  84. package/dist/cdn/draw/snice-draw.js +1 -1
  85. package/dist/cdn/draw/snice-draw.min.js +1 -1
  86. package/dist/cdn/drawer/snice-drawer.js +1 -1
  87. package/dist/cdn/drawer/snice-drawer.min.js +1 -1
  88. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  89. package/dist/cdn/empty-state/snice-empty-state.min.js +1 -1
  90. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  91. package/dist/cdn/file-gallery/snice-file-gallery.min.js +1 -1
  92. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  93. package/dist/cdn/file-upload/snice-file-upload.min.js +1 -1
  94. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  95. package/dist/cdn/flip-card/snice-flip-card.min.js +1 -1
  96. package/dist/cdn/flow/snice-flow.js +1 -1
  97. package/dist/cdn/flow/snice-flow.min.js +1 -1
  98. package/dist/cdn/funnel/snice-funnel.js +1 -1
  99. package/dist/cdn/funnel/snice-funnel.min.js +1 -1
  100. package/dist/cdn/gantt/snice-gantt.js +1 -1
  101. package/dist/cdn/gantt/snice-gantt.min.js +1 -1
  102. package/dist/cdn/gauge/snice-gauge.js +1 -1
  103. package/dist/cdn/gauge/snice-gauge.min.js +1 -1
  104. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  105. package/dist/cdn/heatmap/snice-heatmap.min.js +1 -1
  106. package/dist/cdn/image/snice-image.js +1 -1
  107. package/dist/cdn/image/snice-image.min.js +1 -1
  108. package/dist/cdn/input/snice-input.js +1 -1
  109. package/dist/cdn/input/snice-input.min.js +1 -1
  110. package/dist/cdn/kanban/snice-kanban.js +1 -1
  111. package/dist/cdn/kanban/snice-kanban.min.js +1 -1
  112. package/dist/cdn/kpi/snice-kpi.js +1 -1
  113. package/dist/cdn/kpi/snice-kpi.min.js +1 -1
  114. package/dist/cdn/layout/snice-layout.js +1 -1
  115. package/dist/cdn/layout/snice-layout.min.js +1 -1
  116. package/dist/cdn/link/snice-link.js +1 -1
  117. package/dist/cdn/link/snice-link.min.js +1 -1
  118. package/dist/cdn/link-preview/snice-link-preview.js +2 -2
  119. package/dist/cdn/link-preview/snice-link-preview.js.map +1 -1
  120. package/dist/cdn/link-preview/snice-link-preview.min.js +2 -2
  121. package/dist/cdn/link-preview/snice-link-preview.min.js.map +1 -1
  122. package/dist/cdn/list/snice-list.js +4 -4
  123. package/dist/cdn/list/snice-list.js.map +1 -1
  124. package/dist/cdn/list/snice-list.min.js +2 -2
  125. package/dist/cdn/list/snice-list.min.js.map +1 -1
  126. package/dist/cdn/location/snice-location.js +1 -1
  127. package/dist/cdn/location/snice-location.min.js +1 -1
  128. package/dist/cdn/login/snice-login.js +1 -1
  129. package/dist/cdn/login/snice-login.min.js +1 -1
  130. package/dist/cdn/map/snice-map.js +1 -1
  131. package/dist/cdn/map/snice-map.min.js +1 -1
  132. package/dist/cdn/markdown/snice-markdown.js +1 -1
  133. package/dist/cdn/markdown/snice-markdown.min.js +1 -1
  134. package/dist/cdn/masonry/snice-masonry.js +1 -1
  135. package/dist/cdn/masonry/snice-masonry.min.js +1 -1
  136. package/dist/cdn/menu/snice-menu.js +2 -2
  137. package/dist/cdn/menu/snice-menu.js.map +1 -1
  138. package/dist/cdn/menu/snice-menu.min.js +2 -2
  139. package/dist/cdn/menu/snice-menu.min.js.map +1 -1
  140. package/dist/cdn/modal/README.md +2 -2
  141. package/dist/cdn/modal/snice-modal.js +34 -18
  142. package/dist/cdn/modal/snice-modal.js.map +1 -1
  143. package/dist/cdn/modal/snice-modal.min.js +24 -20
  144. package/dist/cdn/modal/snice-modal.min.js.map +1 -1
  145. package/dist/cdn/music-player/README.md +2 -2
  146. package/dist/cdn/music-player/snice-music-player.js +8 -1
  147. package/dist/cdn/music-player/snice-music-player.js.map +1 -1
  148. package/dist/cdn/music-player/snice-music-player.min.js +3 -3
  149. package/dist/cdn/music-player/snice-music-player.min.js.map +1 -1
  150. package/dist/cdn/nav/snice-nav.js +1 -1
  151. package/dist/cdn/nav/snice-nav.min.js +1 -1
  152. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  153. package/dist/cdn/network-graph/snice-network-graph.min.js +1 -1
  154. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  155. package/dist/cdn/notification-center/snice-notification-center.min.js +1 -1
  156. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  157. package/dist/cdn/org-chart/snice-org-chart.min.js +1 -1
  158. package/dist/cdn/pagination/snice-pagination.js +1 -1
  159. package/dist/cdn/pagination/snice-pagination.min.js +1 -1
  160. package/dist/cdn/paint/snice-paint.js +1 -1
  161. package/dist/cdn/paint/snice-paint.min.js +1 -1
  162. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  163. package/dist/cdn/pdf-viewer/snice-pdf-viewer.min.js +1 -1
  164. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  165. package/dist/cdn/podcast-player/snice-podcast-player.min.js +1 -1
  166. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  167. package/dist/cdn/pricing-table/snice-pricing-table.min.js +1 -1
  168. package/dist/cdn/progress/snice-progress.js +1 -1
  169. package/dist/cdn/progress/snice-progress.min.js +1 -1
  170. package/dist/cdn/qr-code/README.md +2 -2
  171. package/dist/cdn/qr-code/snice-qr-code.js +149 -20
  172. package/dist/cdn/qr-code/snice-qr-code.js.map +1 -1
  173. package/dist/cdn/qr-code/snice-qr-code.min.js +3 -3
  174. package/dist/cdn/qr-code/snice-qr-code.min.js.map +1 -1
  175. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  176. package/dist/cdn/qr-reader/snice-qr-reader.min.js +1 -1
  177. package/dist/cdn/radio/README.md +2 -2
  178. package/dist/cdn/radio/snice-radio.js +23 -3
  179. package/dist/cdn/radio/snice-radio.js.map +1 -1
  180. package/dist/cdn/radio/snice-radio.min.js +3 -3
  181. package/dist/cdn/radio/snice-radio.min.js.map +1 -1
  182. package/dist/cdn/rating/snice-rating.js +1 -1
  183. package/dist/cdn/rating/snice-rating.min.js +1 -1
  184. package/dist/cdn/recipe/snice-recipe.js +1 -1
  185. package/dist/cdn/recipe/snice-recipe.min.js +1 -1
  186. package/dist/cdn/runtime/README.md +2 -2
  187. package/dist/cdn/runtime/snice-runtime.esm.js +514 -47
  188. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  189. package/dist/cdn/runtime/snice-runtime.esm.min.js +6 -6
  190. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  191. package/dist/cdn/runtime/snice-runtime.js +6420 -5951
  192. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  193. package/dist/cdn/runtime/snice-runtime.min.js +18 -18
  194. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  195. package/dist/cdn/sankey/snice-sankey.js +1 -1
  196. package/dist/cdn/sankey/snice-sankey.min.js +1 -1
  197. package/dist/cdn/select/README.md +2 -2
  198. package/dist/cdn/select/snice-select.js +46 -92
  199. package/dist/cdn/select/snice-select.js.map +1 -1
  200. package/dist/cdn/select/snice-select.min.js +5 -13
  201. package/dist/cdn/select/snice-select.min.js.map +1 -1
  202. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  203. package/dist/cdn/skeleton/snice-skeleton.min.js +1 -1
  204. package/dist/cdn/slider/snice-slider.js +2 -2
  205. package/dist/cdn/slider/snice-slider.js.map +1 -1
  206. package/dist/cdn/slider/snice-slider.min.js +5 -5
  207. package/dist/cdn/slider/snice-slider.min.js.map +1 -1
  208. package/dist/cdn/sortable/snice-sortable.js +1 -1
  209. package/dist/cdn/sortable/snice-sortable.min.js +1 -1
  210. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  211. package/dist/cdn/sparkline/snice-sparkline.min.js +1 -1
  212. package/dist/cdn/spinner/snice-spinner.js +1 -1
  213. package/dist/cdn/spinner/snice-spinner.min.js +1 -1
  214. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  215. package/dist/cdn/split-pane/snice-split-pane.min.js +1 -1
  216. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  217. package/dist/cdn/spotlight/snice-spotlight.min.js +1 -1
  218. package/dist/cdn/spreadsheet/snice-spreadsheet.js +1 -1
  219. package/dist/cdn/spreadsheet/snice-spreadsheet.min.js +1 -1
  220. package/dist/cdn/stepper/snice-stepper.js +1 -1
  221. package/dist/cdn/stepper/snice-stepper.min.js +1 -1
  222. package/dist/cdn/switch/README.md +1 -1
  223. package/dist/cdn/switch/snice-switch.js +33 -23
  224. package/dist/cdn/switch/snice-switch.js.map +1 -1
  225. package/dist/cdn/switch/snice-switch.min.js +3 -3
  226. package/dist/cdn/switch/snice-switch.min.js.map +1 -1
  227. package/dist/cdn/table/README.md +2 -2
  228. package/dist/cdn/table/snice-table.js +2857 -110
  229. package/dist/cdn/table/snice-table.js.map +1 -1
  230. package/dist/cdn/table/snice-table.min.js +187 -47
  231. package/dist/cdn/table/snice-table.min.js.map +1 -1
  232. package/dist/cdn/tabs/snice-tabs.js +1 -1
  233. package/dist/cdn/tabs/snice-tabs.min.js +1 -1
  234. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  235. package/dist/cdn/tag-input/snice-tag-input.min.js +1 -1
  236. package/dist/cdn/terminal/snice-terminal.js +1 -1
  237. package/dist/cdn/terminal/snice-terminal.min.js +1 -1
  238. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  239. package/dist/cdn/testimonial/snice-testimonial.min.js +1 -1
  240. package/dist/cdn/textarea/snice-textarea.js +1 -1
  241. package/dist/cdn/textarea/snice-textarea.min.js +1 -1
  242. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  243. package/dist/cdn/time-range-picker/snice-time-range-picker.min.js +1 -1
  244. package/dist/cdn/timeline/snice-timeline.js +1 -1
  245. package/dist/cdn/timeline/snice-timeline.min.js +1 -1
  246. package/dist/cdn/timer/snice-timer.js +1 -1
  247. package/dist/cdn/timer/snice-timer.min.js +1 -1
  248. package/dist/cdn/toast/README.md +1 -1
  249. package/dist/cdn/toast/snice-toast.js +3 -3
  250. package/dist/cdn/toast/snice-toast.js.map +1 -1
  251. package/dist/cdn/toast/snice-toast.min.js +2 -2
  252. package/dist/cdn/toast/snice-toast.min.js.map +1 -1
  253. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  254. package/dist/cdn/tooltip/snice-tooltip.min.js +1 -1
  255. package/dist/cdn/tree/snice-tree.js +1 -1
  256. package/dist/cdn/tree/snice-tree.min.js +1 -1
  257. package/dist/cdn/treemap/snice-treemap.js +1 -1
  258. package/dist/cdn/treemap/snice-treemap.min.js +1 -1
  259. package/dist/cdn/video-player/snice-video-player.js +1 -1
  260. package/dist/cdn/video-player/snice-video-player.min.js +1 -1
  261. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  262. package/dist/cdn/virtual-scroller/snice-virtual-scroller.min.js +1 -1
  263. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  264. package/dist/cdn/waterfall/snice-waterfall.min.js +1 -1
  265. package/dist/cdn/weather/snice-weather.js +1 -1
  266. package/dist/cdn/weather/snice-weather.min.js +1 -1
  267. package/dist/components/book/snice-book.d.ts +2 -0
  268. package/dist/components/book/snice-book.js +28 -7
  269. package/dist/components/book/snice-book.js.map +1 -1
  270. package/dist/components/book/snice-book.types.d.ts +7 -0
  271. package/dist/components/code-block/snice-code-block.js +4 -4
  272. package/dist/components/code-block/snice-code-block.js.map +1 -1
  273. package/dist/components/code-block/snice-code-block.types.d.ts +3 -3
  274. package/dist/components/date-picker/snice-date-picker.d.ts +2 -0
  275. package/dist/components/date-picker/snice-date-picker.js +49 -39
  276. package/dist/components/date-picker/snice-date-picker.js.map +1 -1
  277. package/dist/components/doc/snice-doc.d.ts +20 -0
  278. package/dist/components/doc/snice-doc.js +220 -34
  279. package/dist/components/doc/snice-doc.js.map +1 -1
  280. package/dist/components/link-preview/snice-link-preview.js +1 -1
  281. package/dist/components/link-preview/snice-link-preview.js.map +1 -1
  282. package/dist/components/list/snice-list.js +3 -3
  283. package/dist/components/list/snice-list.js.map +1 -1
  284. package/dist/components/menu/snice-menu.js +1 -1
  285. package/dist/components/menu/snice-menu.js.map +1 -1
  286. package/dist/components/modal/snice-modal.d.ts +2 -0
  287. package/dist/components/modal/snice-modal.js +33 -17
  288. package/dist/components/modal/snice-modal.js.map +1 -1
  289. package/dist/components/modal/snice-modal.types.d.ts +2 -0
  290. package/dist/components/music-player/snice-music-player.d.ts +1 -0
  291. package/dist/components/music-player/snice-music-player.js +7 -0
  292. package/dist/components/music-player/snice-music-player.js.map +1 -1
  293. package/dist/components/notification-center/snice-notification-center.d.ts +1 -1
  294. package/dist/components/notification-center/snice-notification-center.js.map +1 -1
  295. package/dist/components/notification-center/snice-notification-center.types.d.ts +1 -0
  296. package/dist/components/qr-code/qrcode.d.ts +1 -0
  297. package/dist/components/qr-code/qrcode.js +16 -8
  298. package/dist/components/qr-code/qrcode.js.map +1 -1
  299. package/dist/components/qr-code/snice-qr-code.d.ts +5 -2
  300. package/dist/components/qr-code/snice-qr-code.js +132 -11
  301. package/dist/components/qr-code/snice-qr-code.js.map +1 -1
  302. package/dist/components/qr-code/snice-qr-code.types.d.ts +3 -2
  303. package/dist/components/radio/snice-radio.d.ts +1 -0
  304. package/dist/components/radio/snice-radio.js +22 -2
  305. package/dist/components/radio/snice-radio.js.map +1 -1
  306. package/dist/components/select/snice-select.d.ts +2 -4
  307. package/dist/components/select/snice-select.js +46 -92
  308. package/dist/components/select/snice-select.js.map +1 -1
  309. package/dist/components/slider/snice-slider.js +1 -1
  310. package/dist/components/slider/snice-slider.js.map +1 -1
  311. package/dist/components/switch/snice-switch.d.ts +2 -0
  312. package/dist/components/switch/snice-switch.js +32 -22
  313. package/dist/components/switch/snice-switch.js.map +1 -1
  314. package/dist/components/table/snice-table.d.ts +2 -0
  315. package/dist/components/table/snice-table.js +17 -3
  316. package/dist/components/table/snice-table.js.map +1 -1
  317. package/dist/components/toast/snice-toast-container.js +2 -2
  318. package/dist/components/toast/snice-toast-container.js.map +1 -1
  319. package/dist/index.cjs +513 -44
  320. package/dist/index.cjs.map +1 -1
  321. package/dist/index.d.ts +1 -0
  322. package/dist/index.esm.js +512 -45
  323. package/dist/index.esm.js.map +1 -1
  324. package/dist/index.iife.js +513 -44
  325. package/dist/index.iife.js.map +1 -1
  326. package/dist/symbols.cjs +1 -1
  327. package/dist/symbols.esm.js +1 -1
  328. package/dist/tooltip-observer.d.ts +11 -0
  329. package/dist/transitions.cjs +1 -1
  330. package/dist/transitions.esm.js +1 -1
  331. package/dist/types/request-options.d.ts +1 -1
  332. package/docs/ai/DEVELOPMENT.md +1 -1
  333. package/docs/ai/api.md +15 -11
  334. package/docs/ai/architecture.md +18 -5
  335. package/docs/ai/components/app-tiles.md +1 -1
  336. package/docs/ai/components/book.md +5 -6
  337. package/docs/ai/components/camera-annotate.md +3 -3
  338. package/docs/ai/components/candlestick.md +3 -3
  339. package/docs/ai/components/chart.md +1 -1
  340. package/docs/ai/components/code-block.md +4 -4
  341. package/docs/ai/components/doc.md +26 -15
  342. package/docs/ai/components/file-gallery.md +1 -1
  343. package/docs/ai/components/link-preview.md +1 -1
  344. package/docs/ai/components/list.md +2 -2
  345. package/docs/ai/components/markdown.md +13 -6
  346. package/docs/ai/components/modal.md +2 -0
  347. package/docs/ai/components/music-player.md +3 -2
  348. package/docs/ai/components/network-graph.md +5 -5
  349. package/docs/ai/components/notification-center.md +1 -0
  350. package/docs/ai/components/pdf-viewer.md +1 -1
  351. package/docs/ai/components/radio.md +2 -2
  352. package/docs/ai/components/sankey.md +3 -3
  353. package/docs/ai/components/select.md +1 -1
  354. package/docs/ai/components/tooltip.md +54 -0
  355. package/docs/ai/decorators.md +6 -6
  356. package/docs/ai/patterns.md +17 -6
  357. package/docs/code-block.md +5 -7
  358. package/docs/components/app-tiles.md +1 -1
  359. package/docs/components/book.md +3 -4
  360. package/docs/components/button.md +2 -2
  361. package/docs/components/camera-annotate.md +6 -6
  362. package/docs/components/candlestick.md +6 -6
  363. package/docs/components/chart.md +4 -6
  364. package/docs/components/checkbox.md +3 -3
  365. package/docs/components/chip.md +4 -4
  366. package/docs/components/code-block.md +4 -3
  367. package/docs/components/doc.md +99 -58
  368. package/docs/components/file-gallery.md +25 -3
  369. package/docs/components/kpi.md +2 -3
  370. package/docs/components/link-preview.md +2 -2
  371. package/docs/components/list.md +3 -3
  372. package/docs/components/markdown.md +14 -36
  373. package/docs/components/modal.md +2 -0
  374. package/docs/components/music-player.md +3 -2
  375. package/docs/components/network-graph.md +7 -7
  376. package/docs/components/notification-center.md +1 -0
  377. package/docs/components/pdf-viewer.md +1 -1
  378. package/docs/components/sankey.md +6 -6
  379. package/docs/components/switch.md +1 -1
  380. package/docs/components/table.md +2 -2
  381. package/docs/components/tooltip.md +133 -0
  382. package/docs/controllers.md +92 -396
  383. package/docs/elements.md +131 -118
  384. package/docs/events.md +75 -81
  385. package/docs/fetcher.md +64 -76
  386. package/docs/observe.md +13 -33
  387. package/docs/placards.md +6 -16
  388. package/docs/request-response.md +173 -693
  389. package/docs/routing.md +67 -136
  390. package/package.json +1 -1
  391. package/docs/migration-v2-to-v3.md +0 -569
@@ -1,8 +1,9 @@
1
1
  # Request/Response API Documentation
2
2
 
3
- Request/Response provides bidirectional request/response communication between elements and controllers using async generators.
3
+ Request/Response provides request/response communication between elements and controllers using async generators.
4
4
 
5
5
  ## Table of Contents
6
+ - [Why Request/Response?](#why-requestresponse)
6
7
  - [Basic Concept](#basic-concept)
7
8
  - [Request/Response Decorators](#requestresponse-decorators)
8
9
  - [Element-Side Requests](#element-side-requests)
@@ -11,15 +12,27 @@ Request/Response provides bidirectional request/response communication between e
11
12
  - [Error Handling](#error-handling)
12
13
  - [Advanced Patterns](#advanced-patterns)
13
14
 
15
+ ## Why Request/Response?
16
+
17
+ Components are **generic**. A `<product-card>` renders a card — it doesn't know or care whether its data comes from a REST API, a GraphQL endpoint, a WebSocket, or a test fixture. Controllers are **specific**. They wire a particular data source, API, or business rule to a generic component.
18
+
19
+ The `@request`/`@respond` pattern keeps this separation clean:
20
+
21
+ - **The element says *what* it needs** (e.g., "I need product data for this ID") without knowing *how* to get it.
22
+ - **The controller decides *how*** — makes the API call, applies business logic, caches results, whatever is needed.
23
+ - **Swapping controllers changes behavior without touching the component.** Attach a mock controller for tests, a real API controller in production, or a WebSocket controller for live updates — the element is the same.
24
+
25
+ This makes components reusable across projects and testable in isolation. An element with `@request('fetch-product')` works with *any* controller that `@respond`s to `'fetch-product'` — no imports, no interfaces, no coupling.
26
+
14
27
  ## Basic Concept
15
28
 
16
- Request/Response enables a request/response pattern between elements and their controllers:
29
+ Request/Response enables a single request/response round-trip between elements and their controllers:
17
30
 
18
- 1. **Element** sends a request using `yield`
19
- 2. **Controller** receives the request and returns a response
20
- 3. **Element** receives the response and can process it
31
+ 1. **Element** yields a request payload — "here's what I need"
32
+ 2. **Controller** receives the payload and returns a response — "here's the data"
33
+ 3. **Element** receives the response and updates its visual state
21
34
 
22
- This pattern is implemented using async generators and custom events.
35
+ This pattern is implemented using async generators and custom events. Each `@request` method supports **one yield** per invocation — the generator yields a request payload, the controller responds, and the generator receives the response.
23
36
 
24
37
  ## Request/Response Decorators
25
38
 
@@ -27,13 +40,15 @@ This pattern is implemented using async generators and custom events.
27
40
 
28
41
  ```typescript
29
42
  function request(requestName: string, options?: RequestOptions): MethodDecorator
30
- function response(responseName: string, options?: RespondOptions): MethodDecorator
43
+ function respond(requestName: string, options?: RespondOptions): MethodDecorator
31
44
 
32
45
  interface RequestOptions extends EventInit {
33
46
  timeout?: number; // Response timeout in ms (default: 120000ms = 2 minutes)
34
47
  discoveryTimeout?: number; // Handler discovery timeout in ms (default: 50ms)
35
48
  debounce?: number; // Debounce requests by specified ms
36
49
  throttle?: number; // Throttle requests by specified ms
50
+ // Note: `composed` is always forced to `true` (crosses shadow DOM boundaries)
51
+ // `bubbles` defaults to true, `cancelable` defaults to false
37
52
  }
38
53
 
39
54
  interface RespondOptions {
@@ -41,812 +56,277 @@ interface RespondOptions {
41
56
  throttle?: number; // Throttle responses by specified ms
42
57
  }
43
58
 
44
- // Type helper to eliminate TypeScript warnings in request generators
45
- // Use this for method signatures - it handles both the generator implementation and promise return
46
- type Response<T> = AsyncGenerator<any, T, any> | Promise<T>;
59
+ // Recommended type helper for request generator return types:
60
+ type RequestResult<T> = AsyncGenerator<any, T, any> | Promise<T>;
61
+ // Define this in your project it satisfies both the generator and the caller
47
62
  ```
48
63
 
49
64
  #### Response Debounce/Throttle
50
65
 
51
- Response handlers can also be debounced or throttled to prevent excessive processing:
66
+ Response handlers can be debounced or throttled:
52
67
 
53
68
  ```typescript
54
- @controller('heavy-processing-controller')
69
+ @controller('processing-controller')
55
70
  class ProcessingController implements IController {
71
+ element: HTMLElement | null = null;
72
+ async attach() {}
73
+ async detach() {}
56
74
 
57
- @respond('@api/expensive-calculation', { debounce: 1000 })
58
- async calculateResults(params: any) {
59
- // Debounced by 1 second - rapid requests will only trigger the latest
60
- return await performExpensiveCalculation(params);
75
+ @respond('search', { debounce: 300 })
76
+ async handleSearch(query: { term: string }) {
77
+ return await fetch(`/api/search?q=${encodeURIComponent(query.term)}`).then(r => r.json());
61
78
  }
62
79
 
63
- @respond('@api/real-time-updates', { throttle: 500 })
64
- async handleUpdates(data: any) {
65
- // Throttled to max 2 requests per second
66
- return await processUpdate(data);
80
+ @respond('analytics', { throttle: 1000 })
81
+ async handleAnalytics(event: any) {
82
+ return await fetch('/api/track', { method: 'POST', body: JSON.stringify(event) });
67
83
  }
68
84
  }
69
85
  ```
70
86
 
71
87
  ## Element-Side Requests
72
88
 
73
- Elements use async generators to make requests:
89
+ Elements use async generators to make requests. The element stays visual — it yields data up to the controller and renders the response:
74
90
 
75
91
  ```typescript
76
- import { element, request, Response, render, html } from 'snice';
92
+ import { element, request, property, render, html } from 'snice';
93
+
94
+ type RequestResult<T> = AsyncGenerator<any, T, any> | Promise<T>;
77
95
 
78
- @element('user-card')
79
- class UserCard extends HTMLElement {
80
- userId = 123;
96
+ @element('product-card')
97
+ class ProductCard extends HTMLElement {
98
+ @property() productId = '';
99
+ @property() name = '';
100
+ @property() price = '';
101
+
102
+ @request('fetch-product')
103
+ async *loadProduct(): RequestResult<void> {
104
+ const product = await (yield { id: this.productId });
105
+ this.name = product.name;
106
+ this.price = product.price;
107
+ }
81
108
 
82
109
  @render()
83
110
  renderContent() {
84
111
  return html`
85
- <div class="user-info">
86
- <button @click=${this.loadUser}>Load User</button>
87
- <div class="content"></div>
112
+ <div class="card">
113
+ <h3>${this.name || 'Loading...'}</h3>
114
+ <p>${this.price}</p>
115
+ <button @click=${this.loadProduct}>Refresh</button>
88
116
  </div>
89
117
  `;
90
118
  }
91
-
92
- @request('fetch-user')
93
- async *fetchUserData(): Response<{ success: boolean; user: any }> {
94
- // Yield sends the request, await waits for response
95
- const user = await (yield { userId: this.userId });
96
-
97
- // Process the response
98
- this.displayUser(user);
99
-
100
- // Return final value (optional)
101
- return { success: true, user };
102
- }
103
-
104
- async loadUser() {
105
- try {
106
- const result = await this.fetchUserData();
107
- console.log('Load complete:', result);
108
- } catch (error) {
109
- console.error('Failed to load user:', error);
110
- }
111
- }
112
-
113
- displayUser(user: any) {
114
- const content = this.shadowRoot?.querySelector('.content');
115
- if (content) {
116
- content.innerHTML = `
117
- <h3>${user.name}</h3>
118
- <p>${user.email}</p>
119
- `;
120
- }
121
- }
122
119
  }
123
120
  ```
124
121
 
125
- ### Multiple Yields
126
-
127
- Elements can yield multiple times in a single channel:
128
-
129
- ```typescript
130
- @element('multi-request')
131
- class MultiRequest extends HTMLElement {
132
- @request('multi-data')
133
- async *fetchMultipleData() {
134
- // First request
135
- const userData = await (yield { type: 'user', id: 1 });
136
- console.log('Got user:', userData);
137
-
138
- // Second request based on first response
139
- const postsData = await (yield { type: 'posts', userId: userData.id });
140
- console.log('Got posts:', postsData);
141
-
142
- // Third request
143
- const commentsData = await (yield { type: 'comments', postIds: postsData.map((p: any) => p.id) });
144
- console.log('Got comments:', commentsData);
145
-
146
- // Return combined result
147
- return {
148
- user: userData,
149
- posts: postsData,
150
- comments: commentsData
151
- };
152
- }
153
-
154
- @render()
155
- renderContent() {
156
- return html`<button @click=${this.fetchData}>Fetch All Data</button>`;
157
- }
158
-
159
- async fetchData() {
160
- const result = await this.fetchMultipleData();
161
- console.log('All data loaded:', result);
162
- }
163
- }
164
- ```
122
+ **How it works:**
123
+ 1. `yield { id: this.productId }` dispatches a bubbling custom event with the payload
124
+ 2. A `@respond('fetch-product')` handler (typically in a controller) catches it and returns data
125
+ 3. `await (yield ...)` resolves with the response
126
+ 4. The element updates its properties, triggering a re-render
165
127
 
166
128
  ## Controller-Side Responses
167
129
 
168
- Controllers handle requests and provide responses:
130
+ Controllers handle requests — this is where business logic, API calls, and data management belong:
169
131
 
170
132
  ```typescript
171
133
  import { controller, respond, IController } from 'snice';
172
134
 
173
- @controller('user-controller')
174
- class UserController implements IController {
135
+ @controller('product-controller')
136
+ class ProductController implements IController {
175
137
  element: HTMLElement | null = null;
138
+ async attach() {}
139
+ async detach() {}
176
140
 
177
- async attach(element: HTMLElement) {
178
- console.log('User controller attached');
179
- }
180
-
181
- async detach(element: HTMLElement) {
182
- console.log('User controller detached');
183
- }
184
-
185
- @respond('fetch-user')
186
- async handleFetchUser(request: { userId: number }) {
187
- // Simulate API call
188
- const response = await fetch(`/api/users/${request.userId}`);
189
- const user = await response.json();
190
-
191
- // Return response to element
192
- return user;
193
- }
194
-
195
- @respond('multi-data')
196
- async handleMultiData(request: any) {
197
- switch (request.type) {
198
- case 'user':
199
- return await this.fetchUser(request.id);
200
- case 'posts':
201
- return await this.fetchPosts(request.userId);
202
- case 'comments':
203
- return await this.fetchComments(request.postIds);
204
- default:
205
- throw new Error(`Unknown request type: ${request.type}`);
206
- }
207
- }
208
-
209
- private async fetchUser(id: number) {
210
- // Simulate API call
211
- return { id, name: 'John Doe', email: 'john@example.com' };
212
- }
213
-
214
- private async fetchPosts(userId: number) {
215
- // Simulate API call
216
- return [
217
- { id: 1, userId, title: 'Post 1' },
218
- { id: 2, userId, title: 'Post 2' }
219
- ];
220
- }
221
-
222
- private async fetchComments(postIds: number[]) {
223
- // Simulate API call
224
- return postIds.flatMap(postId => [
225
- { id: 1, postId, text: 'Comment 1' },
226
- { id: 2, postId, text: 'Comment 2' }
227
- ]);
141
+ @respond('fetch-product')
142
+ async handleFetchProduct(request: { id: string }) {
143
+ const response = await fetch(`/api/products/${request.id}`);
144
+ return await response.json();
228
145
  }
229
146
  }
230
147
  ```
231
148
 
232
- ## Request/Response Options
233
-
234
- ### RequestOptions
149
+ **Architecture:** Elements never call `fetch()` or manage data directly. They yield requests upward and render whatever comes back. Controllers own the data layer.
235
150
 
236
- ```typescript
237
- interface RequestOptions extends EventInit {
238
- timeout?: number; // Response timeout in ms (default: 120000ms = 2 minutes)
239
- discoveryTimeout?: number; // Handler discovery timeout in ms (default: 50ms)
240
- debounce?: number; // Debounce requests by specified ms
241
- throttle?: number; // Throttle requests by specified ms
242
- }
243
- ```
151
+ ## Request/Response Options
244
152
 
245
- #### Timeout Behavior (IMPORTANT)
153
+ ### Timeout Behavior
246
154
 
247
- The timeout system has **two separate timeouts** for different phases:
155
+ The timeout system has **two separate timeouts**:
248
156
 
249
- - **Discovery timeout** (`discoveryTimeout`): 50ms (default) - Fast timeout to find a handler
250
- - **Response timeout** (`timeout`): 2 minutes (default) - Total time allowed for the request
157
+ - **Discovery timeout** (`discoveryTimeout`): 50ms default finds a handler quickly
158
+ - **Response timeout** (`timeout`): 2 minutes default total time for the response
251
159
 
252
160
  ```typescript
253
- @request('@api/heavy-processing', {
254
- discoveryTimeout: 50, // 50ms to find handler (fast)
255
- timeout: 30000 // 30s total timeout for processing
161
+ @request('heavy-computation', {
162
+ discoveryTimeout: 50, // 50ms to find handler
163
+ timeout: 30000 // 30s for actual processing
256
164
  })
257
- async *processData() {
258
- // Will timeout in 50ms if no handler exists
259
- // Will timeout in 30s total if processing takes too long
260
- const result = await (yield data);
261
- return result;
262
- }
263
- ```
264
-
265
- **Why two timeouts?**
266
- - **Discovery**: Should be very fast (dozens of milliseconds) - just finding if anyone can handle the request
267
- - **Response**: Should be human-scale (seconds/minutes) - actual work takes time
268
-
269
- #### Debounce Support
270
-
271
- Prevents rapid successive requests by delaying execution:
272
-
273
- ```typescript
274
- @request('@api/search', { debounce: 300 })
275
- async *search() {
276
- // Debounced by 300ms - rapid calls will cancel previous ones
277
- const results = await (yield query);
278
- return results;
165
+ async *compute(): RequestResult<any> {
166
+ return await (yield data);
279
167
  }
280
168
  ```
281
169
 
282
- #### Throttle Support
283
-
284
- Limits request frequency to maximum rate:
170
+ ### Debounce/Throttle
285
171
 
286
172
  ```typescript
287
- @request('@api/analytics', { throttle: 1000 })
288
- async *trackEvent() {
289
- // Throttled to max 1 request per second
290
- const response = await (yield eventData);
291
- return response;
173
+ // Debounce: wait for typing to stop before searching
174
+ @request('search', { debounce: 300 })
175
+ async *search(): RequestResult<any[]> {
176
+ return await (yield { query: this.searchTerm });
292
177
  }
293
- ```
294
178
 
295
- ### Timeout Configuration
296
-
297
- ```typescript
298
- @element('timeout-example')
299
- class TimeoutExample extends HTMLElement {
300
- // Quick discovery, short total timeout for fast operations
301
- @request('quick-data', {
302
- discoveryTimeout: 25, // Very fast discovery
303
- timeout: 1000 // 1 second total
304
- })
305
- async *fetchQuickData() {
306
- const data = await (yield { quick: true });
307
- return data;
308
- }
309
-
310
- // Standard discovery, longer timeout for slow operations
311
- @request('slow-data', {
312
- discoveryTimeout: 50, // Default discovery
313
- timeout: 30000 // 30 seconds total
314
- })
315
- async *fetchSlowData() {
316
- const data = await (yield { slow: true });
317
- return data;
318
- }
319
-
320
- // Use defaults (50ms discovery, 2 minutes total)
321
- @request('default-data')
322
- async *fetchDefaultData() {
323
- const data = await (yield { default: true });
324
- return data;
325
- }
326
-
327
- // Custom event options with timeouts
328
- @request('private-data', {
329
- discoveryTimeout: 100, // Slower discovery
330
- timeout: 60000, // 1 minute total
331
- bubbles: false, // Don't bubble
332
- cancelable: true // Can be canceled
333
- })
334
- async *fetchPrivateData() {
335
- const data = await (yield { private: true });
336
- return data;
337
- }
338
-
339
- @render()
340
- renderContent() {
341
- return html`<div>Timeout examples</div>`;
342
- }
179
+ // Throttle: limit analytics to 1 per second
180
+ @request('track', { throttle: 1000 })
181
+ async *trackEvent(): RequestResult<void> {
182
+ await (yield { event: 'scroll', position: window.scrollY });
343
183
  }
344
184
  ```
345
185
 
346
186
  ## Error Handling
347
187
 
348
- ### Handling Timeouts
188
+ ### Element-Side
349
189
 
350
190
  ```typescript
351
- @element('timeout-handler')
352
- class TimeoutHandler extends HTMLElement {
353
- @request('data', {
354
- discoveryTimeout: 50,
355
- timeout: 5000
356
- })
357
- async *fetchData() {
358
- try {
359
- const data = await (yield { request: 'data' });
360
- return { success: true, data };
361
- } catch (error: any) {
362
- // Handle different types of timeout errors
363
- if (error.message.includes('timed out after') && error.message.includes('no handler found')) {
364
- console.error('No handler found for request');
365
- return { success: false, error: 'no_handler' };
366
- } else if (error.message.includes('timed out after')) {
367
- console.error('Request processing timed out');
368
- return { success: false, error: 'timeout' };
369
- }
370
- throw error;
371
- }
372
- }
191
+ @element('safe-loader')
192
+ class SafeLoader extends HTMLElement {
193
+ @property() error = '';
194
+ @property() data: any = null;
373
195
 
374
- async loadData() {
196
+ @request('load-data', { timeout: 5000 })
197
+ async *loadData(): RequestResult<void> {
375
198
  try {
376
- const result = await this.fetchData();
377
- if (!result.success) {
378
- this.showError('Failed to load data');
199
+ this.data = await (yield { id: this.dataId });
200
+ this.error = '';
201
+ } catch (err: any) {
202
+ if (err.message.includes('no handler found')) {
203
+ this.error = 'Service unavailable';
204
+ } else if (err.message.includes('timed out')) {
205
+ this.error = 'Request timed out';
206
+ } else {
207
+ this.error = err.message;
379
208
  }
380
- } catch (error) {
381
- this.showError('Unexpected error');
382
209
  }
383
210
  }
384
211
 
385
- showError(message: string) {
386
- console.error(message);
387
- }
388
-
389
212
  @render()
390
213
  renderContent() {
391
- return html`<button @click=${this.loadData}>Load Data</button>`;
392
- }
393
- }
394
- ```
395
-
396
- ### Controller Error Handling
397
-
398
- ```typescript
399
- @controller('error-controller')
400
- class ErrorController implements IController {
401
- element: HTMLElement | null = null;
402
-
403
- async attach(element: HTMLElement) {}
404
- async detach(element: HTMLElement) {}
405
-
406
- @respond('risky-operation')
407
- async handleRiskyOperation(request: any) {
408
- try {
409
- // Validate request
410
- if (!request.id) {
411
- throw new Error('ID is required');
412
- }
413
-
414
- // Perform operation
415
- const result = await this.performOperation(request.id);
416
-
417
- return { success: true, result };
418
- } catch (error: any) {
419
- // Return error info instead of throwing
420
- return {
421
- success: false,
422
- error: error.message,
423
- code: error.code || 'UNKNOWN_ERROR'
424
- };
425
- }
426
- }
427
-
428
- private async performOperation(id: string) {
429
- // Simulate operation that might fail
430
- if (Math.random() > 0.5) {
431
- throw new Error('Random failure');
432
- }
433
- return { id, processed: true };
214
+ return html`
215
+ <if ${this.error}>
216
+ <div class="error">${this.error}</div>
217
+ </if>
218
+ <if ${this.data}>
219
+ <div class="content">${this.data.title}</div>
220
+ </if>
221
+ `;
434
222
  }
435
223
  }
436
224
  ```
437
225
 
438
- ## Advanced Patterns
439
-
440
- ### Authentication Request/Response
226
+ ### Controller-Side
441
227
 
442
228
  ```typescript
443
- // Element side
444
- @element('protected-content')
445
- class ProtectedContent extends HTMLElement {
446
- @request('authenticate')
447
- async *authenticate() {
448
- // Send credentials
449
- const authResult = await (yield {
450
- username: 'user@example.com',
451
- password: 'secret'
452
- });
453
-
454
- if (!authResult.success) {
455
- throw new Error(authResult.error);
456
- }
457
-
458
- // Store token
459
- localStorage.setItem('token', authResult.token);
460
-
461
- return authResult;
462
- }
463
-
464
- @request('fetch-protected')
465
- async *fetchProtectedData() {
466
- const token = localStorage.getItem('token');
467
-
468
- if (!token) {
469
- // Need to authenticate first
470
- await this.authenticate();
471
- }
472
-
473
- // Fetch with token
474
- const data = await (yield {
475
- resource: 'protected',
476
- token: localStorage.getItem('token')
477
- });
478
-
479
- return data;
480
- }
481
-
482
- @render()
483
- renderContent() {
484
- return html`<button @click=${this.loadProtected}>Load Protected Data</button>`;
485
- }
486
-
487
- async loadProtected() {
488
- try {
489
- const data = await this.fetchProtectedData();
490
- console.log('Protected data:', data);
491
- } catch (error) {
492
- console.error('Failed to load:', error);
493
- }
494
- }
495
- }
496
-
497
- // Controller side
498
- @controller('auth-controller')
499
- class AuthController implements IController {
229
+ @controller('resilient-controller')
230
+ class ResilientController implements IController {
500
231
  element: HTMLElement | null = null;
501
- private tokens = new Map<string, any>();
502
-
503
- async attach(element: HTMLElement) {}
504
- async detach(element: HTMLElement) {}
505
-
506
- @respond('authenticate')
507
- async handleAuth(credentials: any) {
508
- // Validate credentials
509
- if (credentials.username === 'user@example.com' &&
510
- credentials.password === 'secret') {
511
-
512
- const token = this.generateToken();
513
- const user = { id: 1, name: 'User' };
514
-
515
- this.tokens.set(token, user);
232
+ async attach() {}
233
+ async detach() {}
516
234
 
517
- return {
518
- success: true,
519
- token,
520
- user
521
- };
235
+ @respond('load-data')
236
+ async handleLoadData(request: { id: string }) {
237
+ if (!request.id) {
238
+ throw new Error('ID is required');
522
239
  }
523
240
 
524
- return {
525
- success: false,
526
- error: 'Invalid credentials'
527
- };
528
- }
529
-
530
- @respond('fetch-protected')
531
- async handleFetchProtected(request: any) {
532
- // Validate token
533
- const user = this.tokens.get(request.token);
534
-
535
- if (!user) {
536
- throw new Error('Invalid or expired token');
241
+ const response = await fetch(`/api/data/${request.id}`);
242
+ if (!response.ok) {
243
+ throw new Error(`API error: ${response.status}`);
537
244
  }
538
245
 
539
- // Return protected data
540
- return {
541
- resource: request.resource,
542
- data: { secret: 'Protected information' },
543
- user
544
- };
545
- }
546
-
547
- private generateToken() {
548
- return Math.random().toString(36).substring(2);
246
+ return await response.json();
549
247
  }
550
248
  }
551
249
  ```
552
250
 
553
- ### Streaming Data Request/Response
554
-
555
- ```typescript
556
- // Element side
557
- @element('data-streamer')
558
- class DataStreamer extends HTMLElement {
559
- private items: any[] = [];
560
-
561
- @request('stream-data')
562
- async *streamData() {
563
- let hasMore = true;
564
- let page = 1;
565
-
566
- while (hasMore) {
567
- // Request next page
568
- const response = await (yield {
569
- page,
570
- pageSize: 10
571
- });
572
-
573
- // Add items to list
574
- this.items.push(...response.items);
575
- this.renderItems();
576
-
577
- // Check if more pages available
578
- hasMore = response.hasMore;
579
- page++;
580
- }
581
-
582
- return {
583
- totalItems: this.items.length,
584
- complete: true
585
- };
586
- }
587
-
588
- renderItems() {
589
- const container = this.shadowRoot?.querySelector('.items');
590
- if (container) {
591
- container.innerHTML = this.items
592
- .map(item => `<div>${item.name}</div>`)
593
- .join('');
594
- }
595
- }
596
-
597
- @render()
598
- renderContent() {
599
- return html`
600
- <button @click=${this.loadAllData}>Load All Data</button>
601
- <div class="items"></div>
602
- `;
603
- }
604
-
605
- async loadAllData() {
606
- const result = await this.streamData();
607
- console.log(`Loaded ${result.totalItems} items`);
608
- }
609
- }
610
-
611
- // Controller side
612
- @controller('stream-controller')
613
- class StreamController implements IController {
614
- element: HTMLElement | null = null;
615
- private allData: any[] = Array.from({ length: 35 }, (_, i) => ({
616
- id: i + 1,
617
- name: `Item ${i + 1}`
618
- }));
619
-
620
- async attach(element: HTMLElement) {}
621
- async detach(element: HTMLElement) {}
622
-
623
- @respond('stream-data')
624
- async handleStreamData(request: { page: number; pageSize: number }) {
625
- // Simulate delay
626
- await new Promise(resolve => setTimeout(resolve, 500));
627
-
628
- // Calculate pagination
629
- const start = (request.page - 1) * request.pageSize;
630
- const end = start + request.pageSize;
631
-
632
- const items = this.allData.slice(start, end);
633
- const hasMore = end < this.allData.length;
634
-
635
- return {
636
- items,
637
- hasMore,
638
- page: request.page,
639
- totalPages: Math.ceil(this.allData.length / request.pageSize)
640
- };
641
- }
642
- }
643
- ```
251
+ ## Advanced Patterns
644
252
 
645
- ### Cached Request/Response
253
+ ### Cached Responses
646
254
 
647
255
  ```typescript
648
256
  @controller('cached-controller')
649
257
  class CachedController implements IController {
650
258
  element: HTMLElement | null = null;
651
259
  private cache = new Map<string, { data: any; timestamp: number }>();
652
- private cacheTimeout = 60000; // 1 minute
260
+ private ttl = 60000; // 1 minute
653
261
 
654
- async attach(element: HTMLElement) {}
655
- async detach(element: HTMLElement) {}
262
+ async attach() {}
263
+ async detach() {}
656
264
 
657
265
  @respond('fetch-cached')
658
- async handleFetchCached(request: { key: string; forceRefresh?: boolean }) {
659
- const cacheKey = request.key;
660
- const cached = this.cache.get(cacheKey);
661
-
662
- // Check cache validity
663
- if (!request.forceRefresh && cached) {
664
- const age = Date.now() - cached.timestamp;
665
- if (age < this.cacheTimeout) {
666
- console.log(`Returning cached data for ${cacheKey}`);
667
- return {
668
- data: cached.data,
669
- fromCache: true,
670
- age
671
- };
672
- }
266
+ async handleFetch(request: { key: string; forceRefresh?: boolean }) {
267
+ const cached = this.cache.get(request.key);
268
+ if (!request.forceRefresh && cached && Date.now() - cached.timestamp < this.ttl) {
269
+ return { data: cached.data, fromCache: true };
673
270
  }
674
271
 
675
- // Fetch fresh data
676
- console.log(`Fetching fresh data for ${cacheKey}`);
677
- const freshData = await this.fetchFreshData(cacheKey);
678
-
679
- // Update cache
680
- this.cache.set(cacheKey, {
681
- data: freshData,
682
- timestamp: Date.now()
683
- });
684
-
685
- return {
686
- data: freshData,
687
- fromCache: false,
688
- age: 0
689
- };
690
- }
691
-
692
- private async fetchFreshData(key: string) {
693
- // Simulate API call
694
- await new Promise(resolve => setTimeout(resolve, 1000));
695
- return { key, value: Math.random(), timestamp: Date.now() };
272
+ const data = await fetch(`/api/${request.key}`).then(r => r.json());
273
+ this.cache.set(request.key, { data, timestamp: Date.now() });
274
+ return { data, fromCache: false };
696
275
  }
697
276
  }
698
277
  ```
699
278
 
700
- ### Bidirectional Updates
701
-
702
- ```typescript
703
- // Element that can both request and be updated
704
- import { element, property, query, request, watch, render, html } from 'snice';
279
+ ### Subscription Pattern
705
280
 
706
- @element('live-data')
707
- class LiveData extends HTMLElement {
708
- private updateInterval?: number;
281
+ Use `@request` for one-time fetches and `@dispatch` + `@on` for ongoing updates:
709
282
 
710
- @property()
711
- status = 'Disconnected';
283
+ ```typescript
284
+ // Element: visual, subscribes to updates
285
+ @element('live-ticker')
286
+ class LiveTicker extends HTMLElement {
287
+ @property() price = '0.00';
288
+ @property() symbol = 'BTC';
712
289
 
713
- @query('.status')
714
- statusDiv?: HTMLElement;
290
+ @request('subscribe-ticker')
291
+ async *subscribe(): RequestResult<void> {
292
+ await (yield { symbol: this.symbol });
293
+ }
715
294
 
716
- @query('.data')
717
- dataDiv?: HTMLElement;
295
+ @on('ticker-update')
296
+ onUpdate(e: CustomEvent) {
297
+ this.price = e.detail.price;
298
+ }
718
299
 
719
300
  @render()
720
301
  renderContent() {
721
302
  return html`
722
- <div class="status">${this.status}</div>
723
- <div class="data"></div>
724
- <button @click=${this.connect}>Connect</button>
725
- <button @click=${this.disconnect}>Disconnect</button>
303
+ <span class="symbol">${this.symbol}</span>
304
+ <span class="price">${this.price}</span>
726
305
  `;
727
306
  }
728
-
729
- @request('subscribe')
730
- async *subscribe() {
731
- // Send subscription request
732
- const subscription = await (yield {
733
- subscribe: true,
734
- events: ['update', 'status']
735
- });
736
-
737
- if (subscription.success) {
738
- this.status = 'Connected'; // @watch will handle UI update
739
-
740
- // Start polling for updates
741
- this.startPolling();
742
- }
743
-
744
- return subscription;
745
- }
746
-
747
- @request('poll-updates')
748
- async *pollForUpdates() {
749
- const updates = await (yield { poll: true });
750
-
751
- if (updates && updates.length > 0) {
752
- this.processUpdates(updates);
753
- }
754
-
755
- return { processed: updates.length };
756
- }
757
-
758
- async connect() {
759
- await this.subscribe();
760
- }
761
-
762
- disconnect() {
763
- this.stopPolling();
764
- this.status = 'Disconnected';
765
- }
766
-
767
- startPolling() {
768
- this.updateInterval = setInterval(async () => {
769
- await this.pollForUpdates();
770
- }, 2000);
771
- }
772
-
773
- stopPolling() {
774
- if (this.updateInterval) {
775
- clearInterval(this.updateInterval);
776
- this.updateInterval = undefined;
777
- }
778
- }
779
-
780
- processUpdates(updates: any[]) {
781
- if (this.dataDiv) {
782
- updates.forEach(update => {
783
- const entry = document.createElement('div');
784
- entry.textContent = `${update.type}: ${update.value}`;
785
- this.dataDiv!.appendChild(entry);
786
- });
787
- }
788
- }
789
-
790
- @watch('status')
791
- updateStatus() {
792
- if (this.statusDiv) {
793
- this.statusDiv.textContent = this.status;
794
- }
795
- }
796
-
797
- disconnectedCallback() {
798
- super.disconnectedCallback?.();
799
- this.stopPolling();
800
- }
801
307
  }
802
308
 
803
- // Controller that manages subscriptions
804
- @controller('subscription-controller')
805
- class SubscriptionController implements IController {
309
+ // Controller: manages WebSocket, dispatches updates
310
+ @controller('ticker-controller')
311
+ class TickerController implements IController {
806
312
  element: HTMLElement | null = null;
807
- private subscribers = new Set<string>();
808
- private updates: any[] = [];
809
-
810
- async attach(element: HTMLElement) {
811
- // Generate updates periodically
812
- setInterval(() => {
813
- this.updates.push({
814
- type: 'update',
815
- value: Math.random(),
816
- timestamp: Date.now()
817
- });
818
- }, 3000);
819
- }
313
+ private ws?: WebSocket;
820
314
 
821
- async detach(element: HTMLElement) {
822
- this.subscribers.clear();
823
- }
824
-
825
- @respond('subscribe')
826
- handleSubscribe(request: any) {
827
- if (request.subscribe) {
828
- const id = Math.random().toString(36);
829
- this.subscribers.add(id);
830
-
831
- return {
832
- success: true,
833
- subscriptionId: id,
834
- events: request.events
835
- };
836
- }
315
+ async attach() {}
837
316
 
838
- return { success: false };
317
+ async detach() {
318
+ this.ws?.close();
839
319
  }
840
320
 
841
- @respond('poll-updates')
842
- handlePollUpdates(request: any) {
843
- if (!request.poll) return [];
844
-
845
- // Return and clear updates
846
- const updates = [...this.updates];
847
- this.updates = [];
848
-
849
- return updates;
321
+ @respond('subscribe-ticker')
322
+ async handleSubscribe(request: { symbol: string }) {
323
+ this.ws = new WebSocket(`wss://api.example.com/ticker/${request.symbol}`);
324
+ this.ws.onmessage = (msg) => {
325
+ this.element?.dispatchEvent(new CustomEvent('ticker-update', {
326
+ detail: JSON.parse(msg.data)
327
+ }));
328
+ };
329
+ return { subscribed: true };
850
330
  }
851
331
  }
852
332
  ```