snice 7.5.0 → 7.7.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 (265) hide show
  1. package/adapters/react/SniceProvider.js +1 -1
  2. package/adapters/react/SniceRouter.js +1 -1
  3. package/adapters/react/avatar.d.ts +1 -0
  4. package/adapters/react/avatar.d.ts.map +1 -1
  5. package/adapters/react/avatar.js +1 -1
  6. package/adapters/react/avatar.js.map +1 -1
  7. package/adapters/react/avatar.tsx +2 -1
  8. package/adapters/react/calendar.d.ts +4 -0
  9. package/adapters/react/calendar.d.ts.map +1 -1
  10. package/adapters/react/calendar.js +1 -1
  11. package/adapters/react/calendar.js.map +1 -1
  12. package/adapters/react/calendar.tsx +5 -1
  13. package/adapters/react/matchRoute.js +68 -2
  14. package/adapters/react/matchRoute.js.map +1 -1
  15. package/adapters/react/route-match.d.ts +7 -0
  16. package/adapters/react/useRequestHandler.js +1 -1
  17. package/bin/analyzer-contracts.js +60 -2
  18. package/bin/project-analyzer.js +1262 -118
  19. package/bin/snice.js +95 -5
  20. package/custom-elements.json +112 -0
  21. package/dist/cdn/accordion/snice-accordion.js +1 -1
  22. package/dist/cdn/action-bar/snice-action-bar.js +1 -1
  23. package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
  24. package/dist/cdn/alert/snice-alert.js +1 -1
  25. package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
  26. package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
  27. package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
  28. package/dist/cdn/availability/snice-availability.js +1 -1
  29. package/dist/cdn/avatar/README.md +1 -1
  30. package/dist/cdn/avatar/snice-avatar.js +9 -3
  31. package/dist/cdn/avatar/snice-avatar.js.map +1 -1
  32. package/dist/cdn/avatar/snice-avatar.min.js +2 -2
  33. package/dist/cdn/avatar/snice-avatar.min.js.map +1 -1
  34. package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
  35. package/dist/cdn/badge/snice-badge.js +1 -1
  36. package/dist/cdn/banner/snice-banner.js +1 -1
  37. package/dist/cdn/binpack/snice-binpack.js +1 -1
  38. package/dist/cdn/book/snice-book.js +1 -1
  39. package/dist/cdn/booking/snice-booking.js +1 -1
  40. package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
  41. package/dist/cdn/button/snice-button.js +2 -2
  42. package/dist/cdn/button/snice-button.js.map +1 -1
  43. package/dist/cdn/button/snice-button.min.js +1 -1
  44. package/dist/cdn/button/snice-button.min.js.map +1 -1
  45. package/dist/cdn/calendar/README.md +2 -2
  46. package/dist/cdn/calendar/snice-calendar.js +562 -40
  47. package/dist/cdn/calendar/snice-calendar.js.map +1 -1
  48. package/dist/cdn/calendar/snice-calendar.min.js +28 -1
  49. package/dist/cdn/calendar/snice-calendar.min.js.map +1 -1
  50. package/dist/cdn/camera/snice-camera.js +1 -1
  51. package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
  52. package/dist/cdn/candlestick/snice-candlestick.js +1 -1
  53. package/dist/cdn/card/snice-card.js +1 -1
  54. package/dist/cdn/carousel/snice-carousel.js +1 -1
  55. package/dist/cdn/cart/snice-cart.js +1 -1
  56. package/dist/cdn/chart/snice-chart.js +1 -1
  57. package/dist/cdn/chat/snice-chat.js +1 -1
  58. package/dist/cdn/checkbox/snice-checkbox.js +1 -1
  59. package/dist/cdn/chip/snice-chip.js +1 -1
  60. package/dist/cdn/code-block/snice-code-block.js +1 -1
  61. package/dist/cdn/color-display/snice-color-display.js +1 -1
  62. package/dist/cdn/color-picker/snice-color-picker.js +1 -1
  63. package/dist/cdn/command-palette/snice-command-palette.js +1 -1
  64. package/dist/cdn/comments/snice-comments.js +1 -1
  65. package/dist/cdn/countdown/snice-countdown.js +1 -1
  66. package/dist/cdn/cropper/snice-cropper.js +1 -1
  67. package/dist/cdn/data-card/snice-data-card.js +1 -1
  68. package/dist/cdn/date-picker/snice-date-picker.js +1 -1
  69. package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
  70. package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
  71. package/dist/cdn/diff/snice-diff.js +1 -1
  72. package/dist/cdn/divider/snice-divider.js +1 -1
  73. package/dist/cdn/doc/snice-doc.js +1 -1
  74. package/dist/cdn/draw/snice-draw.js +1 -1
  75. package/dist/cdn/drawer/snice-drawer.js +1 -1
  76. package/dist/cdn/empty-state/snice-empty-state.js +1 -1
  77. package/dist/cdn/estimate/snice-estimate.js +1 -1
  78. package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
  79. package/dist/cdn/file-upload/snice-file-upload.js +1 -1
  80. package/dist/cdn/flip-card/snice-flip-card.js +1 -1
  81. package/dist/cdn/flow/snice-flow.js +1 -1
  82. package/dist/cdn/form-layout/snice-form-layout.js +1 -1
  83. package/dist/cdn/funnel/snice-funnel.js +1 -1
  84. package/dist/cdn/gantt/snice-gantt.js +1 -1
  85. package/dist/cdn/gauge/snice-gauge.js +1 -1
  86. package/dist/cdn/grid/snice-grid.js +1 -1
  87. package/dist/cdn/heatmap/snice-heatmap.js +1 -1
  88. package/dist/cdn/image/snice-image.js +1 -1
  89. package/dist/cdn/input/snice-input.js +1 -1
  90. package/dist/cdn/invoice/snice-invoice.js +1 -1
  91. package/dist/cdn/kanban/snice-kanban.js +1 -1
  92. package/dist/cdn/key-value/snice-key-value.js +1 -1
  93. package/dist/cdn/kpi/snice-kpi.js +1 -1
  94. package/dist/cdn/layout/snice-layout.js +1 -1
  95. package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
  96. package/dist/cdn/link/snice-link.js +1 -1
  97. package/dist/cdn/link-preview/snice-link-preview.js +1 -1
  98. package/dist/cdn/list/snice-list.js +1 -1
  99. package/dist/cdn/location/snice-location.js +1 -1
  100. package/dist/cdn/login/README.md +1 -1
  101. package/dist/cdn/login/snice-login.js +2 -2
  102. package/dist/cdn/login/snice-login.js.map +1 -1
  103. package/dist/cdn/login/snice-login.min.js +1 -1
  104. package/dist/cdn/login/snice-login.min.js.map +1 -1
  105. package/dist/cdn/map/snice-map.js +1 -1
  106. package/dist/cdn/markdown/snice-markdown.js +1 -1
  107. package/dist/cdn/masonry/snice-masonry.js +1 -1
  108. package/dist/cdn/menu/snice-menu.js +1 -1
  109. package/dist/cdn/message-strip/snice-message-strip.js +1 -1
  110. package/dist/cdn/modal/snice-modal.js +1 -1
  111. package/dist/cdn/music-player/snice-music-player.js +1 -1
  112. package/dist/cdn/nav/snice-nav.js +1 -1
  113. package/dist/cdn/network-graph/snice-network-graph.js +1 -1
  114. package/dist/cdn/notification-center/snice-notification-center.js +1 -1
  115. package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
  116. package/dist/cdn/org-chart/snice-org-chart.js +1 -1
  117. package/dist/cdn/pagination/snice-pagination.js +1 -1
  118. package/dist/cdn/paint/snice-paint.js +1 -1
  119. package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
  120. package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
  121. package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
  122. package/dist/cdn/popover/snice-popover.js +1 -1
  123. package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
  124. package/dist/cdn/product-card/snice-product-card.js +1 -1
  125. package/dist/cdn/progress/snice-progress.js +1 -1
  126. package/dist/cdn/progress-ring/snice-progress-ring.js +1 -1
  127. package/dist/cdn/qr-code/snice-qr-code.js +1 -1
  128. package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
  129. package/dist/cdn/radio/snice-radio.js +1 -1
  130. package/dist/cdn/range-slider/snice-range-slider.js +1 -1
  131. package/dist/cdn/rating/snice-rating.js +1 -1
  132. package/dist/cdn/receipt/snice-receipt.js +1 -1
  133. package/dist/cdn/recipe/snice-recipe.js +1 -1
  134. package/dist/cdn/runtime/README.md +2 -2
  135. package/dist/cdn/runtime/snice-runtime.esm.js +706 -267
  136. package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
  137. package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
  138. package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
  139. package/dist/cdn/runtime/snice-runtime.js +706 -267
  140. package/dist/cdn/runtime/snice-runtime.js.map +1 -1
  141. package/dist/cdn/runtime/snice-runtime.min.js +1 -1
  142. package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
  143. package/dist/cdn/sankey/snice-sankey.js +1 -1
  144. package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
  145. package/dist/cdn/select/snice-select.js +3 -1
  146. package/dist/cdn/select/snice-select.js.map +1 -1
  147. package/dist/cdn/select/snice-select.min.js +2 -2
  148. package/dist/cdn/select/snice-select.min.js.map +1 -1
  149. package/dist/cdn/skeleton/snice-skeleton.js +1 -1
  150. package/dist/cdn/slider/snice-slider.js +1 -1
  151. package/dist/cdn/sortable/snice-sortable.js +1 -1
  152. package/dist/cdn/sparkline/snice-sparkline.js +1 -1
  153. package/dist/cdn/spinner/snice-spinner.js +1 -1
  154. package/dist/cdn/split-button/snice-split-button.js +1 -1
  155. package/dist/cdn/split-pane/snice-split-pane.js +1 -1
  156. package/dist/cdn/spotlight/snice-spotlight.js +1 -1
  157. package/dist/cdn/stat-group/snice-stat-group.js +1 -1
  158. package/dist/cdn/step-input/snice-step-input.js +1 -1
  159. package/dist/cdn/stepper/snice-stepper.js +1 -1
  160. package/dist/cdn/switch/README.md +1 -1
  161. package/dist/cdn/switch/snice-switch.js +8 -4
  162. package/dist/cdn/switch/snice-switch.js.map +1 -1
  163. package/dist/cdn/switch/snice-switch.min.js +8 -4
  164. package/dist/cdn/switch/snice-switch.min.js.map +1 -1
  165. package/dist/cdn/table/README.md +2 -2
  166. package/dist/cdn/table/snice-table.esm.js +77 -5
  167. package/dist/cdn/table/snice-table.esm.js.map +1 -1
  168. package/dist/cdn/table/snice-table.esm.min.js +68 -47
  169. package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
  170. package/dist/cdn/table/snice-table.js +76 -4
  171. package/dist/cdn/table/snice-table.js.map +1 -1
  172. package/dist/cdn/table/snice-table.min.js +53 -32
  173. package/dist/cdn/table/snice-table.min.js.map +1 -1
  174. package/dist/cdn/tabs/snice-tabs.js +1 -1
  175. package/dist/cdn/tag/snice-tag.js +1 -1
  176. package/dist/cdn/tag-input/snice-tag-input.js +1 -1
  177. package/dist/cdn/terminal/snice-terminal.js +1 -1
  178. package/dist/cdn/testimonial/snice-testimonial.js +1 -1
  179. package/dist/cdn/textarea/snice-textarea.js +1 -1
  180. package/dist/cdn/time-picker/snice-time-picker.js +1 -1
  181. package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
  182. package/dist/cdn/timeline/snice-timeline.js +1 -1
  183. package/dist/cdn/timer/snice-timer.js +1 -1
  184. package/dist/cdn/toast/snice-toast.js +1 -1
  185. package/dist/cdn/tooltip/snice-tooltip.js +1 -1
  186. package/dist/cdn/tree/snice-tree.js +1 -1
  187. package/dist/cdn/treemap/snice-treemap.js +1 -1
  188. package/dist/cdn/user-card/snice-user-card.js +1 -1
  189. package/dist/cdn/video-player/snice-video-player.js +1 -1
  190. package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
  191. package/dist/cdn/waterfall/snice-waterfall.js +1 -1
  192. package/dist/cdn/weather/snice-weather.js +1 -1
  193. package/dist/cdn/work-order/snice-work-order.js +1 -1
  194. package/dist/components/avatar/snice-avatar.d.ts +1 -0
  195. package/dist/components/avatar/snice-avatar.js +8 -2
  196. package/dist/components/avatar/snice-avatar.js.map +1 -1
  197. package/dist/components/avatar/snice-avatar.types.d.ts +2 -0
  198. package/dist/components/button/snice-button.js +1 -1
  199. package/dist/components/button/snice-button.js.map +1 -1
  200. package/dist/components/calendar/snice-calendar.d.ts +38 -1
  201. package/dist/components/calendar/snice-calendar.js +390 -40
  202. package/dist/components/calendar/snice-calendar.js.map +1 -1
  203. package/dist/components/calendar/snice-calendar.types.d.ts +63 -0
  204. package/dist/components/select/snice-select.js +2 -0
  205. package/dist/components/select/snice-select.js.map +1 -1
  206. package/dist/components/switch/snice-switch.js +7 -3
  207. package/dist/components/switch/snice-switch.js.map +1 -1
  208. package/dist/components/table/snice-table.d.ts +9 -0
  209. package/dist/components/table/snice-table.js +72 -2
  210. package/dist/components/table/snice-table.js.map +1 -1
  211. package/dist/components/table/table-keyboard.js +1 -1
  212. package/dist/components/table/table-keyboard.js.map +1 -1
  213. package/dist/index.cjs +703 -264
  214. package/dist/index.cjs.map +1 -1
  215. package/dist/index.d.ts +3 -1
  216. package/dist/index.esm.js +703 -264
  217. package/dist/index.esm.js.map +1 -1
  218. package/dist/index.iife.js +703 -264
  219. package/dist/index.iife.js.map +1 -1
  220. package/dist/parts.d.ts +14 -4
  221. package/dist/react/SniceProvider.js +1 -1
  222. package/dist/react/SniceRouter.js +1 -1
  223. package/dist/react/index.js +1 -1
  224. package/dist/react/matchRoute.js +68 -2
  225. package/dist/react/matchRoute.js.map +1 -1
  226. package/dist/react/route-match.d.ts +7 -0
  227. package/dist/react/useRequestHandler.js +1 -1
  228. package/dist/render-errors.d.ts +9 -0
  229. package/dist/route-match.d.ts +7 -0
  230. package/dist/symbols.cjs +1 -1
  231. package/dist/symbols.esm.js +1 -1
  232. package/dist/template-marker.d.ts +5 -0
  233. package/dist/testing-dom.cjs +1 -1
  234. package/dist/testing-dom.esm.js +1 -1
  235. package/dist/testing.d.ts +3 -0
  236. package/dist/transitions.cjs +1 -1
  237. package/dist/transitions.esm.js +1 -1
  238. package/dist/types/dispatch-options.d.ts +5 -4
  239. package/dist/types/event-timing.d.ts +7 -0
  240. package/dist/types/index.d.ts +1 -0
  241. package/dist/types/on-options.d.ts +21 -6
  242. package/dist/utils.d.ts +3 -0
  243. package/docs/ai/DEVELOPMENT.md +1 -1
  244. package/docs/ai/api.md +7 -2
  245. package/docs/ai/cli.md +14 -0
  246. package/docs/ai/components/avatar.md +1 -0
  247. package/docs/ai/components/calendar.md +24 -1
  248. package/docs/ai/components/switch.md +3 -0
  249. package/docs/ai/components/table.md +1 -1
  250. package/docs/ai/decorators.md +3 -2
  251. package/docs/ai/events.md +45 -8
  252. package/docs/ai/rendering.md +7 -0
  253. package/docs/ai/routing.md +6 -1
  254. package/docs/ai/testing.md +17 -0
  255. package/docs/cli.md +19 -0
  256. package/docs/components/avatar.md +1 -0
  257. package/docs/components/calendar.md +112 -2
  258. package/docs/components/switch.md +7 -1
  259. package/docs/components/table.md +2 -1
  260. package/docs/events.md +90 -10
  261. package/docs/rendering.md +10 -0
  262. package/docs/routing.md +22 -1
  263. package/docs/testing.md +19 -0
  264. package/package.json +2 -2
  265. package/vscode.html-custom-data.json +28 -0
package/dist/parts.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import { TemplateResult } from './template';
2
+ import { type RenderHostIdentity } from './render-errors';
3
+ type PropertyBindingRegistry = Pick<CustomElementRegistry, 'get' | 'whenDefined'>;
2
4
  export declare const noChange: unique symbol;
3
5
  export type NoChange = typeof noChange;
4
6
  declare const LIVE_MARKER: unique symbol;
@@ -45,18 +47,21 @@ interface TemplatePart {
45
47
  * Instance of a rendered template
46
48
  */
47
49
  export declare class TemplateInstance {
50
+ #private;
48
51
  template: Template;
49
52
  strings: TemplateStringsArray;
50
53
  parts: Part[];
51
54
  fragment: DocumentFragment | null;
52
55
  private conditionalParts;
53
56
  private regularParts;
54
- constructor(result: TemplateResult);
57
+ constructor(result: TemplateResult, renderIdentity?: RenderHostIdentity | null, customElementRegistry?: PropertyBindingRegistry | null);
55
58
  /**
56
59
  * Check if this instance uses the same template strings
57
60
  * (template identity is based on the strings array reference)
58
61
  */
59
62
  isSameTemplate(strings: TemplateStringsArray): boolean;
63
+ /** Internal attribution value; intentionally contains no live renderer host. */
64
+ getRenderHostIdentity(): RenderHostIdentity | null;
60
65
  renderFragment(): DocumentFragment;
61
66
  render(values: readonly any[]): DocumentFragment;
62
67
  update(values: readonly any[]): void;
@@ -85,6 +90,7 @@ export declare abstract class Part {
85
90
  * - Reuses child NodeParts when rendering arrays
86
91
  */
87
92
  export declare class NodePart extends Part {
93
+ #private;
88
94
  readonly type: "node";
89
95
  private startNode;
90
96
  private endNode;
@@ -98,7 +104,8 @@ export declare class NodePart extends Part {
98
104
  private _asyncCompleted;
99
105
  private _asyncPaused;
100
106
  private _committingAsyncValue;
101
- constructor(startNode: Comment, endNode: Comment);
107
+ constructor(startNode: Comment, endNode: Comment, renderIdentity?: RenderHostIdentity | null, customElementRegistry?: PropertyBindingRegistry | null);
108
+ private _withRenderContext;
102
109
  commit(value: any): void;
103
110
  /**
104
111
  * Commit a primitive value as text
@@ -209,13 +216,14 @@ export declare class StylePart extends Part {
209
216
  }
210
217
  /** Named spreads: ...props, ...attrs, and ...events. */
211
218
  export declare class SpreadPart extends Part {
219
+ #private;
212
220
  readonly type: "spread";
213
221
  element: Element;
214
222
  readonly name: string;
215
223
  private committed;
216
224
  private listeners;
217
225
  private consumedOnce;
218
- constructor(element: Element, name: string);
226
+ constructor(element: Element, name: string, customElementRegistry?: PropertyBindingRegistry | null);
219
227
  commit(value: unknown): void;
220
228
  private commitProperties;
221
229
  private commitAttributes;
@@ -231,12 +239,14 @@ export declare class SpreadPart extends Part {
231
239
  * PropertyPart handles property bindings (.property=${value})
232
240
  */
233
241
  export declare class PropertyPart extends Part {
242
+ #private;
234
243
  readonly type: "property";
235
244
  element: Element;
236
245
  readonly name: string;
237
246
  private _committedValue;
238
- constructor(element: Element, name: string);
247
+ constructor(element: Element, name: string, customElementRegistry?: PropertyBindingRegistry | null);
239
248
  commit(value: unknown): void;
249
+ reconnected(): void;
240
250
  clear(): void;
241
251
  }
242
252
  /**
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -42,6 +42,72 @@ function routeSpecificity(spec) {
42
42
  return score;
43
43
  }
44
44
 
45
+ const EMPTY_QUERY_MARKER = '__snice_empty_query_value__';
46
+ const QUERY_PARAM_PATTERN = /=:[\w-]+\)?$/;
47
+ function containsDecodedMarker(url, marker) {
48
+ if (url.includes(marker))
49
+ return true;
50
+ const queryIndex = url.indexOf('?');
51
+ if (queryIndex === -1)
52
+ return false;
53
+ return url.slice(queryIndex + 1).split('&').some(part => {
54
+ try {
55
+ return decodeURIComponent(part).includes(marker);
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ });
61
+ }
62
+ function emptyQueryMarker(route, url) {
63
+ let marker = EMPTY_QUERY_MARKER;
64
+ while (route.spec.includes(marker) || containsDecodedMarker(url, marker)) {
65
+ marker += '_';
66
+ }
67
+ return marker;
68
+ }
69
+ function fillEmptyQueryParams(route, url, marker) {
70
+ const urlQueryIndex = url.indexOf('?');
71
+ const routeQueryIndex = route.spec.indexOf('?');
72
+ if (urlQueryIndex === -1 || routeQueryIndex === -1)
73
+ return null;
74
+ const urlParts = url.slice(urlQueryIndex + 1).split('&');
75
+ const routeParts = route.spec.slice(routeQueryIndex + 1).split('&');
76
+ let changed = false;
77
+ const normalizedParts = urlParts.map((part, index) => {
78
+ if (part.endsWith('=') && QUERY_PARAM_PATTERN.test(routeParts[index] ?? '')) {
79
+ changed = true;
80
+ return `${part}${marker}`;
81
+ }
82
+ return part;
83
+ });
84
+ if (!changed)
85
+ return null;
86
+ return `${url.slice(0, urlQueryIndex + 1)}${normalizedParts.join('&')}`;
87
+ }
88
+ /**
89
+ * Match through pica-route while preserving legal empty query parameter values.
90
+ * pica-route 1.1.2 rejects an empty param and therefore discards the whole route.
91
+ * Keep this compatibility shim in sync with packages/core/src/route-match.ts.
92
+ */
93
+ function matchRoute(route, url) {
94
+ const directMatch = route.match(url);
95
+ if (directMatch !== false)
96
+ return directMatch;
97
+ const marker = emptyQueryMarker(route, url);
98
+ const normalizedUrl = fillEmptyQueryParams(route, url, marker);
99
+ if (normalizedUrl === null)
100
+ return false;
101
+ const params = route.match(normalizedUrl);
102
+ if (params === false)
103
+ return false;
104
+ for (const name of Object.keys(params)) {
105
+ if (params[name] === marker)
106
+ params[name] = '';
107
+ }
108
+ return params;
109
+ }
110
+
45
111
  /**
46
112
  * Match a URL path against an array of route configs.
47
113
  * Uses pica-route — same matching as vanilla Snice's Router.
@@ -62,7 +128,7 @@ function matchRoutes(routes, pathname) {
62
128
  || a.registrationOrder - b.registrationOrder);
63
129
  for (const route of sorted) {
64
130
  const matcher = new Route(route.path);
65
- const params = matcher.match(pathname);
131
+ const params = matchRoute(matcher, pathname);
66
132
  if (params !== false) {
67
133
  return {
68
134
  index: route.index,
@@ -1 +1 @@
1
- {"version":3,"file":"matchRoute.js","sources":["../../src/react/route-specificity.ts","../../src/react/matchRoute.ts"],"sourcesContent":["/**\n * Route specificity scorer for the React binding.\n *\n * IMPORTANT: keep this in exact sync with `src/route-specificity.ts`. The React\n * integration is bundled in isolation (rootDir: src/react) and cannot import\n * across that boundary, so the scorer is mirrored here. The `matchRoutes`\n * resolution tests exercise this copy, so behavioral drift is caught.\n *\n * Scoring is per-segment by ROLE, not string length: a static segment outweighs\n * a dynamic (`:param`) segment, which outweighs a wildcard (`*` / `*name`). So\n * `/post/new` beats `/post/:id`, and a splat always ranks last. Sort callers\n * descending by this score (highest = most specific = first).\n */\nconst STATIC_SEGMENT = 10;\nconst DYNAMIC_SEGMENT = 3;\nconst WILDCARD_SEGMENT = -2;\n\nexport function routeSpecificity(spec: string): number {\n const segments = spec.split('/').filter(Boolean);\n // Base on segment count so a deeper path outranks a shallower one on ties.\n let score = segments.length;\n\n for (const segment of segments) {\n if (segment.startsWith('*')) {\n score += WILDCARD_SEGMENT;\n } else if (segment.startsWith(':')) {\n score += DYNAMIC_SEGMENT;\n } else {\n score += STATIC_SEGMENT;\n }\n }\n\n return score;\n}\n","import { Route, type RouteParams } from 'pica-route';\nimport { routeSpecificity } from './route-specificity';\n\nexport interface RouteConfig {\n path: string;\n index: number;\n /** Lower values win when route specificity ties. Defaults to 0. */\n order?: number;\n}\n\nexport interface MatchResult {\n index: number;\n params: RouteParams;\n path: string;\n}\n\n/**\n * Match a URL path against an array of route configs.\n * Uses pica-route — same matching as vanilla Snice's Router.\n * Routes are sorted by per-segment specificity (static > dynamic > wildcard),\n * then optional lower-first order, then declaration order.\n */\nexport function matchRoutes(routes: RouteConfig[], pathname: string): MatchResult | null {\n for (const route of routes) {\n if (route.order !== undefined && !Number.isFinite(route.order)) {\n throw new TypeError(`Route order for \"${route.path}\" must be a finite number.`);\n }\n }\n // Sort by specificity (most specific first), same model as the vanilla Router.\n const sorted = routes\n .map((route, registrationOrder) => ({ ...route, registrationOrder }))\n .sort((a, b) =>\n routeSpecificity(b.path) - routeSpecificity(a.path)\n || (a.order ?? 0) - (b.order ?? 0)\n || a.registrationOrder - b.registrationOrder\n );\n\n for (const route of sorted) {\n const matcher = new Route(route.path);\n const params = matcher.match(pathname);\n if (params !== false) {\n return {\n index: route.index,\n params: params as RouteParams,\n path: route.path,\n };\n }\n }\n\n return null;\n}\n"],"names":[],"mappings":";;;;;;;;;;AAAA;;;;;;;;;;;;AAYG;AACH,MAAM,cAAc,GAAG,EAAE;AACzB,MAAM,eAAe,GAAG,CAAC;AACzB,MAAM,gBAAgB,GAAG,EAAE;AAErB,SAAU,gBAAgB,CAAC,IAAY,EAAA;AAC3C,IAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;;AAEhD,IAAA,IAAI,KAAK,GAAG,QAAQ,CAAC,MAAM;AAE3B,IAAA,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;AAC9B,QAAA,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;YAC3B,KAAK,IAAI,gBAAgB;QAC3B;AAAO,aAAA,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;YAClC,KAAK,IAAI,eAAe;QAC1B;aAAO;YACL,KAAK,IAAI,cAAc;QACzB;IACF;AAEA,IAAA,OAAO,KAAK;AACd;;ACjBA;;;;;AAKG;AACG,SAAU,WAAW,CAAC,MAAqB,EAAE,QAAgB,EAAA;AACjE,IAAA,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;AAC1B,QAAA,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;YAC9D,MAAM,IAAI,SAAS,CAAC,CAAA,iBAAA,EAAoB,KAAK,CAAC,IAAI,CAAA,0BAAA,CAA4B,CAAC;QACjF;IACF;;IAEA,MAAM,MAAM,GAAG;AACZ,SAAA,GAAG,CAAC,CAAC,KAAK,EAAE,iBAAiB,MAAM,EAAE,GAAG,KAAK,EAAE,iBAAiB,EAAE,CAAC;SACnE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KACT,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,IAAI;AAC/C,WAAA,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,CAAC;AAC9B,WAAA,CAAC,CAAC,iBAAiB,GAAG,CAAC,CAAC,iBAAiB,CAC7C;AAEH,IAAA,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;QAC1B,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;QACrC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;AACtC,QAAA,IAAI,MAAM,KAAK,KAAK,EAAE;YACpB,OAAO;gBACL,KAAK,EAAE,KAAK,CAAC,KAAK;AAClB,gBAAA,MAAM,EAAE,MAAqB;gBAC7B,IAAI,EAAE,KAAK,CAAC,IAAI;aACjB;QACH;IACF;AAEA,IAAA,OAAO,IAAI;AACb;;;;"}
1
+ {"version":3,"file":"matchRoute.js","sources":["../../src/react/route-specificity.ts","../../src/react/route-match.ts","../../src/react/matchRoute.ts"],"sourcesContent":["/**\n * Route specificity scorer for the React binding.\n *\n * IMPORTANT: keep this in exact sync with `src/route-specificity.ts`. The React\n * integration is bundled in isolation (rootDir: src/react) and cannot import\n * across that boundary, so the scorer is mirrored here. The `matchRoutes`\n * resolution tests exercise this copy, so behavioral drift is caught.\n *\n * Scoring is per-segment by ROLE, not string length: a static segment outweighs\n * a dynamic (`:param`) segment, which outweighs a wildcard (`*` / `*name`). So\n * `/post/new` beats `/post/:id`, and a splat always ranks last. Sort callers\n * descending by this score (highest = most specific = first).\n */\nconst STATIC_SEGMENT = 10;\nconst DYNAMIC_SEGMENT = 3;\nconst WILDCARD_SEGMENT = -2;\n\nexport function routeSpecificity(spec: string): number {\n const segments = spec.split('/').filter(Boolean);\n // Base on segment count so a deeper path outranks a shallower one on ties.\n let score = segments.length;\n\n for (const segment of segments) {\n if (segment.startsWith('*')) {\n score += WILDCARD_SEGMENT;\n } else if (segment.startsWith(':')) {\n score += DYNAMIC_SEGMENT;\n } else {\n score += STATIC_SEGMENT;\n }\n }\n\n return score;\n}\n","import type { Route, RouteParams } from 'pica-route';\n\nconst EMPTY_QUERY_MARKER = '__snice_empty_query_value__';\nconst QUERY_PARAM_PATTERN = /=:[\\w-]+\\)?$/;\n\nfunction containsDecodedMarker(url: string, marker: string): boolean {\n if (url.includes(marker)) return true;\n\n const queryIndex = url.indexOf('?');\n if (queryIndex === -1) return false;\n\n return url.slice(queryIndex + 1).split('&').some(part => {\n try {\n return decodeURIComponent(part).includes(marker);\n } catch {\n return false;\n }\n });\n}\n\nfunction emptyQueryMarker(route: Route, url: string): string {\n let marker = EMPTY_QUERY_MARKER;\n while (route.spec.includes(marker) || containsDecodedMarker(url, marker)) {\n marker += '_';\n }\n return marker;\n}\n\nfunction fillEmptyQueryParams(route: Route, url: string, marker: string): string | null {\n const urlQueryIndex = url.indexOf('?');\n const routeQueryIndex = route.spec.indexOf('?');\n if (urlQueryIndex === -1 || routeQueryIndex === -1) return null;\n\n const urlParts = url.slice(urlQueryIndex + 1).split('&');\n const routeParts = route.spec.slice(routeQueryIndex + 1).split('&');\n let changed = false;\n\n const normalizedParts = urlParts.map((part, index) => {\n if (part.endsWith('=') && QUERY_PARAM_PATTERN.test(routeParts[index] ?? '')) {\n changed = true;\n return `${part}${marker}`;\n }\n return part;\n });\n\n if (!changed) return null;\n return `${url.slice(0, urlQueryIndex + 1)}${normalizedParts.join('&')}`;\n}\n\n/**\n * Match through pica-route while preserving legal empty query parameter values.\n * pica-route 1.1.2 rejects an empty param and therefore discards the whole route.\n * Keep this compatibility shim in sync with packages/core/src/route-match.ts.\n */\nexport function matchRoute(route: Route, url: string): RouteParams | false {\n const directMatch = route.match(url);\n if (directMatch !== false) return directMatch;\n\n const marker = emptyQueryMarker(route, url);\n const normalizedUrl = fillEmptyQueryParams(route, url, marker);\n if (normalizedUrl === null) return false;\n\n const params = route.match(normalizedUrl);\n if (params === false) return false;\n\n for (const name of Object.keys(params)) {\n if (params[name] === marker) params[name] = '';\n }\n return params;\n}\n","import { Route, type RouteParams } from 'pica-route';\nimport { routeSpecificity } from './route-specificity';\nimport { matchRoute } from './route-match';\n\nexport interface RouteConfig {\n path: string;\n index: number;\n /** Lower values win when route specificity ties. Defaults to 0. */\n order?: number;\n}\n\nexport interface MatchResult {\n index: number;\n params: RouteParams;\n path: string;\n}\n\n/**\n * Match a URL path against an array of route configs.\n * Uses pica-route — same matching as vanilla Snice's Router.\n * Routes are sorted by per-segment specificity (static > dynamic > wildcard),\n * then optional lower-first order, then declaration order.\n */\nexport function matchRoutes(routes: RouteConfig[], pathname: string): MatchResult | null {\n for (const route of routes) {\n if (route.order !== undefined && !Number.isFinite(route.order)) {\n throw new TypeError(`Route order for \"${route.path}\" must be a finite number.`);\n }\n }\n // Sort by specificity (most specific first), same model as the vanilla Router.\n const sorted = routes\n .map((route, registrationOrder) => ({ ...route, registrationOrder }))\n .sort((a, b) =>\n routeSpecificity(b.path) - routeSpecificity(a.path)\n || (a.order ?? 0) - (b.order ?? 0)\n || a.registrationOrder - b.registrationOrder\n );\n\n for (const route of sorted) {\n const matcher = new Route(route.path);\n const params = matchRoute(matcher, pathname);\n if (params !== false) {\n return {\n index: route.index,\n params: params as RouteParams,\n path: route.path,\n };\n }\n }\n\n return null;\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { Route, RouteParams } from 'pica-route';
2
+ /**
3
+ * Match through pica-route while preserving legal empty query parameter values.
4
+ * pica-route 1.1.2 rejects an empty param and therefore discards the whole route.
5
+ * Keep this compatibility shim in sync with packages/core/src/route-match.ts.
6
+ */
7
+ export declare function matchRoute(route: Route, url: string): RouteParams | false;
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -0,0 +1,9 @@
1
+ /** Sanitized render attribution. It contains no host, node, constructor, or prototype. */
2
+ export interface RenderHostIdentity {
3
+ readonly label: string;
4
+ readonly marker: symbol;
5
+ }
6
+ export declare function registerRenderHostIdentity(constructor: unknown, tagName: string): void;
7
+ /** Capture once at the render boundary; failures fall back without masking the render error. */
8
+ export declare function captureRenderHostIdentity(host: HTMLElement): RenderHostIdentity;
9
+ export declare function contextualizeRenderError(identity: RenderHostIdentity, error: unknown): Error;
@@ -0,0 +1,7 @@
1
+ import type { Route, RouteParams } from 'pica-route';
2
+ /**
3
+ * Match through pica-route while preserving legal empty query parameter values.
4
+ * pica-route 1.1.2 rejects an empty param and therefore discards the whole route.
5
+ * Keep this compatibility shim in sync with packages/react/src/route-match.ts.
6
+ */
7
+ export declare function matchRoute(route: Route, url: string): RouteParams | false;
package/dist/symbols.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -0,0 +1,5 @@
1
+ export declare const templateMarker: string;
2
+ export declare const templateMarkerMatch: string;
3
+ export declare const templateNodeMarker: string;
4
+ export declare const templateMarkerRegex: RegExp;
5
+ export declare function hasTemplateMarker(value: string | null): boolean;
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
package/dist/testing.d.ts CHANGED
@@ -2,3 +2,6 @@ export * from './index';
2
2
  export { registerControllerCleanup, getControllerScope, attachController, detachController, getController, cleanupNativeElementControllers } from './controller';
3
3
  export { CONTROLLER_KEY, CONTROLLER_OPERATIONS, CONTROLLER_NAME_KEY, CONTROLLER_ID, PROPERTIES_INITIALIZED, PROPERTY_VALUES, PROPERTY_WATCHERS, EXPLICITLY_SET_PROPERTIES, READY_PROMISE, READY_RESOLVE, READY_HANDLERS, DISPOSE_HANDLERS, PARTS, PART_TIMERS, ROUTER_CONTEXT, CONTEXT_REQUEST_HANDLER, PAGE_TRANSITION, CREATED_AT, IS_ELEMENT_CLASS, IS_CONTROLLER_CLASS, IS_CONTROLLER_INSTANCE, IS_DAEMON_CLASS, IS_DAEMON_INSTANCE, DAEMON_STATE, NATIVE_CONTROLLER, DIRECT_CONTROLLER, PENDING_CONTROLLER_BINDING, CLEANUP, ON_HANDLERS, DISPATCH_TIMERS, CHANNEL_HANDLERS, OBSERVERS } from './symbols';
4
4
  export { parseAttributeValue, detectType } from './utils';
5
+ export { TemplateInstance } from './parts';
6
+ export { captureRenderHostIdentity } from './render-errors';
7
+ export { renderElementNow } from './render';
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * snice v7.4.0
2
+ * snice v7.6.0
3
3
  * A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
4
4
  * (c) 2024
5
5
  * Released under the MIT License.
@@ -1,13 +1,14 @@
1
1
  import type { OnScope } from './on-options';
2
+ import type { EventTiming } from './event-timing';
2
3
  export interface DispatchOptions extends EventInit {
3
4
  /**
4
5
  * Whether to dispatch even if the method returns undefined (default: true)
5
6
  */
6
7
  dispatchOnUndefined?: boolean;
7
- /** Debounce the dispatch by specified milliseconds */
8
- debounce?: number;
9
- /** Throttle the dispatch by specified milliseconds */
10
- throttle?: number;
8
+ /** Debounce by milliseconds or resolve milliseconds from this instance */
9
+ debounce?: EventTiming;
10
+ /** Throttle by milliseconds or resolve milliseconds from this instance */
11
+ throttle?: EventTiming;
11
12
  /**
12
13
  * Where to dispatch the event. Default is the host element.
13
14
  * Use `'global'` to dispatch on `document`, a selector for an ancestor
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Milliseconds used by an event timing option.
3
+ *
4
+ * A resolver is invoked with the decorated element, controller, or daemon as
5
+ * `this`, allowing the interval to vary per instance.
6
+ */
7
+ export type EventTiming = number | ((this: any) => number);
@@ -13,6 +13,7 @@ export * from './page-options';
13
13
  export * from './router-instance';
14
14
  export * from './on-options';
15
15
  export * from './dispatch-options';
16
+ export * from './event-timing';
16
17
  export * from './i-controller';
17
18
  export * from './transition';
18
19
  export * from './observe-options';
@@ -1,3 +1,4 @@
1
+ import type { EventTiming } from './event-timing';
1
2
  /**
2
3
  * Resolver for the EventTarget where the listener is attached.
3
4
  * - `'global'`: attach to `document` (cross-cutting events)
@@ -9,7 +10,9 @@ export type OnScope = 'global' | string | EventTarget | ((this: HTMLElement) =>
9
10
  export interface OnOptions {
10
11
  /** Use capture phase instead of bubble phase */
11
12
  capture?: boolean;
12
- /** Remove listener after first trigger */
13
+ /** Run the handler exactly once, then remove every listener it registered.
14
+ * Events that don't match the delegation selector or key filter don't
15
+ * consume it. */
13
16
  once?: boolean;
14
17
  /** Passive listener (can't preventDefault) */
15
18
  passive?: boolean;
@@ -17,12 +20,24 @@ export interface OnOptions {
17
20
  preventDefault?: boolean;
18
21
  /** Automatically call stopPropagation on the event */
19
22
  stopPropagation?: boolean;
20
- /** Debounce the handler by specified milliseconds */
21
- debounce?: number;
22
- /** Throttle the handler by specified milliseconds */
23
- throttle?: number;
24
- /** CSS selector to target specific elements within shadow root */
23
+ /** Debounce by milliseconds or resolve milliseconds from this instance */
24
+ debounce?: EventTiming;
25
+ /** Throttle by milliseconds or resolve milliseconds from this instance */
26
+ throttle?: EventTiming;
27
+ /** CSS selector for event delegation; equivalent to the positional selector argument */
25
28
  target?: string;
29
+ /**
30
+ * Listen in the light DOM (the host element and its light-DOM children).
31
+ * Same tree toggle as @query. Defaults to true; combine with `shadow` to
32
+ * pick light, shadow, or both. Ignored when `scope`/`daemon` is set.
33
+ */
34
+ light?: boolean;
35
+ /**
36
+ * Listen in the shadow tree (the component's shadow root).
37
+ * Same tree toggle as @query. Defaults to true; combine with `light` to
38
+ * pick light, shadow, or both. Ignored when `scope`/`daemon` is set.
39
+ */
40
+ shadow?: boolean;
26
41
  /**
27
42
  * Where to attach the listener. Default is the host element.
28
43
  * Use `'global'` for document-wide events, a selector for an ancestor
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { PropertyOptions } from './types';
2
+ import type { EventTiming } from './types/event-timing';
2
3
  /**
3
4
  * Detects the type constructor from an initial value
4
5
  * @param initialValue - The default value assigned to a property field (e.g., `name = "default"` -> "default")
@@ -108,6 +109,8 @@ export declare function createDebounced<T extends (...args: any[]) => any>(fn: T
108
109
  * Create a throttled version of a function (leading + trailing edge)
109
110
  */
110
111
  export declare function createThrottled<T extends (...args: any[]) => any>(fn: T, delay: number): T;
112
+ /** Resolve and validate an @on/@dispatch debounce or throttle interval. */
113
+ export declare function resolveEventTiming(instance: object, timing: EventTiming | undefined, decorator: '@on' | '@dispatch', option: 'debounce' | 'throttle'): number | undefined;
111
114
  /**
112
115
  * Resolve a scope option to a concrete EventTarget.
113
116
  *
@@ -140,7 +140,7 @@ All CDN builds use the shared runtime (external `snice` imports). Load `snice-ru
140
140
  - IIFE: `.js` + `.min.js`
141
141
  - Sourcemaps + README
142
142
 
143
- **Size:** Runtime ~32KB gzip, components ~1-110KB each
143
+ **Size:** Runtime ~34KB gzip, components ~1-111KB each
144
144
 
145
145
  **Features:**
146
146
  - Shared runtime (external snice imports)
package/docs/ai/api.md CHANGED
@@ -114,6 +114,8 @@ html`
114
114
  @on(event: string | string[], selector?: string, options?: OnOptions)
115
115
  // Works in elements + controllers
116
116
  // Options: { debounce?, throttle?, preventDefault?, stopPropagation?, once?, capture?, passive?, target?, scope?, daemon? }
117
+ // debounce/throttle: number | function called with decorated instance as `this`
118
+ // @on resolves timing functions at listener setup/reconnect; finite non-negative ms required
117
119
  // target: CSS selector for shadow DOM event delegation
118
120
  // scope: 'global' | selector | EventTarget | () => EventTarget | null — redirects listener attachment
119
121
  // Keyboard: 'keydown:Enter', 'keydown.escape', 'keydown:ctrl+s', 'keydown:~Space'
@@ -122,6 +124,8 @@ html`
122
124
  @dispatch(eventName: string, options?: { debounce?, throttle?, dispatchOnUndefined?, scope?, daemon?, ...EventInit })
123
125
  // Fires CustomEvent after method, detail = return value
124
126
  // Supports async methods (dispatches after promise resolves)
127
+ // Timing functions resolve against the decorated instance per invocation
128
+ // Teardown cancels queued and unresolved-async dispatch work
125
129
  // dispatchOnUndefined: false — skips when return is undefined; omitted/true still dispatches
126
130
  // scope: 'global' | selector | EventTarget | () => EventTarget | null — redirects dispatch target
127
131
  ```
@@ -367,8 +371,9 @@ Full references: [rendering.md](rendering.md) for renderer structure and [bindin
367
371
  ```typescript
368
372
  interface TemplateResult { readonly _$litType$: number; }
369
373
  interface CSSResult { cssText: string; }
370
- interface OnOptions { debounce?, throttle?, preventDefault?, stopPropagation?, once?, capture?, passive?, target?, scope? }
371
- interface DispatchOptions extends EventInit { debounce?, throttle?, dispatchOnUndefined?, scope? }
374
+ type EventTiming = number | ((this: any) => number)
375
+ interface OnOptions { debounce?: EventTiming, throttle?: EventTiming, preventDefault?, stopPropagation?, once?, capture?, passive?, target?, scope? }
376
+ interface DispatchOptions extends EventInit { debounce?: EventTiming, throttle?: EventTiming, dispatchOnUndefined?, scope? }
372
377
  type OnScope = 'global' | string | EventTarget | ((this: HTMLElement) => EventTarget | null)
373
378
  interface RenderOptions { debounce?, throttle?, once?, sync?, differential? }
374
379
  interface PropertyOptions { type?, attribute?, reflect?, deep?, converter?, hasChanged? }
package/docs/ai/cli.md CHANGED
@@ -46,6 +46,20 @@ npx snice validate # source analyzer only
46
46
  - deep imports that were never released package paths, e.g. `snice/decorators`
47
47
  - Router missing `target`, `type`, or a project-wide `initialize()` call
48
48
  - a routed class combining `@page` with redundant `@element`
49
+ - route path/query `:param` or named `*splat` without a reachable attribute target,
50
+ including `attribute: false` and mismatched aliases
51
+ (`snice/route-param-has-no-binding-target`, non-blocking warning)
52
+ - controller `@context()` handlers that start load/reload/refresh/fetch work
53
+ without `{ once: true }`, a first-delivery guard, or an update diff
54
+ (`snice/unguarded-context-load`, non-blocking warning)
55
+
56
+ Route-param inheritance follows proven local declarations, direct relative
57
+ re-exports, and named or namespace Snice imports. Missing-target findings are
58
+ deferred for unresolved/ambiguous bases or dynamic route/attribute contracts;
59
+ locally visible disabled or mismatched properties are still reported.
60
+ The rule stays warning-level because the dependency-free static lexer cannot
61
+ model every valid JavaScript/TypeScript grammar edge; verify the Router
62
+ attribute channel, but this finding alone does not fail `validate` or `check`.
49
63
 
50
64
  Non-blocking architecture suggestions:
51
65
  - `@page` -> `src/pages/`
@@ -18,6 +18,7 @@ alt: string = ''; // Alt text (falls back to `name`)
18
18
  name: string = ''; // User's name (initials + color generation)
19
19
  size: 'xs'|'small'|'medium'|'large'|'xl'|'xxl' = 'medium';
20
20
  shape: 'circle'|'square'|'rounded' = 'circle';
21
+ loading: 'lazy'|'eager' = 'lazy'; // img loading strategy
21
22
  fallbackColor: string = '#ffffff'; // attr: fallback-color — text color for initials
22
23
  fallbackBackground: string = ''; // attr: fallback-background — overrides auto-color
23
24
  ```
@@ -15,6 +15,8 @@ highlightToday: boolean = true; // attribute: highlight-today
15
15
  showWeekNumbers: boolean = false; // attribute: show-week-numbers
16
16
  firstDayOfWeek: number = 0; // attribute: first-day-of-week (0=Sun, 1=Mon)
17
17
  locale: string = 'en-US';
18
+ noDaySelect: boolean = false; // attribute: no-day-select — display-only: day clicks don't select/highlight or fire calendar-change; event bars stay interactive
19
+ cellSizing: 'square'|'stretch' = 'square'; // attribute: cell-sizing — square: cells as tall as the column is wide; stretch: rows collapse to content + event-lane reservation
18
20
  ```
19
21
 
20
22
  ## Methods
@@ -48,8 +50,29 @@ locale: string = 'en-US';
48
50
  import 'snice/components/calendar/snice-calendar';
49
51
 
50
52
  calendar.events = [
51
- { id: 1, title: 'Meeting', start: new Date(), color: '#2196f3' }
53
+ { id: 1, title: 'Meeting', start: new Date(), color: '#2196f3' },
54
+ { id: 2, title: 'Conf', start: '2026-06-04', end: '2026-06-10' } // ranged
52
55
  ];
56
+ // Ranged events render as continuous stripes: one bar per week row, chopped
57
+ // at week boundaries (squared corners on the continuing side, title repeats).
58
+ // Concurrent events stack into lanes (start asc, longer first on ties);
59
+ // max 3 lanes, deeper stacks collapse to a per-day "+N more" chip.
60
+ // Bars: part="event-bar", click -> calendar-event-click.
61
+ // Styling: color -> background; avatar?: string | {src?,name?,alt?} ->
62
+ // <snice-avatar> on each bar (part="event-avatar"; name -> initials fallback);
63
+ // className?: string -> added to bar classes AND part list, so
64
+ // ::part(<className>) themes specific events from outside.
65
+ // Tooltips: event.tooltip?: string (static), or on the element:
66
+ // eventTooltip: (event) => string | Node | Promise<string | Node> // lazy/rich; wins over event.tooltip
67
+ // Stale async results discarded on pointer-leave. Overlay part="event-tooltip".
68
+ // Popovers (click-to-open, interactive; STRICT per-event opt-in):
69
+ // event.popover?: boolean | string | Node | (() => Node)
70
+ // Resolution: inline content -> element eventPopover(event) => string|Node|Promise
71
+ // -> @request('calendar/event-popover') {event} (answer with @respond) -> warn+close.
72
+ // role="dialog" part="event-popover"; Escape/outside-click dismiss, focus returns
73
+ // to bar; loading state while pending; stale results discarded.
74
+ // Bars with popover: role="button" tabindex=0, Enter/Space opens.
75
+ // calendar-event-click still fires. closeEventPopover() closes programmatically.
53
76
 
54
77
  calendar.addEventListener('calendar-change', (e) => {
55
78
  console.log('Selected:', e.detail.value);
@@ -58,6 +58,9 @@ readonly labels: NodeList | null;
58
58
  - `thumb` - Switch thumb
59
59
  - `spinner` - Loading spinner
60
60
  - `label` - Label text
61
+ - `label-on` / `label-off` - State text inside the track (render when set)
62
+
63
+ State labels: track auto-sizes to the widest label (medium/large; small hides them); checked thumb travel follows actual track width, so `::part(track)` width overrides stay aligned.
61
64
 
62
65
  ## Basic Usage
63
66
 
@@ -52,7 +52,7 @@ interface ColumnDefinition {
52
52
  key:string; label:string; type?:ColumnType; align?:'left'|'center'|'right'; width?:string; flex?:number; minWidth?:number; maxWidth?:number;
53
53
  sortable?:boolean; filterable?:boolean; resizable?:boolean; reorderable?:boolean; hideable?:boolean; pinnable?:boolean; pinned?:'left'|'right'|false;
54
54
  editable?:boolean; exportable?:boolean; editorType?:'text'|'number'|'date'|'boolean'|'select'; selectOptions?:{value:string;label:string}[];
55
- formatter?:(value:any,row?:any)=>string; valueGetter?:(value:any,row:any)=>any; valueFormatter?:(value:any,row:any)=>string;
55
+ formatter?:(value:any,row?:any)=>string; valueGetter?:(value:any,row:any)=>any; /* runs for display, sort, aggregation */ valueFormatter?:(value:any,row:any)=>string;
56
56
  valueParser?:(value:string,row:any)=>any; valueSetter?:(value:any,row:any)=>any; sortComparator?:(a:any,b:any,direction:'asc'|'desc')=>number;
57
57
  colSpan?:number|((value:any,row:any)=>number); aggregate?:Aggregator; renderCell?:(value:any,row:any,column:ColumnDefinition)=>HTMLElement|string;
58
58
  renderEditor?:(value:any,row:any,column:ColumnDefinition,commit:(v:any)=>void,cancel:()=>void)=>HTMLElement;
@@ -31,8 +31,9 @@
31
31
  - `@queryAll('selector', { light? })` - NodeList
32
32
 
33
33
  ## Events
34
- - `@on('event', 'selector?', options?)` - Delegation, auto-bound. Options: `{ capture?, once?, passive?, preventDefault?, stopPropagation?, debounce?, throttle?, scope?, daemon? }`
35
- - `@dispatch('event-name', options?)` - Emit CustomEvent, detail = return value. Supports async. Options: `{ debounce?, throttle?, dispatchOnUndefined?, scope?, daemon?, ...EventInit }`
34
+ - `@on('event', 'selector?', options?)` - Delegation, auto-bound. Options: `{ capture?, once?, passive?, preventDefault?, stopPropagation?, debounce?: EventTiming, throttle?: EventTiming, scope?, daemon? }`
35
+ - `@dispatch('event-name', options?)` - Emit CustomEvent, detail = return value. Supports async. Options: `{ debounce?: EventTiming, throttle?: EventTiming, dispatchOnUndefined?, scope?, daemon?, ...EventInit }`
36
+ - `EventTiming = number | ((this: any) => number)` - Per-instance resolver. `@on` resolves at listener setup; `@dispatch` resolves per invocation. Must return finite non-negative ms; `0` disables.
36
37
 
37
38
  ### scope (on both `@on` and `@dispatch`)
38
39