snice 5.2.2 → 6.0.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.
- package/README.md +5 -8
- package/adapters/react/SniceProvider.js +1 -1
- package/adapters/react/SniceRouter.js +1 -1
- package/adapters/react/audio-recorder.d.ts +0 -4
- package/adapters/react/audio-recorder.d.ts.map +1 -1
- package/adapters/react/audio-recorder.js +1 -1
- package/adapters/react/audio-recorder.js.map +1 -1
- package/adapters/react/audio-recorder.tsx +1 -5
- package/adapters/react/avatar.d.ts +0 -1
- package/adapters/react/avatar.d.ts.map +1 -1
- package/adapters/react/avatar.js +1 -1
- package/adapters/react/avatar.js.map +1 -1
- package/adapters/react/avatar.tsx +1 -2
- package/adapters/react/breadcrumbs.d.ts +0 -1
- package/adapters/react/breadcrumbs.d.ts.map +1 -1
- package/adapters/react/breadcrumbs.js +1 -1
- package/adapters/react/breadcrumbs.js.map +1 -1
- package/adapters/react/breadcrumbs.tsx +1 -2
- package/adapters/react/camera-annotate.d.ts +0 -5
- package/adapters/react/camera-annotate.d.ts.map +1 -1
- package/adapters/react/camera-annotate.js +1 -1
- package/adapters/react/camera-annotate.js.map +1 -1
- package/adapters/react/camera-annotate.tsx +1 -6
- package/adapters/react/card.d.ts +0 -2
- package/adapters/react/card.d.ts.map +1 -1
- package/adapters/react/card.js +1 -1
- package/adapters/react/card.js.map +1 -1
- package/adapters/react/card.tsx +1 -3
- package/adapters/react/carousel.d.ts +0 -1
- package/adapters/react/carousel.d.ts.map +1 -1
- package/adapters/react/carousel.js +1 -1
- package/adapters/react/carousel.js.map +1 -1
- package/adapters/react/carousel.tsx +1 -2
- package/adapters/react/cart.d.ts +0 -1
- package/adapters/react/cart.d.ts.map +1 -1
- package/adapters/react/cart.js +1 -1
- package/adapters/react/cart.js.map +1 -1
- package/adapters/react/cart.tsx +1 -2
- package/adapters/react/chart.d.ts +0 -5
- package/adapters/react/chart.d.ts.map +1 -1
- package/adapters/react/chart.js +1 -1
- package/adapters/react/chart.js.map +1 -1
- package/adapters/react/chart.tsx +1 -6
- package/adapters/react/chat.d.ts +4 -1
- package/adapters/react/chat.d.ts.map +1 -1
- package/adapters/react/chat.js +2 -2
- package/adapters/react/chat.js.map +1 -1
- package/adapters/react/chat.tsx +6 -3
- package/adapters/react/chip.d.ts +0 -1
- package/adapters/react/chip.d.ts.map +1 -1
- package/adapters/react/chip.js +1 -1
- package/adapters/react/chip.js.map +1 -1
- package/adapters/react/chip.tsx +1 -2
- package/adapters/react/command-palette.d.ts +0 -4
- package/adapters/react/command-palette.d.ts.map +1 -1
- package/adapters/react/command-palette.js +1 -1
- package/adapters/react/command-palette.js.map +1 -1
- package/adapters/react/command-palette.tsx +1 -5
- package/adapters/react/comments.d.ts +0 -1
- package/adapters/react/comments.d.ts.map +1 -1
- package/adapters/react/comments.js +1 -1
- package/adapters/react/comments.js.map +1 -1
- package/adapters/react/comments.tsx +1 -2
- package/adapters/react/countdown.d.ts +0 -4
- package/adapters/react/countdown.d.ts.map +1 -1
- package/adapters/react/countdown.js +1 -1
- package/adapters/react/countdown.js.map +1 -1
- package/adapters/react/countdown.tsx +1 -5
- package/adapters/react/date-time-picker.d.ts +0 -1
- package/adapters/react/date-time-picker.d.ts.map +1 -1
- package/adapters/react/date-time-picker.js +1 -1
- package/adapters/react/date-time-picker.js.map +1 -1
- package/adapters/react/date-time-picker.tsx +1 -2
- package/adapters/react/file-upload.d.ts +0 -2
- package/adapters/react/file-upload.d.ts.map +1 -1
- package/adapters/react/file-upload.js +1 -1
- package/adapters/react/file-upload.js.map +1 -1
- package/adapters/react/file-upload.tsx +1 -3
- package/adapters/react/heatmap.d.ts +0 -4
- package/adapters/react/heatmap.d.ts.map +1 -1
- package/adapters/react/heatmap.js +1 -1
- package/adapters/react/heatmap.js.map +1 -1
- package/adapters/react/heatmap.tsx +1 -5
- package/adapters/react/image.d.ts +0 -2
- package/adapters/react/image.d.ts.map +1 -1
- package/adapters/react/image.js +1 -1
- package/adapters/react/image.js.map +1 -1
- package/adapters/react/image.tsx +1 -3
- package/adapters/react/kanban.d.ts +0 -4
- package/adapters/react/kanban.d.ts.map +1 -1
- package/adapters/react/kanban.js +1 -1
- package/adapters/react/kanban.js.map +1 -1
- package/adapters/react/kanban.tsx +1 -5
- package/adapters/react/key-value.d.ts +0 -2
- package/adapters/react/key-value.d.ts.map +1 -1
- package/adapters/react/key-value.js +1 -1
- package/adapters/react/key-value.js.map +1 -1
- package/adapters/react/key-value.tsx +1 -3
- package/adapters/react/leaderboard.d.ts +0 -1
- package/adapters/react/leaderboard.d.ts.map +1 -1
- package/adapters/react/leaderboard.js +1 -1
- package/adapters/react/leaderboard.js.map +1 -1
- package/adapters/react/leaderboard.tsx +1 -2
- package/adapters/react/map.d.ts +0 -3
- package/adapters/react/map.d.ts.map +1 -1
- package/adapters/react/map.js +1 -1
- package/adapters/react/map.js.map +1 -1
- package/adapters/react/map.tsx +1 -4
- package/adapters/react/markdown.d.ts +1 -1
- package/adapters/react/markdown.d.ts.map +1 -1
- package/adapters/react/markdown.js +1 -1
- package/adapters/react/markdown.js.map +1 -1
- package/adapters/react/markdown.tsx +2 -2
- package/adapters/react/matchRoute.d.ts +1 -1
- package/adapters/react/matchRoute.js +38 -4
- package/adapters/react/matchRoute.js.map +1 -1
- package/adapters/react/message-strip.d.ts +1 -1
- package/adapters/react/message-strip.js +1 -1
- package/adapters/react/message-strip.tsx +2 -2
- package/adapters/react/music-player.d.ts +0 -1
- package/adapters/react/music-player.d.ts.map +1 -1
- package/adapters/react/music-player.js +1 -1
- package/adapters/react/music-player.js.map +1 -1
- package/adapters/react/music-player.tsx +1 -2
- package/adapters/react/org-chart.d.ts +0 -1
- package/adapters/react/org-chart.d.ts.map +1 -1
- package/adapters/react/org-chart.js +1 -1
- package/adapters/react/org-chart.js.map +1 -1
- package/adapters/react/org-chart.tsx +1 -2
- package/adapters/react/paint.d.ts +0 -1
- package/adapters/react/paint.d.ts.map +1 -1
- package/adapters/react/paint.js +1 -1
- package/adapters/react/paint.js.map +1 -1
- package/adapters/react/paint.tsx +1 -2
- package/adapters/react/podcast-player.d.ts +0 -2
- package/adapters/react/podcast-player.d.ts.map +1 -1
- package/adapters/react/podcast-player.js +1 -1
- package/adapters/react/podcast-player.js.map +1 -1
- package/adapters/react/podcast-player.tsx +1 -3
- package/adapters/react/product-card.d.ts +0 -3
- package/adapters/react/product-card.d.ts.map +1 -1
- package/adapters/react/product-card.js +1 -1
- package/adapters/react/product-card.js.map +1 -1
- package/adapters/react/product-card.tsx +1 -4
- package/adapters/react/qr-reader.d.ts +0 -4
- package/adapters/react/qr-reader.d.ts.map +1 -1
- package/adapters/react/qr-reader.js +1 -1
- package/adapters/react/qr-reader.js.map +1 -1
- package/adapters/react/qr-reader.tsx +1 -5
- package/adapters/react/recipe.d.ts +0 -5
- package/adapters/react/recipe.d.ts.map +1 -1
- package/adapters/react/recipe.js +1 -1
- package/adapters/react/recipe.js.map +1 -1
- package/adapters/react/recipe.tsx +1 -6
- package/adapters/react/route-specificity.d.ts +1 -0
- package/adapters/react/spotlight.d.ts +0 -2
- package/adapters/react/spotlight.d.ts.map +1 -1
- package/adapters/react/spotlight.js +1 -1
- package/adapters/react/spotlight.js.map +1 -1
- package/adapters/react/spotlight.tsx +1 -3
- package/adapters/react/tag-input.d.ts +0 -4
- package/adapters/react/tag-input.d.ts.map +1 -1
- package/adapters/react/tag-input.js +1 -1
- package/adapters/react/tag-input.js.map +1 -1
- package/adapters/react/tag-input.tsx +1 -5
- package/adapters/react/terminal.d.ts +0 -1
- package/adapters/react/terminal.d.ts.map +1 -1
- package/adapters/react/terminal.js +1 -1
- package/adapters/react/terminal.js.map +1 -1
- package/adapters/react/terminal.tsx +1 -2
- package/adapters/react/time-picker.d.ts +0 -1
- package/adapters/react/time-picker.d.ts.map +1 -1
- package/adapters/react/time-picker.js +1 -1
- package/adapters/react/time-picker.js.map +1 -1
- package/adapters/react/time-picker.tsx +1 -2
- package/adapters/react/timer.d.ts +0 -1
- package/adapters/react/timer.d.ts.map +1 -1
- package/adapters/react/timer.js +1 -1
- package/adapters/react/timer.js.map +1 -1
- package/adapters/react/timer.tsx +1 -2
- package/adapters/react/useRequestHandler.js +1 -1
- package/adapters/react/video-player.d.ts +0 -6
- package/adapters/react/video-player.d.ts.map +1 -1
- package/adapters/react/video-player.js +1 -1
- package/adapters/react/video-player.js.map +1 -1
- package/adapters/react/video-player.tsx +1 -7
- package/adapters/react/virtual-scroller.d.ts +0 -1
- package/adapters/react/virtual-scroller.d.ts.map +1 -1
- package/adapters/react/virtual-scroller.js +1 -1
- package/adapters/react/virtual-scroller.js.map +1 -1
- package/adapters/react/virtual-scroller.tsx +1 -2
- package/bin/templates/default/tests/services/jwt.test.ts +8 -1
- package/bin/templates/react/tests/services/jwt.test.ts +10 -3
- package/dist/cdn/accordion/snice-accordion.js +1 -1
- package/dist/cdn/accordion/snice-accordion.min.js +1 -1
- package/dist/cdn/action-bar/README.md +2 -2
- package/dist/cdn/action-bar/snice-action-bar.js +2 -2
- package/dist/cdn/action-bar/snice-action-bar.js.map +1 -1
- package/dist/cdn/action-bar/snice-action-bar.min.js +2 -2
- package/dist/cdn/action-bar/snice-action-bar.min.js.map +1 -1
- package/dist/cdn/activity-feed/snice-activity-feed.js +1 -1
- package/dist/cdn/activity-feed/snice-activity-feed.min.js +1 -1
- package/dist/cdn/alert/snice-alert.js +1 -1
- package/dist/cdn/alert/snice-alert.js.map +1 -1
- package/dist/cdn/alert/snice-alert.min.js +1 -1
- package/dist/cdn/alert/snice-alert.min.js.map +1 -1
- package/dist/cdn/app-tiles/snice-app-tiles.js +1 -1
- package/dist/cdn/app-tiles/snice-app-tiles.min.js +1 -1
- package/dist/cdn/approval-flow/snice-approval-flow.js +1 -1
- package/dist/cdn/approval-flow/snice-approval-flow.js.map +1 -1
- package/dist/cdn/approval-flow/snice-approval-flow.min.js +1 -1
- package/dist/cdn/approval-flow/snice-approval-flow.min.js.map +1 -1
- package/dist/cdn/audio-recorder/snice-audio-recorder.js +1 -1
- package/dist/cdn/audio-recorder/snice-audio-recorder.min.js +1 -1
- package/dist/cdn/availability/snice-availability.js +1 -1
- package/dist/cdn/availability/snice-availability.min.js +1 -1
- package/dist/cdn/avatar/snice-avatar.js +1 -1
- package/dist/cdn/avatar/snice-avatar.min.js +1 -1
- package/dist/cdn/avatar-group/snice-avatar-group.js +1 -1
- package/dist/cdn/avatar-group/snice-avatar-group.min.js +1 -1
- package/dist/cdn/badge/snice-badge.js +1 -1
- package/dist/cdn/badge/snice-badge.js.map +1 -1
- package/dist/cdn/badge/snice-badge.min.js +1 -1
- package/dist/cdn/badge/snice-badge.min.js.map +1 -1
- package/dist/cdn/banner/README.md +1 -1
- package/dist/cdn/banner/snice-banner.js +2 -2
- package/dist/cdn/banner/snice-banner.js.map +1 -1
- package/dist/cdn/banner/snice-banner.min.js +2 -2
- package/dist/cdn/banner/snice-banner.min.js.map +1 -1
- package/dist/cdn/binpack/snice-binpack.js +1 -1
- package/dist/cdn/binpack/snice-binpack.min.js +1 -1
- package/dist/cdn/book/snice-book.js +1 -1
- package/dist/cdn/book/snice-book.min.js +1 -1
- package/dist/cdn/booking/snice-booking.js +1 -1
- package/dist/cdn/booking/snice-booking.min.js +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.js.map +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.min.js +1 -1
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.min.js.map +1 -1
- package/dist/cdn/button/snice-button.js +1 -1
- package/dist/cdn/button/snice-button.js.map +1 -1
- package/dist/cdn/button/snice-button.min.js +1 -1
- package/dist/cdn/button/snice-button.min.js.map +1 -1
- package/dist/cdn/calendar/snice-calendar.js +1 -1
- package/dist/cdn/calendar/snice-calendar.min.js +1 -1
- package/dist/cdn/camera/snice-camera.js +1 -1
- package/dist/cdn/camera/snice-camera.min.js +1 -1
- package/dist/cdn/camera-annotate/snice-camera-annotate.js +1 -1
- package/dist/cdn/camera-annotate/snice-camera-annotate.min.js +1 -1
- package/dist/cdn/candlestick/snice-candlestick.js +1 -1
- package/dist/cdn/candlestick/snice-candlestick.min.js +1 -1
- package/dist/cdn/card/snice-card.js +1 -1
- package/dist/cdn/card/snice-card.min.js +1 -1
- package/dist/cdn/carousel/snice-carousel.js +1 -1
- package/dist/cdn/carousel/snice-carousel.min.js +1 -1
- package/dist/cdn/cart/snice-cart.js +1 -1
- package/dist/cdn/cart/snice-cart.min.js +1 -1
- package/dist/cdn/chart/snice-chart.js +1 -1
- package/dist/cdn/chart/snice-chart.js.map +1 -1
- package/dist/cdn/chart/snice-chart.min.js +1 -1
- package/dist/cdn/chart/snice-chart.min.js.map +1 -1
- package/dist/cdn/chat/README.md +2 -2
- package/dist/cdn/chat/snice-chat.js +785 -89
- package/dist/cdn/chat/snice-chat.js.map +1 -1
- package/dist/cdn/chat/snice-chat.min.js +71 -75
- package/dist/cdn/chat/snice-chat.min.js.map +1 -1
- package/dist/cdn/checkbox/snice-checkbox.js +1 -1
- package/dist/cdn/checkbox/snice-checkbox.min.js +1 -1
- package/dist/cdn/chip/snice-chip.js +1 -1
- package/dist/cdn/chip/snice-chip.js.map +1 -1
- package/dist/cdn/chip/snice-chip.min.js +1 -1
- package/dist/cdn/chip/snice-chip.min.js.map +1 -1
- package/dist/cdn/code-block/snice-code-block.js +1 -1
- package/dist/cdn/code-block/snice-code-block.min.js +1 -1
- package/dist/cdn/color-display/snice-color-display.js +1 -1
- package/dist/cdn/color-display/snice-color-display.min.js +1 -1
- package/dist/cdn/color-picker/snice-color-picker.js +1 -1
- package/dist/cdn/color-picker/snice-color-picker.min.js +1 -1
- package/dist/cdn/command-palette/README.md +1 -1
- package/dist/cdn/command-palette/snice-command-palette.js +2 -2
- package/dist/cdn/command-palette/snice-command-palette.js.map +1 -1
- package/dist/cdn/command-palette/snice-command-palette.min.js +2 -2
- package/dist/cdn/command-palette/snice-command-palette.min.js.map +1 -1
- package/dist/cdn/comments/snice-comments.js +1 -1
- package/dist/cdn/comments/snice-comments.min.js +1 -1
- package/dist/cdn/countdown/snice-countdown.js +1 -1
- package/dist/cdn/countdown/snice-countdown.min.js +1 -1
- package/dist/cdn/cropper/snice-cropper.js +1 -1
- package/dist/cdn/cropper/snice-cropper.min.js +1 -1
- package/dist/cdn/data-card/snice-data-card.js +1 -1
- package/dist/cdn/data-card/snice-data-card.js.map +1 -1
- package/dist/cdn/data-card/snice-data-card.min.js +1 -1
- package/dist/cdn/data-card/snice-data-card.min.js.map +1 -1
- package/dist/cdn/date-picker/snice-date-picker.js +1 -1
- package/dist/cdn/date-picker/snice-date-picker.min.js +1 -1
- package/dist/cdn/date-range-picker/snice-date-range-picker.js +1 -1
- package/dist/cdn/date-range-picker/snice-date-range-picker.min.js +1 -1
- package/dist/cdn/date-time-picker/snice-date-time-picker.js +1 -1
- package/dist/cdn/date-time-picker/snice-date-time-picker.min.js +1 -1
- package/dist/cdn/diff/snice-diff.js +1 -1
- package/dist/cdn/diff/snice-diff.min.js +1 -1
- package/dist/cdn/divider/snice-divider.js +1 -1
- package/dist/cdn/divider/snice-divider.min.js +1 -1
- package/dist/cdn/doc/snice-doc.js +1 -1
- package/dist/cdn/doc/snice-doc.min.js +1 -1
- package/dist/cdn/draw/snice-draw.js +1 -1
- package/dist/cdn/draw/snice-draw.min.js +1 -1
- package/dist/cdn/drawer/snice-drawer.js +1 -1
- package/dist/cdn/drawer/snice-drawer.js.map +1 -1
- package/dist/cdn/drawer/snice-drawer.min.js +1 -1
- package/dist/cdn/drawer/snice-drawer.min.js.map +1 -1
- package/dist/cdn/empty-state/snice-empty-state.js +1 -1
- package/dist/cdn/empty-state/snice-empty-state.js.map +1 -1
- package/dist/cdn/empty-state/snice-empty-state.min.js +1 -1
- package/dist/cdn/empty-state/snice-empty-state.min.js.map +1 -1
- package/dist/cdn/estimate/snice-estimate.js +1 -1
- package/dist/cdn/estimate/snice-estimate.min.js +1 -1
- package/dist/cdn/file-gallery/snice-file-gallery.js +1 -1
- package/dist/cdn/file-gallery/snice-file-gallery.min.js +1 -1
- package/dist/cdn/file-upload/snice-file-upload.js +1 -1
- package/dist/cdn/file-upload/snice-file-upload.min.js +1 -1
- package/dist/cdn/flip-card/snice-flip-card.js +1 -1
- package/dist/cdn/flip-card/snice-flip-card.min.js +1 -1
- package/dist/cdn/flow/snice-flow.js +1 -1
- package/dist/cdn/flow/snice-flow.min.js +1 -1
- package/dist/cdn/form-layout/snice-form-layout.js +1 -1
- package/dist/cdn/form-layout/snice-form-layout.min.js +1 -1
- package/dist/cdn/funnel/snice-funnel.js +1 -1
- package/dist/cdn/funnel/snice-funnel.js.map +1 -1
- package/dist/cdn/funnel/snice-funnel.min.js +1 -1
- package/dist/cdn/funnel/snice-funnel.min.js.map +1 -1
- package/dist/cdn/gantt/snice-gantt.js +1 -1
- package/dist/cdn/gantt/snice-gantt.min.js +1 -1
- package/dist/cdn/gauge/snice-gauge.js +1 -1
- package/dist/cdn/gauge/snice-gauge.min.js +1 -1
- package/dist/cdn/grid/snice-grid.js +1 -1
- package/dist/cdn/grid/snice-grid.min.js +1 -1
- package/dist/cdn/heatmap/snice-heatmap.js +1 -1
- package/dist/cdn/heatmap/snice-heatmap.min.js +1 -1
- package/dist/cdn/image/snice-image.js +1 -1
- package/dist/cdn/image/snice-image.js.map +1 -1
- package/dist/cdn/image/snice-image.min.js +1 -1
- package/dist/cdn/image/snice-image.min.js.map +1 -1
- package/dist/cdn/input/snice-input.js +1 -1
- package/dist/cdn/input/snice-input.js.map +1 -1
- package/dist/cdn/input/snice-input.min.js +1 -1
- package/dist/cdn/input/snice-input.min.js.map +1 -1
- package/dist/cdn/invoice/README.md +1 -1
- package/dist/cdn/invoice/snice-invoice.js +2 -2
- package/dist/cdn/invoice/snice-invoice.js.map +1 -1
- package/dist/cdn/invoice/snice-invoice.min.js +2 -2
- package/dist/cdn/invoice/snice-invoice.min.js.map +1 -1
- package/dist/cdn/kanban/snice-kanban.js +1 -1
- package/dist/cdn/kanban/snice-kanban.min.js +1 -1
- package/dist/cdn/key-value/snice-key-value.js +1 -1
- package/dist/cdn/key-value/snice-key-value.min.js +1 -1
- package/dist/cdn/kpi/snice-kpi.js +1 -1
- package/dist/cdn/kpi/snice-kpi.js.map +1 -1
- package/dist/cdn/kpi/snice-kpi.min.js +1 -1
- package/dist/cdn/kpi/snice-kpi.min.js.map +1 -1
- package/dist/cdn/layout/snice-layout.js +1 -1
- package/dist/cdn/layout/snice-layout.min.js +1 -1
- package/dist/cdn/leaderboard/snice-leaderboard.js +1 -1
- package/dist/cdn/leaderboard/snice-leaderboard.min.js +1 -1
- package/dist/cdn/link/snice-link.js +1 -1
- package/dist/cdn/link/snice-link.min.js +1 -1
- package/dist/cdn/link-preview/snice-link-preview.js +1 -1
- package/dist/cdn/link-preview/snice-link-preview.min.js +1 -1
- package/dist/cdn/list/snice-list.js +1 -1
- package/dist/cdn/list/snice-list.js.map +1 -1
- package/dist/cdn/list/snice-list.min.js +1 -1
- package/dist/cdn/list/snice-list.min.js.map +1 -1
- package/dist/cdn/location/snice-location.js +1 -1
- package/dist/cdn/location/snice-location.js.map +1 -1
- package/dist/cdn/location/snice-location.min.js +1 -1
- package/dist/cdn/location/snice-location.min.js.map +1 -1
- package/dist/cdn/login/snice-login.js +3 -1
- package/dist/cdn/login/snice-login.js.map +1 -1
- package/dist/cdn/login/snice-login.min.js +1 -1
- package/dist/cdn/login/snice-login.min.js.map +1 -1
- package/dist/cdn/map/snice-map.js +1 -1
- package/dist/cdn/map/snice-map.min.js +1 -1
- package/dist/cdn/markdown/README.md +2 -2
- package/dist/cdn/markdown/snice-markdown.js +16 -3
- package/dist/cdn/markdown/snice-markdown.js.map +1 -1
- package/dist/cdn/markdown/snice-markdown.min.js +3 -3
- package/dist/cdn/markdown/snice-markdown.min.js.map +1 -1
- package/dist/cdn/masonry/snice-masonry.js +1 -1
- package/dist/cdn/masonry/snice-masonry.min.js +1 -1
- package/dist/cdn/menu/snice-menu.js +2 -2
- package/dist/cdn/menu/snice-menu.js.map +1 -1
- package/dist/cdn/menu/snice-menu.min.js +2 -2
- package/dist/cdn/menu/snice-menu.min.js.map +1 -1
- package/dist/cdn/message-strip/snice-message-strip.js +9 -9
- package/dist/cdn/message-strip/snice-message-strip.js.map +1 -1
- package/dist/cdn/message-strip/snice-message-strip.min.js +3 -3
- package/dist/cdn/message-strip/snice-message-strip.min.js.map +1 -1
- package/dist/cdn/metric-table/snice-metric-table.js +1 -1
- package/dist/cdn/metric-table/snice-metric-table.min.js +1 -1
- package/dist/cdn/modal/snice-modal.js +1 -1
- package/dist/cdn/modal/snice-modal.js.map +1 -1
- package/dist/cdn/modal/snice-modal.min.js +1 -1
- package/dist/cdn/modal/snice-modal.min.js.map +1 -1
- package/dist/cdn/music-player/snice-music-player.js +1 -1
- package/dist/cdn/music-player/snice-music-player.min.js +1 -1
- package/dist/cdn/nav/snice-nav.js +1 -1
- package/dist/cdn/nav/snice-nav.min.js +1 -1
- package/dist/cdn/network-graph/snice-network-graph.js +1 -1
- package/dist/cdn/network-graph/snice-network-graph.js.map +1 -1
- package/dist/cdn/network-graph/snice-network-graph.min.js +1 -1
- package/dist/cdn/network-graph/snice-network-graph.min.js.map +1 -1
- package/dist/cdn/notification-center/snice-notification-center.js +1 -1
- package/dist/cdn/notification-center/snice-notification-center.js.map +1 -1
- package/dist/cdn/notification-center/snice-notification-center.min.js +1 -1
- package/dist/cdn/notification-center/snice-notification-center.min.js.map +1 -1
- package/dist/cdn/order-tracker/snice-order-tracker.js +1 -1
- package/dist/cdn/order-tracker/snice-order-tracker.min.js +1 -1
- package/dist/cdn/org-chart/snice-org-chart.js +1 -1
- package/dist/cdn/org-chart/snice-org-chart.min.js +1 -1
- package/dist/cdn/pagination/snice-pagination.js +1 -1
- package/dist/cdn/pagination/snice-pagination.min.js +1 -1
- package/dist/cdn/paint/snice-paint.js +1 -1
- package/dist/cdn/paint/snice-paint.min.js +1 -1
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +1 -1
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.min.js +1 -1
- package/dist/cdn/permission-matrix/snice-permission-matrix.js +1 -1
- package/dist/cdn/permission-matrix/snice-permission-matrix.min.js +1 -1
- package/dist/cdn/podcast-player/snice-podcast-player.js +1 -1
- package/dist/cdn/podcast-player/snice-podcast-player.min.js +1 -1
- package/dist/cdn/popover/snice-popover.js +1 -1
- package/dist/cdn/popover/snice-popover.min.js +1 -1
- package/dist/cdn/pricing-table/snice-pricing-table.js +1 -1
- package/dist/cdn/pricing-table/snice-pricing-table.min.js +1 -1
- package/dist/cdn/product-card/snice-product-card.js +3 -3
- package/dist/cdn/product-card/snice-product-card.js.map +1 -1
- package/dist/cdn/product-card/snice-product-card.min.js +3 -3
- package/dist/cdn/product-card/snice-product-card.min.js.map +1 -1
- package/dist/cdn/progress/README.md +1 -1
- package/dist/cdn/progress/snice-progress.js +2 -2
- package/dist/cdn/progress/snice-progress.js.map +1 -1
- package/dist/cdn/progress/snice-progress.min.js +2 -2
- package/dist/cdn/progress/snice-progress.min.js.map +1 -1
- package/dist/cdn/progress-ring/snice-progress-ring.js +2 -2
- package/dist/cdn/progress-ring/snice-progress-ring.js.map +1 -1
- package/dist/cdn/progress-ring/snice-progress-ring.min.js +2 -2
- package/dist/cdn/progress-ring/snice-progress-ring.min.js.map +1 -1
- package/dist/cdn/qr-code/snice-qr-code.js +1 -1
- package/dist/cdn/qr-code/snice-qr-code.min.js +1 -1
- package/dist/cdn/qr-reader/snice-qr-reader.js +1 -1
- package/dist/cdn/qr-reader/snice-qr-reader.min.js +1 -1
- package/dist/cdn/radio/snice-radio.js +1 -1
- package/dist/cdn/radio/snice-radio.min.js +1 -1
- package/dist/cdn/range-slider/snice-range-slider.js +1 -1
- package/dist/cdn/range-slider/snice-range-slider.min.js +1 -1
- package/dist/cdn/rating/snice-rating.js +1 -1
- package/dist/cdn/rating/snice-rating.min.js +1 -1
- package/dist/cdn/receipt/snice-receipt.js +1 -1
- package/dist/cdn/receipt/snice-receipt.min.js +1 -1
- package/dist/cdn/recipe/snice-recipe.js +1 -1
- package/dist/cdn/recipe/snice-recipe.min.js +1 -1
- package/dist/cdn/runtime/README.md +2 -2
- package/dist/cdn/runtime/snice-runtime.esm.js +644 -172
- package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js +8 -8
- package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.js +644 -172
- package/dist/cdn/runtime/snice-runtime.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js +8 -8
- package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
- package/dist/cdn/sankey/snice-sankey.js +1 -1
- package/dist/cdn/sankey/snice-sankey.js.map +1 -1
- package/dist/cdn/sankey/snice-sankey.min.js +1 -1
- package/dist/cdn/sankey/snice-sankey.min.js.map +1 -1
- package/dist/cdn/segmented-control/snice-segmented-control.js +1 -1
- package/dist/cdn/segmented-control/snice-segmented-control.min.js +1 -1
- package/dist/cdn/select/snice-select.js +1 -1
- package/dist/cdn/select/snice-select.min.js +1 -1
- package/dist/cdn/skeleton/snice-skeleton.js +1 -1
- package/dist/cdn/skeleton/snice-skeleton.min.js +1 -1
- package/dist/cdn/slider/snice-slider.js +1 -1
- package/dist/cdn/slider/snice-slider.min.js +1 -1
- package/dist/cdn/sortable/snice-sortable.js +1 -1
- package/dist/cdn/sortable/snice-sortable.min.js +1 -1
- package/dist/cdn/sparkline/snice-sparkline.js +1 -1
- package/dist/cdn/sparkline/snice-sparkline.min.js +1 -1
- package/dist/cdn/spinner/snice-spinner.js +1 -1
- package/dist/cdn/spinner/snice-spinner.min.js +1 -1
- package/dist/cdn/split-button/snice-split-button.js +1 -1
- package/dist/cdn/split-button/snice-split-button.js.map +1 -1
- package/dist/cdn/split-button/snice-split-button.min.js +1 -1
- package/dist/cdn/split-button/snice-split-button.min.js.map +1 -1
- package/dist/cdn/split-pane/snice-split-pane.js +1 -1
- package/dist/cdn/split-pane/snice-split-pane.min.js +1 -1
- package/dist/cdn/spotlight/snice-spotlight.js +1 -1
- package/dist/cdn/spotlight/snice-spotlight.min.js +1 -1
- package/dist/cdn/stat-group/snice-stat-group.js +1 -1
- package/dist/cdn/stat-group/snice-stat-group.min.js +1 -1
- package/dist/cdn/step-input/snice-step-input.js +1 -1
- package/dist/cdn/step-input/snice-step-input.min.js +1 -1
- package/dist/cdn/stepper/snice-stepper.js +1 -1
- package/dist/cdn/stepper/snice-stepper.min.js +1 -1
- package/dist/cdn/switch/snice-switch.js +1 -1
- package/dist/cdn/switch/snice-switch.min.js +1 -1
- package/dist/cdn/table/README.md +1 -1
- package/dist/cdn/table/snice-table.js +14 -4
- package/dist/cdn/table/snice-table.js.map +1 -1
- package/dist/cdn/table/snice-table.min.js +14 -4
- package/dist/cdn/table/snice-table.min.js.map +1 -1
- package/dist/cdn/tabs/snice-tabs.js +1 -1
- package/dist/cdn/tabs/snice-tabs.min.js +1 -1
- package/dist/cdn/tag/snice-tag.js +1 -1
- package/dist/cdn/tag/snice-tag.min.js +1 -1
- package/dist/cdn/tag-input/snice-tag-input.js +2 -2
- package/dist/cdn/tag-input/snice-tag-input.js.map +1 -1
- package/dist/cdn/tag-input/snice-tag-input.min.js +2 -2
- package/dist/cdn/tag-input/snice-tag-input.min.js.map +1 -1
- package/dist/cdn/terminal/snice-terminal.js +1 -1
- package/dist/cdn/terminal/snice-terminal.min.js +1 -1
- package/dist/cdn/testimonial/snice-testimonial.js +1 -1
- package/dist/cdn/testimonial/snice-testimonial.min.js +1 -1
- package/dist/cdn/textarea/snice-textarea.js +1 -1
- package/dist/cdn/textarea/snice-textarea.min.js +1 -1
- package/dist/cdn/time-picker/README.md +1 -1
- package/dist/cdn/time-picker/snice-time-picker.js +18 -13
- package/dist/cdn/time-picker/snice-time-picker.js.map +1 -1
- package/dist/cdn/time-picker/snice-time-picker.min.js +3 -3
- package/dist/cdn/time-picker/snice-time-picker.min.js.map +1 -1
- package/dist/cdn/time-range-picker/snice-time-range-picker.js +1 -1
- package/dist/cdn/time-range-picker/snice-time-range-picker.min.js +1 -1
- package/dist/cdn/timeline/snice-timeline.js +1 -1
- package/dist/cdn/timeline/snice-timeline.js.map +1 -1
- package/dist/cdn/timeline/snice-timeline.min.js +1 -1
- package/dist/cdn/timeline/snice-timeline.min.js.map +1 -1
- package/dist/cdn/timer/snice-timer.js +1 -1
- package/dist/cdn/timer/snice-timer.min.js +1 -1
- package/dist/cdn/toast/snice-toast.js +1 -1
- package/dist/cdn/toast/snice-toast.js.map +1 -1
- package/dist/cdn/toast/snice-toast.min.js +1 -1
- package/dist/cdn/toast/snice-toast.min.js.map +1 -1
- package/dist/cdn/tooltip/snice-tooltip.js +1 -1
- package/dist/cdn/tooltip/snice-tooltip.min.js +1 -1
- package/dist/cdn/tree/snice-tree.js +1 -1
- package/dist/cdn/tree/snice-tree.min.js +1 -1
- package/dist/cdn/treemap/snice-treemap.js +1 -1
- package/dist/cdn/treemap/snice-treemap.js.map +1 -1
- package/dist/cdn/treemap/snice-treemap.min.js +1 -1
- package/dist/cdn/treemap/snice-treemap.min.js.map +1 -1
- package/dist/cdn/user-card/snice-user-card.js +1 -1
- package/dist/cdn/user-card/snice-user-card.min.js +1 -1
- package/dist/cdn/video-player/snice-video-player.js +1 -1
- package/dist/cdn/video-player/snice-video-player.min.js +1 -1
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +1 -1
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.min.js +1 -1
- package/dist/cdn/waterfall/snice-waterfall.js +1 -1
- package/dist/cdn/waterfall/snice-waterfall.min.js +1 -1
- package/dist/cdn/weather/snice-weather.js +1 -1
- package/dist/cdn/weather/snice-weather.min.js +1 -1
- package/dist/cdn/work-order/snice-work-order.js +1 -1
- package/dist/cdn/work-order/snice-work-order.min.js +1 -1
- package/dist/components/action-bar/snice-action-bar.js +1 -1
- package/dist/components/action-bar/snice-action-bar.js.map +1 -1
- package/dist/components/banner/snice-banner.js +1 -1
- package/dist/components/banner/snice-banner.js.map +1 -1
- package/dist/components/chat/snice-chat-message.d.ts +32 -0
- package/dist/components/chat/snice-chat-message.js +127 -0
- package/dist/components/chat/snice-chat-message.js.map +1 -0
- package/dist/components/chat/snice-chat-message.types.d.ts +30 -0
- package/dist/components/chat/snice-chat.d.ts +31 -4
- package/dist/components/chat/snice-chat.js +363 -90
- package/dist/components/chat/snice-chat.js.map +1 -1
- package/dist/components/chat/snice-chat.types.d.ts +34 -3
- package/dist/components/command-palette/snice-command-palette.js +1 -1
- package/dist/components/command-palette/snice-command-palette.js.map +1 -1
- package/dist/components/icons/index.d.ts +4 -0
- package/dist/components/icons/index.js +5 -1
- package/dist/components/icons/index.js.map +1 -1
- package/dist/components/invoice/snice-invoice.js +1 -1
- package/dist/components/invoice/snice-invoice.js.map +1 -1
- package/dist/components/login/snice-login.d.ts +2 -2
- package/dist/components/login/snice-login.js +2 -0
- package/dist/components/login/snice-login.js.map +1 -1
- package/dist/components/login/snice-login.types.d.ts +4 -0
- package/dist/components/markdown/snice-markdown.d.ts +1 -0
- package/dist/components/markdown/snice-markdown.js +15 -2
- package/dist/components/markdown/snice-markdown.js.map +1 -1
- package/dist/components/menu/snice-menu.js +1 -1
- package/dist/components/menu/snice-menu.js.map +1 -1
- package/dist/components/message-strip/snice-message-strip.d.ts +1 -1
- package/dist/components/message-strip/snice-message-strip.js +8 -8
- package/dist/components/message-strip/snice-message-strip.types.d.ts +1 -1
- package/dist/components/product-card/snice-product-card.js +2 -2
- package/dist/components/product-card/snice-product-card.js.map +1 -1
- package/dist/components/progress/snice-progress.js +1 -1
- package/dist/components/progress/snice-progress.js.map +1 -1
- package/dist/components/progress-ring/snice-progress-ring.js +1 -1
- package/dist/components/progress-ring/snice-progress-ring.js.map +1 -1
- package/dist/components/table/snice-table.js +13 -3
- package/dist/components/table/snice-table.js.map +1 -1
- package/dist/components/tag-input/snice-tag-input.js +1 -1
- package/dist/components/tag-input/snice-tag-input.js.map +1 -1
- package/dist/components/time-picker/snice-time-picker.js +17 -12
- package/dist/components/time-picker/snice-time-picker.js.map +1 -1
- package/dist/components/utils.d.ts +6 -0
- package/dist/components/utils.js +1 -1
- package/dist/components/utils.js.map +1 -1
- package/dist/element.d.ts +2 -1
- package/dist/events.d.ts +6 -0
- package/dist/index.cjs +641 -169
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +641 -169
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +641 -169
- package/dist/index.iife.js.map +1 -1
- package/dist/parts.d.ts +1 -8
- package/dist/react/SniceProvider.js +1 -1
- package/dist/react/SniceRouter.js +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/matchRoute.d.ts +1 -1
- package/dist/react/matchRoute.js +38 -4
- package/dist/react/matchRoute.js.map +1 -1
- package/dist/react/route-specificity.d.ts +1 -0
- package/dist/react/useRequestHandler.js +1 -1
- package/dist/render.d.ts +7 -0
- package/dist/route-specificity.d.ts +1 -0
- package/dist/symbols.cjs +14 -1
- package/dist/symbols.cjs.map +1 -1
- package/dist/symbols.d.ts +3 -0
- package/dist/symbols.esm.js +12 -2
- package/dist/symbols.esm.js.map +1 -1
- package/dist/transitions.cjs +1 -1
- package/dist/transitions.esm.js +1 -1
- package/dist/types/context.d.ts +5 -2
- package/dist/types/dispatch-options.d.ts +7 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/on-options.d.ts +14 -0
- package/dist/types/watch-options.d.ts +9 -0
- package/dist/utils.d.ts +26 -0
- package/docs/ai/DEVELOPMENT.md +1 -1
- package/docs/ai/README.md +11 -0
- package/docs/ai/api.md +24 -5
- package/docs/ai/cdn.md +31 -0
- package/docs/ai/components/chat.md +57 -1
- package/docs/ai/components/message-strip.md +3 -3
- package/docs/ai/decorators.md +44 -5
- package/docs/cdn.md +56 -0
- package/docs/components/chat.md +108 -2
- package/docs/components/message-strip.md +8 -8
- package/docs/elements.md +21 -0
- package/docs/events.md +88 -0
- package/docs/superpowers/specs/2026-05-26-chat-styling-design.md +155 -0
- package/docs/superpowers/specs/2026-05-27-chat-theming-REMAINING.md +83 -0
- package/docs/superpowers/specs/2026-06-09-chat-custom-content-design.md +233 -0
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice
|
|
2
|
+
* snice v6.0.0
|
|
3
3
|
* A decorator-driven web component library with differential rendering, routing, controllers, and 130+ ready-made UI components. Use as much or as little as you want. Zero dependencies, works anywhere.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
7
7
|
/*!
|
|
8
|
-
* snice v5.2.
|
|
8
|
+
* snice v5.2.3
|
|
9
9
|
* A decorator-driven web component library with differential rendering, routing, controllers, and 130+ ready-made UI components. Use as much or as little as you want. Zero dependencies, works anywhere.
|
|
10
10
|
* (c) 2024
|
|
11
11
|
* Released under the MIT License.
|
|
@@ -39,6 +39,12 @@ const CHANNEL_HANDLERS = getSymbol('channel-handlers');
|
|
|
39
39
|
// Internal element state symbols
|
|
40
40
|
const READY_PROMISE = getSymbol('ready-promise');
|
|
41
41
|
const READY_RESOLVE = getSymbol('ready-resolve');
|
|
42
|
+
// Set when a render is requested but dropped because the element was
|
|
43
|
+
// disconnected; consumed on reconnect to replay exactly the missed render.
|
|
44
|
+
const PENDING_RECONNECT_RENDER = getSymbol('pending-reconnect-render');
|
|
45
|
+
// Per-element synchronous render-nesting depth, used to cap runaway re-entry
|
|
46
|
+
// without a shared global counter that one element could exhaust for others.
|
|
47
|
+
const RENDER_DEPTH = getSymbol('render-depth');
|
|
42
48
|
const CONTROLLER = getSymbol('controller');
|
|
43
49
|
const INITIALIZED = getSymbol('initialized');
|
|
44
50
|
// Controller symbols
|
|
@@ -110,6 +116,10 @@ const ADOPTED_METHODS = getSymbol('adopted-methods');
|
|
|
110
116
|
// Controller attach-abort — set to an AbortController while attachController
|
|
111
117
|
// is awaiting element.ready, so detachController can short-circuit the wait.
|
|
112
118
|
const CONTROLLER_ABORT = getSymbol('controller-abort');
|
|
119
|
+
// Set on a controller instance once it has gotten past element.ready and is
|
|
120
|
+
// being attached, so detachController knows not to run detach() on a controller
|
|
121
|
+
// whose attach() was aborted before it ever ran.
|
|
122
|
+
const CONTROLLER_ATTACHED = getSymbol('controller-attached');
|
|
113
123
|
const PROPERTY_DEFINERS = getSymbol('property-definers');
|
|
114
124
|
|
|
115
125
|
/**
|
|
@@ -230,11 +240,23 @@ class Context {
|
|
|
230
240
|
this[REGISTERED_ELEMENTS_SET].add(element);
|
|
231
241
|
}
|
|
232
242
|
/**
|
|
233
|
-
* Unregister an element from receiving context updates
|
|
243
|
+
* Unregister an element from receiving context updates. With a methodName,
|
|
244
|
+
* removes only that one handler (so a `once` handler tearing itself down
|
|
245
|
+
* doesn't kill the element's other @context handlers); without one, removes
|
|
246
|
+
* the element entirely (used on disconnect).
|
|
234
247
|
* @internal Used by @context decorator cleanup
|
|
235
248
|
*/
|
|
236
|
-
[CONTEXT_UNREGISTER](element) {
|
|
237
|
-
this[REGISTERED_ELEMENTS]
|
|
249
|
+
[CONTEXT_UNREGISTER](element, methodName) {
|
|
250
|
+
const map = this[REGISTERED_ELEMENTS];
|
|
251
|
+
if (methodName !== undefined) {
|
|
252
|
+
const names = map.get(element);
|
|
253
|
+
if (!names)
|
|
254
|
+
return;
|
|
255
|
+
names.delete(methodName);
|
|
256
|
+
if (names.size > 0)
|
|
257
|
+
return; // other handlers remain — keep the element
|
|
258
|
+
}
|
|
259
|
+
map.delete(element);
|
|
238
260
|
this[REGISTERED_ELEMENTS_SET].delete(element);
|
|
239
261
|
}
|
|
240
262
|
/**
|
|
@@ -399,11 +421,23 @@ function parseAttributeValue(attributeValue, propertyOptions, currentValue, init
|
|
|
399
421
|
* @param initialValue - The default value assigned to the property field (used for type detection)
|
|
400
422
|
* @returns The string representation for the HTML attribute, or null if value should remove attribute
|
|
401
423
|
*/
|
|
424
|
+
/**
|
|
425
|
+
* Default property dirty-check: has the value changed? Like `!==`, but treats
|
|
426
|
+
* NaN → NaN as unchanged (NaN !== NaN would otherwise re-fire forever).
|
|
427
|
+
*/
|
|
428
|
+
function notEqual(value, old) {
|
|
429
|
+
// Changed unless strictly equal, or both are NaN (each self-unequal).
|
|
430
|
+
return value !== old && (value === value || old === old);
|
|
431
|
+
}
|
|
402
432
|
/**
|
|
403
433
|
* Extract attribute name from property options
|
|
404
434
|
*/
|
|
405
435
|
function getAttrName(opts, propName) {
|
|
406
|
-
|
|
436
|
+
// Lowercase always: the DOM stores attribute names lowercase, so an explicit
|
|
437
|
+
// camelCase `attribute:` would otherwise never match observedAttributes and
|
|
438
|
+
// attributeChangedCallback would never fire. get/setAttribute are already
|
|
439
|
+
// case-insensitive, so this is safe for every caller.
|
|
440
|
+
return (typeof opts.attribute === 'string' ? opts.attribute : propName).toLowerCase();
|
|
407
441
|
}
|
|
408
442
|
/**
|
|
409
443
|
* Escape HTML text-node content. Use for text that will be interpolated
|
|
@@ -550,6 +584,42 @@ function invokeWatchers(instance, constructor, propName, oldValue, newValue) {
|
|
|
550
584
|
}
|
|
551
585
|
}
|
|
552
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* Fire the one-time initialization call for `@watch(..., { immediate: true })`
|
|
589
|
+
* handlers. Runs once per declared property after the element is initialized,
|
|
590
|
+
* passing the effective initial value with `oldValue` undefined. Non-immediate
|
|
591
|
+
* watchers are untouched — they only react to later changes.
|
|
592
|
+
*/
|
|
593
|
+
function invokeImmediateWatchers(instance, constructor) {
|
|
594
|
+
const watchers = constructor[PROPERTY_WATCHERS];
|
|
595
|
+
const properties = constructor[PROPERTIES];
|
|
596
|
+
if (!watchers || !properties)
|
|
597
|
+
return;
|
|
598
|
+
for (const [propName, propOptions] of properties) {
|
|
599
|
+
// A watcher may be registered under the JS name, the explicit `attribute:`
|
|
600
|
+
// name, or the `*` wildcard; fire each immediate one exactly once, keyed to
|
|
601
|
+
// this property's current value.
|
|
602
|
+
const keys = [propName];
|
|
603
|
+
const attrName = propOptions && typeof propOptions.attribute === 'string' ? propOptions.attribute : null;
|
|
604
|
+
if (attrName && attrName !== propName)
|
|
605
|
+
keys.push(attrName);
|
|
606
|
+
keys.push('*');
|
|
607
|
+
for (const key of keys) {
|
|
608
|
+
if (!watchers.has(key))
|
|
609
|
+
continue;
|
|
610
|
+
for (const w of watchers.get(key)) {
|
|
611
|
+
if (!w.immediate)
|
|
612
|
+
continue;
|
|
613
|
+
try {
|
|
614
|
+
w.method.call(instance, undefined, instance[propName], propName);
|
|
615
|
+
}
|
|
616
|
+
catch (e) {
|
|
617
|
+
console.error(`Error in @watch('${key}') method ${w.methodName}:`, e);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
553
623
|
/**
|
|
554
624
|
* Create a debounced version of a function
|
|
555
625
|
*/
|
|
@@ -583,6 +653,41 @@ function createThrottled(fn, delay) {
|
|
|
583
653
|
}
|
|
584
654
|
};
|
|
585
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* Resolve a scope option to a concrete EventTarget.
|
|
658
|
+
*
|
|
659
|
+
* - undefined → host element (default behavior)
|
|
660
|
+
* - 'global' → `document`
|
|
661
|
+
* - string → `host.closest(selector)` (nearest matching ancestor)
|
|
662
|
+
* - EventTarget instance → that target directly
|
|
663
|
+
* - resolver function → called with host as `this`, returns target or null
|
|
664
|
+
*
|
|
665
|
+
* Returns null when the scope cannot be resolved (e.g. selector matches no
|
|
666
|
+
* ancestor, resolver returns null). Caller decides how to handle null —
|
|
667
|
+
* typically warn in dev and skip listener attachment.
|
|
668
|
+
*/
|
|
669
|
+
function resolveScope(host, scope) {
|
|
670
|
+
if (scope === undefined || scope === null)
|
|
671
|
+
return host;
|
|
672
|
+
if (scope === 'global')
|
|
673
|
+
return typeof document !== 'undefined' ? document : null;
|
|
674
|
+
if (typeof scope === 'function') {
|
|
675
|
+
try {
|
|
676
|
+
return scope.call(host) || null;
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
return null;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (typeof scope === 'string') {
|
|
683
|
+
return host.closest?.(scope) || null;
|
|
684
|
+
}
|
|
685
|
+
// Direct EventTarget instance.
|
|
686
|
+
if (scope && typeof scope.addEventListener === 'function') {
|
|
687
|
+
return scope;
|
|
688
|
+
}
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
586
691
|
function valueToAttribute(value, propertyOptions, initialValue) {
|
|
587
692
|
// Handle null/undefined/false values and empty arrays
|
|
588
693
|
if (value === null || value === undefined || value === false ||
|
|
@@ -1000,13 +1105,11 @@ function request(requestName, options) {
|
|
|
1000
1105
|
return finalValue;
|
|
1001
1106
|
}
|
|
1002
1107
|
catch (error) {
|
|
1003
|
-
//
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
throw generatorError;
|
|
1009
|
-
}
|
|
1108
|
+
// Drive the generator's own catch block and return whatever it recovers
|
|
1109
|
+
// with (e.g. a cached fallback). If the generator re-throws instead,
|
|
1110
|
+
// generator.throw rejects and the error propagates to the caller.
|
|
1111
|
+
const { value: recovered } = await generator.throw(error);
|
|
1112
|
+
return recovered;
|
|
1010
1113
|
}
|
|
1011
1114
|
}; // Close actualRequest function
|
|
1012
1115
|
// Debounce
|
|
@@ -1030,9 +1133,21 @@ function request(requestName, options) {
|
|
|
1030
1133
|
const now = Date.now();
|
|
1031
1134
|
const remaining = options.throttle - (now - throttleLastCall);
|
|
1032
1135
|
if (remaining <= 0) {
|
|
1136
|
+
// Cancelling the scheduled trailing call must not orphan the promises
|
|
1137
|
+
// queued for it — settle them with this fresh result. Also reset
|
|
1138
|
+
// throttleTimeout so later within-window calls don't attach to a dead
|
|
1139
|
+
// timer and hang.
|
|
1033
1140
|
clearTimeout(throttleTimeout);
|
|
1141
|
+
throttleTimeout = null;
|
|
1034
1142
|
throttleLastCall = now;
|
|
1035
|
-
|
|
1143
|
+
const orphaned = throttleTrailingResolvers.splice(0);
|
|
1144
|
+
const p = actualRequest();
|
|
1145
|
+
if (orphaned.length) {
|
|
1146
|
+
p.then((r) => { for (const x of orphaned)
|
|
1147
|
+
x.resolve(r); }, (e) => { for (const x of orphaned)
|
|
1148
|
+
x.reject(e); });
|
|
1149
|
+
}
|
|
1150
|
+
return p;
|
|
1036
1151
|
}
|
|
1037
1152
|
// Throttle: already has pending trailing call — attach to it
|
|
1038
1153
|
if (throttleTimeout) {
|
|
@@ -1133,10 +1248,20 @@ function setupResponseHandlers(instance, element) {
|
|
|
1133
1248
|
const now = Date.now();
|
|
1134
1249
|
const remaining = handler.options.throttle - (now - throttleLastCall);
|
|
1135
1250
|
if (remaining <= 0) {
|
|
1251
|
+
// Same as the @request side: settle any queued trailing callers with
|
|
1252
|
+
// this fresh result rather than orphaning their promises.
|
|
1136
1253
|
clearTimeout(throttleTimeout);
|
|
1137
1254
|
throttleTimeout = null;
|
|
1138
1255
|
throttleLastCall = now;
|
|
1139
|
-
|
|
1256
|
+
const orphaned = throttleTrailingResolvers.splice(0);
|
|
1257
|
+
throttleLatestArgs = null;
|
|
1258
|
+
const result = originalMethod(...args);
|
|
1259
|
+
if (orphaned.length) {
|
|
1260
|
+
Promise.resolve(result).then((r) => { for (const x of orphaned)
|
|
1261
|
+
x.resolve(r); }, (e) => { for (const x of orphaned)
|
|
1262
|
+
x.reject(e); });
|
|
1263
|
+
}
|
|
1264
|
+
return result;
|
|
1140
1265
|
}
|
|
1141
1266
|
// Remember the LATEST args so the trailing call uses fresh input,
|
|
1142
1267
|
// and queue every suppressed caller's resolver so they all receive
|
|
@@ -1243,7 +1368,10 @@ function css(strings, ...values) {
|
|
|
1243
1368
|
// Combine strings and values into final CSS text
|
|
1244
1369
|
let cssText = strings[0];
|
|
1245
1370
|
for (let i = 0; i < values.length; i++) {
|
|
1246
|
-
|
|
1371
|
+
// Inline a nested css`` result by its text so style fragments compose;
|
|
1372
|
+
// String() on a CSSResult object would yield "[object Object]".
|
|
1373
|
+
const value = values[i];
|
|
1374
|
+
cssText += (isCSSResult(value) ? value.cssText : String(value)) + strings[i + 1];
|
|
1247
1375
|
}
|
|
1248
1376
|
const result = {
|
|
1249
1377
|
_$litType$: CSS_RESULT,
|
|
@@ -1394,6 +1522,20 @@ class Template {
|
|
|
1394
1522
|
const attrStrings = value.split(marker);
|
|
1395
1523
|
// Number of expressions = number of markers = attrStrings.length - 1
|
|
1396
1524
|
const expressionCount = attrStrings.length - 1;
|
|
1525
|
+
// Property/boolean/event bindings take a SINGLE expression — they
|
|
1526
|
+
// can't concatenate static text (a `.prop` is an object, not a
|
|
1527
|
+
// string). They consume the first expression; any static text or
|
|
1528
|
+
// extra expressions are ignored. But the value index must still
|
|
1529
|
+
// advance by the true marker count, or every later binding shifts.
|
|
1530
|
+
const isSingleExpressionBinding = /^@@?|^[.?]/.test(originalName);
|
|
1531
|
+
if (isSingleExpressionBinding) {
|
|
1532
|
+
const hasStaticText = attrStrings.some((s) => s !== '');
|
|
1533
|
+
if (expressionCount > 1 || hasStaticText) {
|
|
1534
|
+
console.warn(`snice: binding "${originalName}" takes a single expression; ` +
|
|
1535
|
+
`static text and extra interpolations are ignored. ` +
|
|
1536
|
+
`Use a plain attribute for string interpolation.`);
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1397
1539
|
if (originalName.startsWith('@@')) {
|
|
1398
1540
|
// Escaped event binding for events with @ in the name (e.g., @@snice/event -> @snice/event)
|
|
1399
1541
|
this.parts.push({
|
|
@@ -1402,7 +1544,7 @@ class Template {
|
|
|
1402
1544
|
name: originalName.slice(1), // Keep the @ in the event name
|
|
1403
1545
|
element
|
|
1404
1546
|
});
|
|
1405
|
-
partIndex +=
|
|
1547
|
+
partIndex += expressionCount;
|
|
1406
1548
|
}
|
|
1407
1549
|
else if (originalName.startsWith('@')) {
|
|
1408
1550
|
// Event binding (single value only)
|
|
@@ -1412,7 +1554,7 @@ class Template {
|
|
|
1412
1554
|
name: originalName.slice(1),
|
|
1413
1555
|
element
|
|
1414
1556
|
});
|
|
1415
|
-
partIndex +=
|
|
1557
|
+
partIndex += expressionCount;
|
|
1416
1558
|
}
|
|
1417
1559
|
else if (originalName.startsWith('.')) {
|
|
1418
1560
|
// Property binding (single value only)
|
|
@@ -1422,7 +1564,7 @@ class Template {
|
|
|
1422
1564
|
name: originalName.slice(1),
|
|
1423
1565
|
element
|
|
1424
1566
|
});
|
|
1425
|
-
partIndex +=
|
|
1567
|
+
partIndex += expressionCount;
|
|
1426
1568
|
}
|
|
1427
1569
|
else if (originalName.startsWith('?')) {
|
|
1428
1570
|
// Boolean attribute (single value only)
|
|
@@ -1432,7 +1574,7 @@ class Template {
|
|
|
1432
1574
|
name: originalName.slice(1),
|
|
1433
1575
|
element
|
|
1434
1576
|
});
|
|
1435
|
-
partIndex +=
|
|
1577
|
+
partIndex += expressionCount;
|
|
1436
1578
|
}
|
|
1437
1579
|
else {
|
|
1438
1580
|
// Regular attribute - supports multiple interpolations
|
|
@@ -1907,7 +2049,13 @@ class NodePart extends Part {
|
|
|
1907
2049
|
* Commit unsafe HTML string
|
|
1908
2050
|
*/
|
|
1909
2051
|
_commitUnsafeHTML(value) {
|
|
1910
|
-
//
|
|
2052
|
+
// Can't diff arbitrary HTML, but when the STRING is unchanged the DOM is
|
|
2053
|
+
// already correct — skip the clear+reparse so live state (typed input
|
|
2054
|
+
// values, focus, scroll) inside the block survives unrelated re-renders.
|
|
2055
|
+
const prev = this._committedValue;
|
|
2056
|
+
if (isUnsafeHTML(prev) && prev.html === value.html) {
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
1911
2059
|
this._clear();
|
|
1912
2060
|
const temp = document.createElement('template');
|
|
1913
2061
|
temp.innerHTML = value.html;
|
|
@@ -2092,7 +2240,9 @@ class EventPart extends Part {
|
|
|
2092
2240
|
// Supports both dot notation (@keydown.enter) and colon notation (@keydown:Enter) to match @on decorator
|
|
2093
2241
|
// Only parse colons for keyboard events, not custom events
|
|
2094
2242
|
const isKeyboardEvent = ['keydown', 'keyup', 'keypress'].includes(eventName.split('.')[0].split(':')[0]);
|
|
2095
|
-
|
|
2243
|
+
// Only keyboard events split on `.`/`:` into a key filter — a custom event
|
|
2244
|
+
// name may legitimately contain a dot (e.g. `app.ready`) and must be kept whole.
|
|
2245
|
+
const dotIndex = isKeyboardEvent ? eventName.indexOf('.') : -1;
|
|
2096
2246
|
const colonIndex = isKeyboardEvent ? eventName.indexOf(':') : -1;
|
|
2097
2247
|
// Use whichever delimiter comes first (dot or colon)
|
|
2098
2248
|
const delimiterIndex = dotIndex > 0 && colonIndex > 0
|
|
@@ -2106,6 +2256,7 @@ class EventPart extends Part {
|
|
|
2106
2256
|
}
|
|
2107
2257
|
else {
|
|
2108
2258
|
this.eventName = eventName;
|
|
2259
|
+
warnIfModifierMisuse(eventName);
|
|
2109
2260
|
}
|
|
2110
2261
|
}
|
|
2111
2262
|
commit(value) {
|
|
@@ -2131,17 +2282,19 @@ class EventPart extends Part {
|
|
|
2131
2282
|
}
|
|
2132
2283
|
if (typeof value !== 'function')
|
|
2133
2284
|
return;
|
|
2134
|
-
// Auto-bind to host element (the custom element with shadow root)
|
|
2135
|
-
if (!this.host) {
|
|
2136
|
-
const rootNode = this.element.getRootNode();
|
|
2137
|
-
this.host = rootNode.host || null;
|
|
2138
|
-
}
|
|
2139
|
-
const context = this.host || null;
|
|
2140
2285
|
const keyFilter = this.keyFilter;
|
|
2141
2286
|
this.listener = ((event) => {
|
|
2142
2287
|
if (keyFilter && !matchesKeyboardFilter(event, keyFilter))
|
|
2143
2288
|
return;
|
|
2144
|
-
|
|
2289
|
+
// Resolve the host (the custom element owning the shadow root) at dispatch
|
|
2290
|
+
// time, not bind time: a binding inside an initially-hidden <if>/<case>
|
|
2291
|
+
// branch commits while off-DOM, where getRootNode() has no host — caching
|
|
2292
|
+
// that null permanently would call the handler with `this = null` once the
|
|
2293
|
+
// branch is shown. By the time an event fires, the element is in the DOM.
|
|
2294
|
+
if (!this.host) {
|
|
2295
|
+
this.host = this.element.getRootNode().host || null;
|
|
2296
|
+
}
|
|
2297
|
+
value.call(this.host || null, event);
|
|
2145
2298
|
});
|
|
2146
2299
|
this.element.addEventListener(this.eventName, this.listener);
|
|
2147
2300
|
}
|
|
@@ -2160,6 +2313,25 @@ class EventPart extends Part {
|
|
|
2160
2313
|
* "ctrl+shift+s" -> { key: "s", ctrl: true, shift: true }
|
|
2161
2314
|
* "~enter" -> { key: "Enter", anyModifiers: true }
|
|
2162
2315
|
*/
|
|
2316
|
+
// once/preventDefault/stopPropagation/capture/passive are @on() OPTIONS, not
|
|
2317
|
+
// template modifiers. A dotted non-keyboard event whose suffix is one of these
|
|
2318
|
+
// is almost certainly a mistake (e.g. `@click.once`) — it binds a listener for
|
|
2319
|
+
// an event type the DOM never dispatches. Warn instead of failing silently. A
|
|
2320
|
+
// legitimate custom event name like `app.ready` won't match and stays quiet.
|
|
2321
|
+
const MODIFIER_WORDS = new Set([
|
|
2322
|
+
'once', 'prevent', 'preventdefault', 'stop', 'stoppropagation', 'capture', 'passive',
|
|
2323
|
+
]);
|
|
2324
|
+
function warnIfModifierMisuse(eventName) {
|
|
2325
|
+
const dot = eventName.lastIndexOf('.');
|
|
2326
|
+
if (dot < 0)
|
|
2327
|
+
return;
|
|
2328
|
+
const suffix = eventName.slice(dot + 1).toLowerCase();
|
|
2329
|
+
if (!MODIFIER_WORDS.has(suffix))
|
|
2330
|
+
return;
|
|
2331
|
+
console.warn(`snice: "@${eventName}" is not a valid modifier — once/preventDefault/` +
|
|
2332
|
+
`stopPropagation/capture are @on() options, not template modifiers. ` +
|
|
2333
|
+
`This registers a listener for event "${eventName}", which never fires.`);
|
|
2334
|
+
}
|
|
2163
2335
|
function parseKeyboardFilter(spec) {
|
|
2164
2336
|
// Handle ~ prefix for matching regardless of modifiers
|
|
2165
2337
|
const anyModifiers = spec.startsWith('~');
|
|
@@ -2262,9 +2434,14 @@ class ConditionalIfPart extends Part {
|
|
|
2262
2434
|
// Insert markers where <if> is
|
|
2263
2435
|
parent.insertBefore(this.startNode, ifElement);
|
|
2264
2436
|
parent.insertBefore(this.endNode, ifElement.nextSibling);
|
|
2265
|
-
//
|
|
2437
|
+
// Park <if>'s children in an off-DOM fragment immediately, rather than
|
|
2438
|
+
// leaving them in the live template fragment. Otherwise a branch that is
|
|
2439
|
+
// hidden on first render would still connect its children (when the
|
|
2440
|
+
// template mounts) and then disconnect them (on the first commit) — firing
|
|
2441
|
+
// mount side effects spuriously. commit(true) inserts them when shown.
|
|
2442
|
+
this.childFragment = document.createDocumentFragment();
|
|
2266
2443
|
while (ifElement.firstChild) {
|
|
2267
|
-
|
|
2444
|
+
this.childFragment.appendChild(ifElement.firstChild);
|
|
2268
2445
|
}
|
|
2269
2446
|
// Remove the <if> element from DOM
|
|
2270
2447
|
parent.removeChild(ifElement);
|
|
@@ -2345,16 +2522,24 @@ class ConditionalCasePart extends Part {
|
|
|
2345
2522
|
parent.removeChild(caseElement);
|
|
2346
2523
|
}
|
|
2347
2524
|
commit(value) {
|
|
2348
|
-
|
|
2525
|
+
// Dirty-check on the SELECTED BRANCH, not the raw value. A value whose type
|
|
2526
|
+
// changed but stringifies the same (number 1 vs string '1' — e.g. a numeric
|
|
2527
|
+
// default replaced by a router param, which is always a string) selects the
|
|
2528
|
+
// same branch, so tearing it down and re-inserting it would needlessly
|
|
2529
|
+
// restart media/animations and lose focus inside it.
|
|
2530
|
+
const valueStr = String(value);
|
|
2531
|
+
const hasBranch = this.branches.has(valueStr);
|
|
2532
|
+
const nextKey = hasBranch ? valueStr : (this.defaultBranch ? '__default__' : null);
|
|
2533
|
+
if (this.currentKey === nextKey) {
|
|
2534
|
+
this.value = value;
|
|
2349
2535
|
return;
|
|
2536
|
+
}
|
|
2350
2537
|
this.value = value;
|
|
2351
|
-
const valueStr = String(value);
|
|
2352
2538
|
// Move current content back to its fragment
|
|
2353
2539
|
this._collectCurrent();
|
|
2354
2540
|
// Insert matching branch
|
|
2355
|
-
const hasBranch = this.branches.has(valueStr);
|
|
2356
2541
|
const matchingFragment = hasBranch ? this.branches.get(valueStr) : this.defaultBranch;
|
|
2357
|
-
this.currentKey =
|
|
2542
|
+
this.currentKey = nextKey;
|
|
2358
2543
|
if (matchingFragment && matchingFragment.hasChildNodes()) {
|
|
2359
2544
|
this.startNode.parentNode.insertBefore(matchingFragment, this.endNode);
|
|
2360
2545
|
}
|
|
@@ -2532,7 +2717,9 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2532
2717
|
// Supports both dot notation (@keydown.enter) and colon notation (@keydown:Enter)
|
|
2533
2718
|
// Only parse colons for keyboard events, not custom events
|
|
2534
2719
|
const isKeyboardEvent = ['keydown', 'keyup', 'keypress'].includes(handler.eventName.split('.')[0].split(':')[0]);
|
|
2535
|
-
|
|
2720
|
+
// Only keyboard events split on `.`/`:` into a key filter — a custom event
|
|
2721
|
+
// name may legitimately contain a dot (e.g. `app.ready`) and must be kept whole.
|
|
2722
|
+
const dotIndex = isKeyboardEvent ? handler.eventName.indexOf('.') : -1;
|
|
2536
2723
|
const colonIndex = isKeyboardEvent ? handler.eventName.indexOf(':') : -1;
|
|
2537
2724
|
const delimiterIndex = dotIndex > 0 && colonIndex > 0
|
|
2538
2725
|
? Math.min(dotIndex, colonIndex)
|
|
@@ -2543,6 +2730,8 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2543
2730
|
const keyModifier = delimiterIndex > 0
|
|
2544
2731
|
? handler.eventName.substring(delimiterIndex + 1)
|
|
2545
2732
|
: null;
|
|
2733
|
+
if (delimiterIndex <= 0)
|
|
2734
|
+
warnIfModifierMisuse(handler.eventName);
|
|
2546
2735
|
// Apply debounce (takes precedence over throttle)
|
|
2547
2736
|
if (handlerOptions.debounce && handlerOptions.debounce > 0) {
|
|
2548
2737
|
boundMethod = createDebounced(boundMethod, handlerOptions.debounce);
|
|
@@ -2569,10 +2758,27 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2569
2758
|
};
|
|
2570
2759
|
// Apply key modifier wrapper
|
|
2571
2760
|
const keyModifierMethod = createKeyModifierHandler(boundMethod);
|
|
2761
|
+
// Resolve scope: explicit `scope` option redirects the listener target
|
|
2762
|
+
// to document / ancestor / arbitrary EventTarget. Default = host element.
|
|
2763
|
+
const hasExplicitScope = handlerOptions.scope !== undefined;
|
|
2764
|
+
let scopedTarget = null;
|
|
2765
|
+
if (hasExplicitScope) {
|
|
2766
|
+
scopedTarget = resolveScope(targetElement, handlerOptions.scope);
|
|
2767
|
+
if (!scopedTarget) {
|
|
2768
|
+
// Dev warning — listener silently dropped when scope cannot resolve.
|
|
2769
|
+
// Skip attachment so we don't bind to the wrong target.
|
|
2770
|
+
console.warn(`[snice/@on] scope did not resolve for "${handler.eventName}" on ${handler.methodName} — listener skipped.`);
|
|
2771
|
+
continue;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2572
2774
|
// Main event handler with error handling and event delegation
|
|
2573
2775
|
if (handler.selector) {
|
|
2574
|
-
// Delegated event handling
|
|
2575
|
-
|
|
2776
|
+
// Delegated event handling.
|
|
2777
|
+
// - Default eventRoot: shadow root if present, else host element.
|
|
2778
|
+
// - With explicit scope: attach to the scoped target instead.
|
|
2779
|
+
const eventRoot = hasExplicitScope
|
|
2780
|
+
? scopedTarget
|
|
2781
|
+
: (targetElement.shadowRoot || targetElement);
|
|
2576
2782
|
const delegatedHandler = (event) => {
|
|
2577
2783
|
const target = event.target;
|
|
2578
2784
|
const matchingElement = (target.matches && target.matches(handler.selector) && target) ||
|
|
@@ -2612,10 +2818,11 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2612
2818
|
});
|
|
2613
2819
|
}
|
|
2614
2820
|
else {
|
|
2615
|
-
// Direct event handling
|
|
2616
|
-
//
|
|
2617
|
-
//
|
|
2618
|
-
|
|
2821
|
+
// Direct event handling.
|
|
2822
|
+
// - Default: shadow root + host element (so events inside shadow and on
|
|
2823
|
+
// the host itself both fire). A per-handler Symbol dedupes.
|
|
2824
|
+
// - With explicit scope: attach to the scoped target ONLY (no duplication).
|
|
2825
|
+
const shadowRoot = hasExplicitScope ? null : targetElement.shadowRoot;
|
|
2619
2826
|
// Per-handler private Symbol so dedup is scoped to THIS handler's two
|
|
2620
2827
|
// listeners (shadowRoot + host). Using Symbol.for() with the method name
|
|
2621
2828
|
// would collide across components that share a method name (e.g. a parent
|
|
@@ -2643,6 +2850,16 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2643
2850
|
once: handlerOptions.once || false,
|
|
2644
2851
|
passive: handlerOptions.passive || false,
|
|
2645
2852
|
};
|
|
2853
|
+
if (hasExplicitScope) {
|
|
2854
|
+
scopedTarget.addEventListener(baseEventName, wrappedMethod, listenerOptions);
|
|
2855
|
+
instance[CLEANUP].events.push({
|
|
2856
|
+
target: scopedTarget,
|
|
2857
|
+
eventName: baseEventName,
|
|
2858
|
+
handler: wrappedMethod,
|
|
2859
|
+
options: listenerOptions,
|
|
2860
|
+
});
|
|
2861
|
+
continue;
|
|
2862
|
+
}
|
|
2646
2863
|
if (shadowRoot) {
|
|
2647
2864
|
// Listen on shadow root for events inside shadow DOM
|
|
2648
2865
|
shadowRoot.addEventListener(baseEventName, wrappedMethod, listenerOptions);
|
|
@@ -2805,11 +3022,25 @@ async function attachController(element, controllerName) {
|
|
|
2805
3022
|
}),
|
|
2806
3023
|
]);
|
|
2807
3024
|
}
|
|
3025
|
+
catch (error) {
|
|
3026
|
+
// Attach never got past `ready` (aborted or deadline). Roll back the stored
|
|
3027
|
+
// references so nothing is left pointing at a controller that never
|
|
3028
|
+
// attached — otherwise a later attach short-circuits as "already attached"
|
|
3029
|
+
// and a detach would run detach() on it.
|
|
3030
|
+
if (element[CONTROLLER_KEY] === controllerInstance) {
|
|
3031
|
+
delete element[CONTROLLER_KEY];
|
|
3032
|
+
delete element[CONTROLLER_NAME_KEY];
|
|
3033
|
+
delete element[CONTROLLER_OPERATIONS];
|
|
3034
|
+
}
|
|
3035
|
+
throw error;
|
|
3036
|
+
}
|
|
2808
3037
|
finally {
|
|
2809
3038
|
if (element[CONTROLLER_ABORT] === abort) {
|
|
2810
3039
|
delete element[CONTROLLER_ABORT];
|
|
2811
3040
|
}
|
|
2812
3041
|
}
|
|
3042
|
+
// Past `ready` — mark the controller so detach knows attach was actually run.
|
|
3043
|
+
controllerInstance[CONTROLLER_ATTACHED] = true;
|
|
2813
3044
|
// Run attach in the controller's scope
|
|
2814
3045
|
await scope.runOperation(async () => {
|
|
2815
3046
|
await controllerInstance.attach(element);
|
|
@@ -2840,6 +3071,19 @@ async function detachController(element) {
|
|
|
2840
3071
|
if (!controllerInstance) {
|
|
2841
3072
|
return;
|
|
2842
3073
|
}
|
|
3074
|
+
// Claim the controller immediately — before any await — so a second detach
|
|
3075
|
+
// that overlaps this one (e.g. disconnect's fire-and-forget detach racing a
|
|
3076
|
+
// controller reassignment) finds no instance and returns, instead of running
|
|
3077
|
+
// detach() a second time on the same controller. The rest of this function
|
|
3078
|
+
// works off the locals captured above.
|
|
3079
|
+
delete element[CONTROLLER_KEY];
|
|
3080
|
+
delete element[CONTROLLER_NAME_KEY];
|
|
3081
|
+
delete element[CONTROLLER_OPERATIONS];
|
|
3082
|
+
// A controller whose attach() was aborted before it ever ran must not have
|
|
3083
|
+
// detach() (or the teardown/event) run on it.
|
|
3084
|
+
if (!controllerInstance[CONTROLLER_ATTACHED]) {
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
2843
3087
|
// Run detach in the controller's scope
|
|
2844
3088
|
if (scope) {
|
|
2845
3089
|
await scope.runOperation(async () => {
|
|
@@ -2862,9 +3106,6 @@ async function detachController(element) {
|
|
|
2862
3106
|
}
|
|
2863
3107
|
// Clean up router context reference
|
|
2864
3108
|
delete controllerInstance[ROUTER_CONTEXT];
|
|
2865
|
-
delete element[CONTROLLER_KEY];
|
|
2866
|
-
delete element[CONTROLLER_NAME_KEY];
|
|
2867
|
-
delete element[CONTROLLER_OPERATIONS];
|
|
2868
3109
|
element.dispatchEvent(new CustomEvent('controller-detached', {
|
|
2869
3110
|
detail: { name: controllerName, controller: controllerInstance }
|
|
2870
3111
|
}));
|
|
@@ -3027,19 +3268,23 @@ function setupContextHandler(element) {
|
|
|
3027
3268
|
const wrappedMethodName = `__wrapped_${methodName}`;
|
|
3028
3269
|
// Create wrapped method with timing controls
|
|
3029
3270
|
element[wrappedMethodName] = function (context) {
|
|
3030
|
-
//
|
|
3031
|
-
|
|
3271
|
+
// `once` is tracked PER method (not a single element-level flag) so
|
|
3272
|
+
// multiple once handlers on the same element each fire.
|
|
3273
|
+
const called = element[CONTEXT_CALLED] ||
|
|
3274
|
+
(element[CONTEXT_CALLED] = new Set());
|
|
3275
|
+
if (options.once && called.has(methodName)) {
|
|
3032
3276
|
return;
|
|
3033
3277
|
}
|
|
3034
3278
|
const callMethod = () => {
|
|
3035
3279
|
method.call(element, context);
|
|
3036
3280
|
// Handle once option
|
|
3037
3281
|
if (options.once) {
|
|
3038
|
-
|
|
3039
|
-
// Unregister
|
|
3282
|
+
called.add(methodName);
|
|
3283
|
+
// Unregister ONLY this handler, not the whole element — otherwise the
|
|
3284
|
+
// element's other @context handlers would stop receiving updates.
|
|
3040
3285
|
const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
|
|
3041
3286
|
if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
|
|
3042
|
-
ctx[CONTEXT_UNREGISTER](element);
|
|
3287
|
+
ctx[CONTEXT_UNREGISTER](element, wrappedMethodName);
|
|
3043
3288
|
}
|
|
3044
3289
|
}
|
|
3045
3290
|
};
|
|
@@ -3142,9 +3387,14 @@ class RenderScheduler {
|
|
|
3142
3387
|
this.pending.clear();
|
|
3143
3388
|
this.scheduled = false;
|
|
3144
3389
|
for (const element of elements) {
|
|
3145
|
-
// Skip elements that were disconnected between scheduling and flush
|
|
3146
|
-
|
|
3390
|
+
// Skip elements that were disconnected between scheduling and flush, but
|
|
3391
|
+
// remember the dropped render so reconnect can replay it — otherwise the
|
|
3392
|
+
// shadow DOM stays stale for a property changed while detached. The flag
|
|
3393
|
+
// lives on the element, so it's GC'd with it (no leak if never reattached).
|
|
3394
|
+
if (!element.isConnected) {
|
|
3395
|
+
element[PENDING_RECONNECT_RENDER] = true;
|
|
3147
3396
|
continue;
|
|
3397
|
+
}
|
|
3148
3398
|
const options = element[RENDER_OPTIONS] || {};
|
|
3149
3399
|
performRender(element, options);
|
|
3150
3400
|
}
|
|
@@ -3163,6 +3413,19 @@ function flushRenderCallbacks(element) {
|
|
|
3163
3413
|
element[RENDER_CALLBACKS] = [];
|
|
3164
3414
|
cbs.forEach(cb => cb());
|
|
3165
3415
|
}
|
|
3416
|
+
/**
|
|
3417
|
+
* Backstop against a render() that mutates an observed property, which makes
|
|
3418
|
+
* the property setter request another render from inside the current one. In
|
|
3419
|
+
* `sync` mode that re-enters performRender synchronously and, unchecked,
|
|
3420
|
+
* recurses to a stack overflow. The counter caps synchronous nesting and turns
|
|
3421
|
+
* a silent crash into an actionable error. Normal renders never nest (child
|
|
3422
|
+
* renders defer to a microtask), so this stays at depth 1 in the common case.
|
|
3423
|
+
*
|
|
3424
|
+
* The depth is tracked PER ELEMENT (on RENDER_DEPTH), not globally: a runaway
|
|
3425
|
+
* component must not consume a shared budget and reject an unrelated component
|
|
3426
|
+
* that happens to render inside its call stack.
|
|
3427
|
+
*/
|
|
3428
|
+
const MAX_RENDER_DEPTH = 50;
|
|
3166
3429
|
/**
|
|
3167
3430
|
* Perform the actual render of an element
|
|
3168
3431
|
*/
|
|
@@ -3174,6 +3437,14 @@ function performRender(element, options, precomputedResult) {
|
|
|
3174
3437
|
if (options.once && element[RENDER_INSTANCE]) {
|
|
3175
3438
|
return;
|
|
3176
3439
|
}
|
|
3440
|
+
const depth = (element[RENDER_DEPTH] ?? 0);
|
|
3441
|
+
if (depth >= MAX_RENDER_DEPTH) {
|
|
3442
|
+
const tag = element.tagName ? element.tagName.toLowerCase() : 'element';
|
|
3443
|
+
console.error(`snice: maximum render depth (${MAX_RENDER_DEPTH}) exceeded for <${tag}>. ` +
|
|
3444
|
+
`render() is mutating an observed property, causing an infinite render loop.`);
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
element[RENDER_DEPTH] = depth + 1;
|
|
3177
3448
|
try {
|
|
3178
3449
|
const result = precomputedResult !== undefined ? precomputedResult : renderMethod.call(element);
|
|
3179
3450
|
if (!element.shadowRoot)
|
|
@@ -3199,14 +3470,18 @@ function performRender(element, options, precomputedResult) {
|
|
|
3199
3470
|
flushRenderCallbacks(element);
|
|
3200
3471
|
return;
|
|
3201
3472
|
}
|
|
3202
|
-
// Different template or first render.
|
|
3203
|
-
//
|
|
3204
|
-
//
|
|
3473
|
+
// Different template or first render. Keep only the framework's own
|
|
3474
|
+
// fallback <style> tags (marked data-snice-style) so the @styles path
|
|
3475
|
+
// (Safari <=16 / jsdom / SSR — no adoptedStyleSheets) survives a template
|
|
3476
|
+
// switch; template-emitted <style> tags are removed and re-created by the
|
|
3477
|
+
// new instance, so they don't accumulate.
|
|
3205
3478
|
if (instance) {
|
|
3206
3479
|
const root = element.shadowRoot;
|
|
3207
3480
|
const toRemove = [];
|
|
3208
3481
|
for (const child of Array.from(root.childNodes)) {
|
|
3209
|
-
if (child.nodeType === 1 &&
|
|
3482
|
+
if (child.nodeType === 1 &&
|
|
3483
|
+
child.tagName === 'STYLE' &&
|
|
3484
|
+
child.hasAttribute('data-snice-style'))
|
|
3210
3485
|
continue;
|
|
3211
3486
|
toRemove.push(child);
|
|
3212
3487
|
}
|
|
@@ -3222,6 +3497,9 @@ function performRender(element, options, precomputedResult) {
|
|
|
3222
3497
|
catch (error) {
|
|
3223
3498
|
console.error('Error rendering element:', error);
|
|
3224
3499
|
}
|
|
3500
|
+
finally {
|
|
3501
|
+
element[RENDER_DEPTH] = depth;
|
|
3502
|
+
}
|
|
3225
3503
|
}
|
|
3226
3504
|
/**
|
|
3227
3505
|
* Request a render for an element
|
|
@@ -3275,6 +3553,33 @@ function requestRender(element, immediate = false) {
|
|
|
3275
3553
|
// Normal rendering (with microtask batching unless sync)
|
|
3276
3554
|
renderScheduler.schedule(element, options);
|
|
3277
3555
|
}
|
|
3556
|
+
/**
|
|
3557
|
+
* Clear any pending debounce/throttle render timers on an element (e.g. on
|
|
3558
|
+
* disconnect, so they don't fire on a dead element and retain it until they
|
|
3559
|
+
* expire). Returns true if a render was actually pending, so the caller can
|
|
3560
|
+
* arrange a replay on reconnect via PENDING_RECONNECT_RENDER.
|
|
3561
|
+
*/
|
|
3562
|
+
function clearRenderTimers(element) {
|
|
3563
|
+
const timers = element[RENDER_TIMERS];
|
|
3564
|
+
if (!timers)
|
|
3565
|
+
return false;
|
|
3566
|
+
let hadPending = false;
|
|
3567
|
+
if (timers.debounce) {
|
|
3568
|
+
clearTimeout(timers.debounce);
|
|
3569
|
+
timers.debounce = null;
|
|
3570
|
+
hadPending = true;
|
|
3571
|
+
}
|
|
3572
|
+
if (timers.throttleTimer) {
|
|
3573
|
+
clearTimeout(timers.throttleTimer);
|
|
3574
|
+
timers.throttleTimer = null;
|
|
3575
|
+
hadPending = true;
|
|
3576
|
+
}
|
|
3577
|
+
// Deliberately do NOT reset timers.lastThrottle: a plain DOM move is a
|
|
3578
|
+
// disconnect+reconnect, and zeroing the cooldown would let the next render
|
|
3579
|
+
// fire inside the throttle window. Preserving it keeps throttle honest
|
|
3580
|
+
// across moves; on a true disconnect the element is discarded anyway.
|
|
3581
|
+
return hadPending;
|
|
3582
|
+
}
|
|
3278
3583
|
/**
|
|
3279
3584
|
* @render decorator for component rendering
|
|
3280
3585
|
*
|
|
@@ -3397,9 +3702,12 @@ function applyStyles(element) {
|
|
|
3397
3702
|
element.shadowRoot.adoptedStyleSheets = allResults.map(r => r.styleSheet);
|
|
3398
3703
|
return;
|
|
3399
3704
|
}
|
|
3400
|
-
// Fallback — one <style> tag per stylesheet, preserving cascade order
|
|
3705
|
+
// Fallback — one <style> tag per stylesheet, preserving cascade order.
|
|
3706
|
+
// Marked so the template-switch cleanup keeps these (framework styles) but
|
|
3707
|
+
// still removes template-emitted <style> tags (which would otherwise pile up).
|
|
3401
3708
|
for (const r of allResults) {
|
|
3402
3709
|
const style = document.createElement('style');
|
|
3710
|
+
style.setAttribute('data-snice-style', '');
|
|
3403
3711
|
style.textContent = r.cssText;
|
|
3404
3712
|
element.shadowRoot.appendChild(style);
|
|
3405
3713
|
}
|
|
@@ -3409,6 +3717,125 @@ function applyStyles(element) {
|
|
|
3409
3717
|
}
|
|
3410
3718
|
}
|
|
3411
3719
|
|
|
3720
|
+
// @dispatch decorator - auto-dispatches custom events from method return values
|
|
3721
|
+
/**
|
|
3722
|
+
* Decorator that automatically dispatches a custom event after a method is called.
|
|
3723
|
+
* The return value of the method becomes the event detail.
|
|
3724
|
+
*
|
|
3725
|
+
* @param eventName The name of the event to dispatch
|
|
3726
|
+
* @param options Optional configuration extending EventInit
|
|
3727
|
+
*/
|
|
3728
|
+
function dispatch(eventName, options) {
|
|
3729
|
+
return function (originalMethod, _context) {
|
|
3730
|
+
return function (...args) {
|
|
3731
|
+
// Create timing wrappers for dispatch (per-instance)
|
|
3732
|
+
if (!this[DISPATCH_TIMERS]) {
|
|
3733
|
+
this[DISPATCH_TIMERS] = new Map();
|
|
3734
|
+
}
|
|
3735
|
+
const timerKey = `${eventName}_${_context.name}`;
|
|
3736
|
+
if (!this[DISPATCH_TIMERS].has(timerKey)) {
|
|
3737
|
+
this[DISPATCH_TIMERS].set(timerKey, {
|
|
3738
|
+
debounceTimeout: null,
|
|
3739
|
+
throttleLastCall: 0,
|
|
3740
|
+
throttleTimeout: null
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
const timers = this[DISPATCH_TIMERS].get(timerKey);
|
|
3744
|
+
// Call the original method with preserved this context
|
|
3745
|
+
const result = originalMethod.apply(this, args);
|
|
3746
|
+
// Helper to dispatch the event
|
|
3747
|
+
const doDispatch = (detail) => {
|
|
3748
|
+
// Skip dispatch if result is undefined and dispatchOnUndefined is false
|
|
3749
|
+
if (detail === undefined && options?.dispatchOnUndefined === false) {
|
|
3750
|
+
return;
|
|
3751
|
+
}
|
|
3752
|
+
// Create event with spread operator for options
|
|
3753
|
+
const event = new CustomEvent(eventName, {
|
|
3754
|
+
bubbles: true, // Default to true for component events
|
|
3755
|
+
composed: true, // Allow crossing shadow DOM boundaries
|
|
3756
|
+
...options, // Spread all EventInit options
|
|
3757
|
+
detail
|
|
3758
|
+
});
|
|
3759
|
+
// Resolve scope target. Default is `this` (host element).
|
|
3760
|
+
// Explicit scope dispatches the event on another target — useful for
|
|
3761
|
+
// 'global' bus events on document, or routing through an ancestor.
|
|
3762
|
+
if (options?.scope !== undefined) {
|
|
3763
|
+
const target = resolveScope(this, options.scope);
|
|
3764
|
+
if (!target) {
|
|
3765
|
+
console.warn(`[snice/@dispatch] scope did not resolve for "${eventName}" — event not dispatched.`);
|
|
3766
|
+
return;
|
|
3767
|
+
}
|
|
3768
|
+
target.dispatchEvent(event);
|
|
3769
|
+
return;
|
|
3770
|
+
}
|
|
3771
|
+
this.dispatchEvent(event);
|
|
3772
|
+
};
|
|
3773
|
+
// Helper to handle timed dispatch
|
|
3774
|
+
const timedDispatch = (detail) => {
|
|
3775
|
+
if (options?.debounce) {
|
|
3776
|
+
clearTimeout(timers.debounceTimeout);
|
|
3777
|
+
timers.debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
if (!options?.throttle) {
|
|
3781
|
+
doDispatch(detail);
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
const now = Date.now();
|
|
3785
|
+
const remaining = options.throttle - (now - timers.throttleLastCall);
|
|
3786
|
+
if (remaining <= 0) {
|
|
3787
|
+
clearTimeout(timers.throttleTimeout);
|
|
3788
|
+
timers.throttleLastCall = now;
|
|
3789
|
+
doDispatch(detail);
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
// Record the LATEST detail so the trailing dispatch carries fresh
|
|
3793
|
+
// data, not the first-suppressed-call detail captured by closure.
|
|
3794
|
+
timers.latestDetail = detail;
|
|
3795
|
+
if (!timers.throttleTimeout) {
|
|
3796
|
+
timers.throttleTimeout = setTimeout(() => {
|
|
3797
|
+
timers.throttleLastCall = Date.now();
|
|
3798
|
+
timers.throttleTimeout = null;
|
|
3799
|
+
const d = timers.latestDetail;
|
|
3800
|
+
timers.latestDetail = undefined;
|
|
3801
|
+
doDispatch(d);
|
|
3802
|
+
}, remaining);
|
|
3803
|
+
}
|
|
3804
|
+
};
|
|
3805
|
+
// Handle async methods
|
|
3806
|
+
if (result instanceof Promise) {
|
|
3807
|
+
return result.then((resolvedResult) => {
|
|
3808
|
+
timedDispatch(resolvedResult);
|
|
3809
|
+
return resolvedResult;
|
|
3810
|
+
});
|
|
3811
|
+
}
|
|
3812
|
+
// Sync method
|
|
3813
|
+
timedDispatch(result);
|
|
3814
|
+
return result;
|
|
3815
|
+
};
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
/**
|
|
3819
|
+
* Clear any pending debounce/throttle dispatch timers on an instance (e.g. on
|
|
3820
|
+
* disconnect, so a queued event doesn't fire into a detached node). A dispatch
|
|
3821
|
+
* is a one-shot signal, so pending ones are dropped, not replayed.
|
|
3822
|
+
*/
|
|
3823
|
+
function clearDispatchTimers(instance) {
|
|
3824
|
+
const timers = instance[DISPATCH_TIMERS];
|
|
3825
|
+
if (!timers)
|
|
3826
|
+
return;
|
|
3827
|
+
for (const t of timers.values()) {
|
|
3828
|
+
if (t.debounceTimeout)
|
|
3829
|
+
clearTimeout(t.debounceTimeout);
|
|
3830
|
+
if (t.throttleTimeout)
|
|
3831
|
+
clearTimeout(t.throttleTimeout);
|
|
3832
|
+
t.debounceTimeout = null;
|
|
3833
|
+
t.throttleTimeout = null;
|
|
3834
|
+
t.throttleLastCall = 0;
|
|
3835
|
+
t.latestDetail = undefined;
|
|
3836
|
+
}
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3412
3839
|
/**
|
|
3413
3840
|
* Method decorators for common patterns
|
|
3414
3841
|
* @debounce, @throttle, @once, @memoize
|
|
@@ -3835,6 +4262,14 @@ function applyElementFunctionality(constructor) {
|
|
|
3835
4262
|
}
|
|
3836
4263
|
}
|
|
3837
4264
|
}
|
|
4265
|
+
// Replay a render that was dropped while disconnected. The scheduler
|
|
4266
|
+
// sets this flag when it skips a detached element, so a property changed
|
|
4267
|
+
// while detached still reaches the DOM on reattach. A plain move (no
|
|
4268
|
+
// pending render) leaves the flag unset, so we don't re-render needlessly.
|
|
4269
|
+
if (this[RENDER_METHOD] && this[PENDING_RECONNECT_RENDER]) {
|
|
4270
|
+
this[PENDING_RECONNECT_RENDER] = false;
|
|
4271
|
+
requestRender(this);
|
|
4272
|
+
}
|
|
3838
4273
|
return;
|
|
3839
4274
|
}
|
|
3840
4275
|
// Mark that properties are being initialized from attributes
|
|
@@ -3882,6 +4317,9 @@ function applyElementFunctionality(constructor) {
|
|
|
3882
4317
|
setupResponseHandlers(this, this);
|
|
3883
4318
|
setupContextHandler(this);
|
|
3884
4319
|
this[INITIALIZED] = true;
|
|
4320
|
+
// Now that the initial value is settled, give @watch immediate handlers
|
|
4321
|
+
// their one init call (before the first render, so derived state is ready).
|
|
4322
|
+
invokeImmediateWatchers(this, constructor);
|
|
3885
4323
|
if (originalConnectedCallback) {
|
|
3886
4324
|
originalConnectedCallback.call(this);
|
|
3887
4325
|
}
|
|
@@ -3976,6 +4414,17 @@ function applyElementFunctionality(constructor) {
|
|
|
3976
4414
|
// Cleanup pending @debounce / @throttle timers so they don't fire on a dead element
|
|
3977
4415
|
clearDebounceTimers(this);
|
|
3978
4416
|
clearThrottleTimers(this);
|
|
4417
|
+
// The render/dispatch debounce-throttle timers live in separate stores.
|
|
4418
|
+
// A pending render is replayed on reconnect (same flag the scheduler
|
|
4419
|
+
// uses); a pending dispatch is a one-shot signal, so it's dropped.
|
|
4420
|
+
if (clearRenderTimers(this)) {
|
|
4421
|
+
this[PENDING_RECONNECT_RENDER] = true;
|
|
4422
|
+
}
|
|
4423
|
+
clearDispatchTimers(this);
|
|
4424
|
+
// @moved / @adopted debounce-throttle timers, likewise dropped so they
|
|
4425
|
+
// don't fire on a dead element.
|
|
4426
|
+
clearLifecycleTimers(this, MOVED_TIMERS);
|
|
4427
|
+
clearLifecycleTimers(this, ADOPTED_TIMERS);
|
|
3979
4428
|
};
|
|
3980
4429
|
constructor.prototype.attributeChangedCallback = function (name, oldValue, newValue) {
|
|
3981
4430
|
originalAttributeChangedCallback?.call(this, name, oldValue, newValue);
|
|
@@ -3991,13 +4440,27 @@ function applyElementFunctionality(constructor) {
|
|
|
3991
4440
|
if (attributeName.toLowerCase() !== name.toLowerCase())
|
|
3992
4441
|
continue;
|
|
3993
4442
|
const currentValue = this[PROPERTY_VALUES]?.[propName];
|
|
3994
|
-
|
|
3995
|
-
|
|
4443
|
+
// A removed attribute reverts the property to its field default. Read it
|
|
4444
|
+
// back through the getter (attribute is already gone at this point) so the
|
|
4445
|
+
// watcher's newValue and PROPERTY_VALUES match what this[propName] now
|
|
4446
|
+
// returns — parseAttributeValue(null) would diverge to null for String/Number.
|
|
4447
|
+
const parsedValue = newValue === null
|
|
4448
|
+
? this[propName]
|
|
4449
|
+
: parseAttributeValue(newValue, propOptions, currentValue, undefined);
|
|
4450
|
+
const changed = propOptions?.hasChanged
|
|
4451
|
+
? propOptions.hasChanged(parsedValue, currentValue)
|
|
4452
|
+
: notEqual(parsedValue, currentValue);
|
|
4453
|
+
if (!changed)
|
|
3996
4454
|
break;
|
|
3997
4455
|
ensureSet(this, EXPLICITLY_SET_PROPERTIES).add(propName);
|
|
3998
4456
|
ensureObj(this, PROPERTY_VALUES)[propName] = parsedValue;
|
|
3999
4457
|
if (!this[SETTING_FROM_PROPERTY]?.has(name.toLowerCase())) {
|
|
4000
|
-
|
|
4458
|
+
// Watchers react to changes only. During upgrade the initial value
|
|
4459
|
+
// is not a change, so suppress until INITIALIZED; @watch immediate
|
|
4460
|
+
// handlers get their one init call from invokeImmediateWatchers.
|
|
4461
|
+
if (this[INITIALIZED]) {
|
|
4462
|
+
invokeWatchers(this, constructor, propName, currentValue, parsedValue);
|
|
4463
|
+
}
|
|
4001
4464
|
if (this[RENDER_METHOD] && this[INITIALIZED]) {
|
|
4002
4465
|
requestRender(this);
|
|
4003
4466
|
}
|
|
@@ -4007,12 +4470,14 @@ function applyElementFunctionality(constructor) {
|
|
|
4007
4470
|
};
|
|
4008
4471
|
// Add connectedMoveCallback for handling DOM moves
|
|
4009
4472
|
constructor.prototype.connectedMoveCallback = async function () {
|
|
4010
|
-
// Call @moved handlers
|
|
4473
|
+
// Call @moved handlers via the prototype method (the debounce/throttle
|
|
4474
|
+
// wrapper), not the raw handler — otherwise @moved({debounce}) options
|
|
4475
|
+
// are ignored for real moves and only work when called directly.
|
|
4011
4476
|
const movedHandlers = constructor[MOVED_HANDLERS];
|
|
4012
4477
|
if (movedHandlers) {
|
|
4013
4478
|
for (const handler of movedHandlers) {
|
|
4014
4479
|
try {
|
|
4015
|
-
await handler.
|
|
4480
|
+
await this[handler.methodName]();
|
|
4016
4481
|
}
|
|
4017
4482
|
catch (error) {
|
|
4018
4483
|
console.error(`Error in @moved handler ${handler.methodName}:`, error);
|
|
@@ -4022,12 +4487,13 @@ function applyElementFunctionality(constructor) {
|
|
|
4022
4487
|
};
|
|
4023
4488
|
// Add adoptedCallback for handling document adoption
|
|
4024
4489
|
constructor.prototype.adoptedCallback = async function () {
|
|
4025
|
-
// Call @adopted handlers
|
|
4490
|
+
// Call @adopted handlers via the prototype method (the debounce/throttle
|
|
4491
|
+
// wrapper), not the raw handler — same reason as connectedMoveCallback.
|
|
4026
4492
|
const adoptedHandlers = constructor[ADOPTED_HANDLERS];
|
|
4027
4493
|
if (adoptedHandlers) {
|
|
4028
4494
|
for (const handler of adoptedHandlers) {
|
|
4029
4495
|
try {
|
|
4030
|
-
await handler.
|
|
4496
|
+
await this[handler.methodName]();
|
|
4031
4497
|
}
|
|
4032
4498
|
catch (error) {
|
|
4033
4499
|
console.error(`Error in @adopted handler ${handler.methodName}:`, error);
|
|
@@ -4192,7 +4658,12 @@ function property(options) {
|
|
|
4192
4658
|
},
|
|
4193
4659
|
set(newValue) {
|
|
4194
4660
|
const oldValue = this[propertyKey];
|
|
4195
|
-
|
|
4661
|
+
// A custom hasChanged comparator overrides the default === check
|
|
4662
|
+
// (e.g. deep-equal objects, or forcing an update on a mutated ref).
|
|
4663
|
+
const changed = finalOptions?.hasChanged
|
|
4664
|
+
? finalOptions.hasChanged(newValue, oldValue)
|
|
4665
|
+
: notEqual(newValue, oldValue);
|
|
4666
|
+
if (!changed)
|
|
4196
4667
|
return;
|
|
4197
4668
|
// Pre-init: store for later, don't reflect to DOM yet
|
|
4198
4669
|
if (!this[PROPERTIES_INITIALIZED]) {
|
|
@@ -4209,18 +4680,27 @@ function property(options) {
|
|
|
4209
4680
|
const attributeName = getAttrName(finalOptions, propertyKey);
|
|
4210
4681
|
const attributeValue = valueToAttribute(newValue, finalOptions, initialValue);
|
|
4211
4682
|
ensureSet(this, EXPLICITLY_SET_PROPERTIES).add(propertyKey);
|
|
4212
|
-
|
|
4683
|
+
const attrKey = attributeName.toLowerCase();
|
|
4684
|
+
ensureSet(this, SETTING_FROM_PROPERTY).add(attrKey);
|
|
4685
|
+
// attributeChangedCallback runs synchronously inside setAttribute /
|
|
4686
|
+
// removeAttribute (custom element reactions are synchronous), so the
|
|
4687
|
+
// guard only needs to bracket this one reflection. Clear it right
|
|
4688
|
+
// after — not on a microtask — otherwise an external attribute change
|
|
4689
|
+
// in the same tick is mistaken for our own echo and silently dropped.
|
|
4213
4690
|
if (attributeValue === null) {
|
|
4214
4691
|
this.removeAttribute?.(attributeName);
|
|
4215
4692
|
}
|
|
4216
4693
|
else {
|
|
4217
4694
|
this.setAttribute?.(attributeName, attributeValue);
|
|
4218
4695
|
}
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4696
|
+
this[SETTING_FROM_PROPERTY].delete(attrKey);
|
|
4697
|
+
}
|
|
4698
|
+
// See attributeChangedCallback: the initial value is not a change,
|
|
4699
|
+
// so suppress watchers until INITIALIZED. @watch immediate handlers
|
|
4700
|
+
// get their one init call from invokeImmediateWatchers.
|
|
4701
|
+
if (this[INITIALIZED]) {
|
|
4702
|
+
invokeWatchers(this, this.constructor, propertyKey, oldValue, newValue);
|
|
4222
4703
|
}
|
|
4223
|
-
invokeWatchers(this, this.constructor, propertyKey, oldValue, newValue);
|
|
4224
4704
|
if (this[RENDER_METHOD] && this[INITIALIZED]) {
|
|
4225
4705
|
requestRender(this);
|
|
4226
4706
|
}
|
|
@@ -4290,7 +4770,12 @@ function queryAll(selector, options = {}) {
|
|
|
4290
4770
|
};
|
|
4291
4771
|
};
|
|
4292
4772
|
}
|
|
4293
|
-
function watch(...
|
|
4773
|
+
function watch(...args) {
|
|
4774
|
+
// A trailing options object is separated from the watched property names.
|
|
4775
|
+
const propertyNames = args.filter((a) => typeof a === 'string');
|
|
4776
|
+
const options = args.find((a) => typeof a === 'object' && a !== null);
|
|
4777
|
+
// Fire on init by default; opt out of the init call with { immediate: false }.
|
|
4778
|
+
const immediate = options?.immediate !== false;
|
|
4294
4779
|
return function (target, context) {
|
|
4295
4780
|
const methodName = context.name;
|
|
4296
4781
|
context.addInitializer(function () {
|
|
@@ -4330,7 +4815,8 @@ function watch(...propertyNames) {
|
|
|
4330
4815
|
}
|
|
4331
4816
|
constructor[PROPERTY_WATCHERS].get(propertyName).push({
|
|
4332
4817
|
methodName,
|
|
4333
|
-
method: target
|
|
4818
|
+
method: target,
|
|
4819
|
+
immediate
|
|
4334
4820
|
});
|
|
4335
4821
|
}
|
|
4336
4822
|
});
|
|
@@ -4439,6 +4925,24 @@ function reconnect() {
|
|
|
4439
4925
|
registerHandler(RECONNECT_HANDLERS, RECONNECT_METHODS, target, context);
|
|
4440
4926
|
};
|
|
4441
4927
|
}
|
|
4928
|
+
/**
|
|
4929
|
+
* Clear any pending @moved / @adopted debounce/throttle timers on an instance
|
|
4930
|
+
* (e.g. on disconnect), so they don't fire on a dead element.
|
|
4931
|
+
*/
|
|
4932
|
+
function clearLifecycleTimers(instance, timersSymbol) {
|
|
4933
|
+
const map = instance[timersSymbol];
|
|
4934
|
+
if (!map)
|
|
4935
|
+
return;
|
|
4936
|
+
for (const t of map.values()) {
|
|
4937
|
+
if (t.debounceTimer)
|
|
4938
|
+
clearTimeout(t.debounceTimer);
|
|
4939
|
+
if (t.throttleTimer)
|
|
4940
|
+
clearTimeout(t.throttleTimer);
|
|
4941
|
+
t.debounceTimer = null;
|
|
4942
|
+
t.throttleTimer = null;
|
|
4943
|
+
t.lastThrottleCall = 0;
|
|
4944
|
+
}
|
|
4945
|
+
}
|
|
4442
4946
|
function createLifecycleDecorator(handlersSymbol, timersSymbol, methodsSymbol) {
|
|
4443
4947
|
return function (options = {}) {
|
|
4444
4948
|
return function (originalMethod, context) {
|
|
@@ -4452,9 +4956,23 @@ function createLifecycleDecorator(handlersSymbol, timersSymbol, methodsSymbol) {
|
|
|
4452
4956
|
}
|
|
4453
4957
|
const timers = this[timersSymbol].get(methodName);
|
|
4454
4958
|
const exec = (...a) => originalMethod.apply(this, a);
|
|
4959
|
+
// Deferred (debounce/throttle) calls run inside a setTimeout, outside
|
|
4960
|
+
// the caller's try/catch — so catch their sync throws and async
|
|
4961
|
+
// rejections here and log, rather than surface an uncaught error.
|
|
4962
|
+
const execDeferred = (...a) => {
|
|
4963
|
+
try {
|
|
4964
|
+
const result = originalMethod.apply(this, a);
|
|
4965
|
+
if (result && typeof result.then === 'function') {
|
|
4966
|
+
result.catch((error) => console.error(`Error in deferred lifecycle handler ${methodName}:`, error));
|
|
4967
|
+
}
|
|
4968
|
+
}
|
|
4969
|
+
catch (error) {
|
|
4970
|
+
console.error(`Error in deferred lifecycle handler ${methodName}:`, error);
|
|
4971
|
+
}
|
|
4972
|
+
};
|
|
4455
4973
|
if (options.debounce > 0) {
|
|
4456
4974
|
clearTimeout(timers.debounceTimer);
|
|
4457
|
-
timers.debounceTimer = setTimeout(() =>
|
|
4975
|
+
timers.debounceTimer = setTimeout(() => execDeferred(...args), options.debounce);
|
|
4458
4976
|
return undefined;
|
|
4459
4977
|
}
|
|
4460
4978
|
if (options.throttle > 0) {
|
|
@@ -4468,7 +4986,7 @@ function createLifecycleDecorator(handlersSymbol, timersSymbol, methodsSymbol) {
|
|
|
4468
4986
|
timers.throttleTimer = setTimeout(() => {
|
|
4469
4987
|
timers.throttleTimer = null;
|
|
4470
4988
|
timers.lastThrottleCall = Date.now();
|
|
4471
|
-
|
|
4989
|
+
execDeferred(...args);
|
|
4472
4990
|
}, remaining);
|
|
4473
4991
|
}
|
|
4474
4992
|
return undefined;
|
|
@@ -5100,6 +5618,40 @@ class Route {
|
|
|
5100
5618
|
}
|
|
5101
5619
|
}
|
|
5102
5620
|
|
|
5621
|
+
/**
|
|
5622
|
+
* Score a route pattern by specificity so that more specific routes are tried
|
|
5623
|
+
* first. Scoring is per-segment by ROLE, not by string length: a static segment
|
|
5624
|
+
* outweighs a dynamic (`:param`) segment, which outweighs a wildcard (`*` /
|
|
5625
|
+
* `*name`). So `/post/new` beats `/post/:id`, and a splat always ranks last —
|
|
5626
|
+
* regardless of how long the pattern string happens to be. This mirrors the
|
|
5627
|
+
* segment-scoring model mainstream routers (React Router, Remix) use.
|
|
5628
|
+
*
|
|
5629
|
+
* Sort callers descending by this score (highest = most specific = first).
|
|
5630
|
+
*
|
|
5631
|
+
* NOTE: mirrored in `src/react/route-specificity.ts` for the isolated React
|
|
5632
|
+
* bundle — keep the two in sync.
|
|
5633
|
+
*/
|
|
5634
|
+
const STATIC_SEGMENT = 10;
|
|
5635
|
+
const DYNAMIC_SEGMENT = 3;
|
|
5636
|
+
const WILDCARD_SEGMENT = -2;
|
|
5637
|
+
function routeSpecificity(spec) {
|
|
5638
|
+
const segments = spec.split('/').filter(Boolean);
|
|
5639
|
+
// Base on segment count so a deeper path outranks a shallower one on ties.
|
|
5640
|
+
let score = segments.length;
|
|
5641
|
+
for (const segment of segments) {
|
|
5642
|
+
if (segment.startsWith('*')) {
|
|
5643
|
+
score += WILDCARD_SEGMENT;
|
|
5644
|
+
}
|
|
5645
|
+
else if (segment.startsWith(':')) {
|
|
5646
|
+
score += DYNAMIC_SEGMENT;
|
|
5647
|
+
}
|
|
5648
|
+
else {
|
|
5649
|
+
score += STATIC_SEGMENT;
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
return score;
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5103
5655
|
/**
|
|
5104
5656
|
* Generic transition system for animating between elements
|
|
5105
5657
|
*/
|
|
@@ -5367,7 +5919,7 @@ function Router(options) {
|
|
|
5367
5919
|
}
|
|
5368
5920
|
const needsSorting = !is_sorted;
|
|
5369
5921
|
if (needsSorting) {
|
|
5370
|
-
routes.sort((a, b) => b.route.spec
|
|
5922
|
+
routes.sort((a, b) => routeSpecificity(b.route.spec) - routeSpecificity(a.route.spec));
|
|
5371
5923
|
is_sorted = true;
|
|
5372
5924
|
}
|
|
5373
5925
|
// Collect placards from registered routes
|
|
@@ -5438,7 +5990,7 @@ function Router(options) {
|
|
|
5438
5990
|
</div>`;
|
|
5439
5991
|
return container;
|
|
5440
5992
|
}
|
|
5441
|
-
async function checkGuards(guards, params, target) {
|
|
5993
|
+
async function checkGuards(guards, params, target, stale) {
|
|
5442
5994
|
const hasGuards = !!guards;
|
|
5443
5995
|
if (!hasGuards) {
|
|
5444
5996
|
return true;
|
|
@@ -5446,8 +5998,11 @@ function Router(options) {
|
|
|
5446
5998
|
const guardsArray = Array.isArray(guards) ? guards : [guards];
|
|
5447
5999
|
let spinner = null;
|
|
5448
6000
|
let spinnerTimer = null;
|
|
5449
|
-
// Show spinner after a short delay (avoids flash for fast sync guards)
|
|
6001
|
+
// Show spinner after a short delay (avoids flash for fast sync guards).
|
|
6002
|
+
// Skip if a newer navigation has already superseded this one.
|
|
5450
6003
|
spinnerTimer = setTimeout(() => {
|
|
6004
|
+
if (stale && stale())
|
|
6005
|
+
return;
|
|
5451
6006
|
spinner = createLoadingSpinner();
|
|
5452
6007
|
target.appendChild(spinner);
|
|
5453
6008
|
}, 50);
|
|
@@ -5455,7 +6010,11 @@ function Router(options) {
|
|
|
5455
6010
|
for (const guard of guardsArray) {
|
|
5456
6011
|
const allowed = await guard(context, params);
|
|
5457
6012
|
if (!allowed) {
|
|
5458
|
-
|
|
6013
|
+
// Don't stomp the current page with a 403 if a newer navigation
|
|
6014
|
+
// already took over while this guard was resolving.
|
|
6015
|
+
if (!stale || !stale()) {
|
|
6016
|
+
renderForbiddenPage(target);
|
|
6017
|
+
}
|
|
5459
6018
|
return false;
|
|
5460
6019
|
}
|
|
5461
6020
|
}
|
|
@@ -5491,14 +6050,14 @@ function Router(options) {
|
|
|
5491
6050
|
div.innerHTML = /*html*/ `<h1>404</h1><p>Page not found</p>`;
|
|
5492
6051
|
return { element: div, transition: undefined, layout: undefined };
|
|
5493
6052
|
}
|
|
5494
|
-
async function resolveRoute(path, target) {
|
|
6053
|
+
async function resolveRoute(path, target, stale) {
|
|
5495
6054
|
for (const route of routes) {
|
|
5496
6055
|
const params = route.route.match(path);
|
|
5497
6056
|
const isMatch = params !== false;
|
|
5498
6057
|
if (!isMatch) {
|
|
5499
6058
|
continue;
|
|
5500
6059
|
}
|
|
5501
|
-
const guardsAllowed = await checkGuards(route.guards, params, target);
|
|
6060
|
+
const guardsAllowed = await checkGuards(route.guards, params, target, stale);
|
|
5502
6061
|
if (!guardsAllowed) {
|
|
5503
6062
|
return { result: RouteResult.GUARDS_FAILED };
|
|
5504
6063
|
}
|
|
@@ -5626,7 +6185,7 @@ function Router(options) {
|
|
|
5626
6185
|
const isHomePath = (path?.trim() === '' || path === '/') && !!home;
|
|
5627
6186
|
if (isHomePath) {
|
|
5628
6187
|
const homeRoute = routes.find(r => r.route.match('/'));
|
|
5629
|
-
if (!(await checkGuards(homeRoute?.guards, {}, target)))
|
|
6188
|
+
if (!(await checkGuards(homeRoute?.guards, {}, target, stale)))
|
|
5630
6189
|
return;
|
|
5631
6190
|
if (stale())
|
|
5632
6191
|
return;
|
|
@@ -5638,7 +6197,7 @@ function Router(options) {
|
|
|
5638
6197
|
if (!path)
|
|
5639
6198
|
return;
|
|
5640
6199
|
// Resolve route
|
|
5641
|
-
const routeResult = await resolveRoute(path, target);
|
|
6200
|
+
const routeResult = await resolveRoute(path, target, stale);
|
|
5642
6201
|
if (stale())
|
|
5643
6202
|
return;
|
|
5644
6203
|
// Guards failed (403 already rendered by checkGuards)
|
|
@@ -5652,7 +6211,7 @@ function Router(options) {
|
|
|
5652
6211
|
}
|
|
5653
6212
|
// 404 fallthrough
|
|
5654
6213
|
const { element, transition, layout } = create404Element();
|
|
5655
|
-
await renderPage(target, element, transition, layout, path, {});
|
|
6214
|
+
await renderPage(target, element, transition, layout, path, {}, stale);
|
|
5656
6215
|
}
|
|
5657
6216
|
async function performTransition$1(container, oldElement, newElement, transition, atGen = navGeneration) {
|
|
5658
6217
|
// `isStale` returns true when a newer navigate() has been kicked off.
|
|
@@ -5669,93 +6228,6 @@ function Router(options) {
|
|
|
5669
6228
|
};
|
|
5670
6229
|
}
|
|
5671
6230
|
|
|
5672
|
-
// @dispatch decorator - auto-dispatches custom events from method return values
|
|
5673
|
-
/**
|
|
5674
|
-
* Decorator that automatically dispatches a custom event after a method is called.
|
|
5675
|
-
* The return value of the method becomes the event detail.
|
|
5676
|
-
*
|
|
5677
|
-
* @param eventName The name of the event to dispatch
|
|
5678
|
-
* @param options Optional configuration extending EventInit
|
|
5679
|
-
*/
|
|
5680
|
-
function dispatch(eventName, options) {
|
|
5681
|
-
return function (originalMethod, _context) {
|
|
5682
|
-
return function (...args) {
|
|
5683
|
-
// Create timing wrappers for dispatch (per-instance)
|
|
5684
|
-
if (!this[DISPATCH_TIMERS]) {
|
|
5685
|
-
this[DISPATCH_TIMERS] = new Map();
|
|
5686
|
-
}
|
|
5687
|
-
const timerKey = `${eventName}_${_context.name}`;
|
|
5688
|
-
if (!this[DISPATCH_TIMERS].has(timerKey)) {
|
|
5689
|
-
this[DISPATCH_TIMERS].set(timerKey, {
|
|
5690
|
-
debounceTimeout: null,
|
|
5691
|
-
throttleLastCall: 0,
|
|
5692
|
-
throttleTimeout: null
|
|
5693
|
-
});
|
|
5694
|
-
}
|
|
5695
|
-
const timers = this[DISPATCH_TIMERS].get(timerKey);
|
|
5696
|
-
// Call the original method with preserved this context
|
|
5697
|
-
const result = originalMethod.apply(this, args);
|
|
5698
|
-
// Helper to dispatch the event
|
|
5699
|
-
const doDispatch = (detail) => {
|
|
5700
|
-
// Skip dispatch if result is undefined and dispatchOnUndefined is false
|
|
5701
|
-
if (detail === undefined && options?.dispatchOnUndefined === false) {
|
|
5702
|
-
return;
|
|
5703
|
-
}
|
|
5704
|
-
// Create event with spread operator for options
|
|
5705
|
-
const event = new CustomEvent(eventName, {
|
|
5706
|
-
bubbles: true, // Default to true for component events
|
|
5707
|
-
composed: true, // Allow crossing shadow DOM boundaries
|
|
5708
|
-
...options, // Spread all EventInit options
|
|
5709
|
-
detail
|
|
5710
|
-
});
|
|
5711
|
-
this.dispatchEvent(event);
|
|
5712
|
-
};
|
|
5713
|
-
// Helper to handle timed dispatch
|
|
5714
|
-
const timedDispatch = (detail) => {
|
|
5715
|
-
if (options?.debounce) {
|
|
5716
|
-
clearTimeout(timers.debounceTimeout);
|
|
5717
|
-
timers.debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
|
|
5718
|
-
return;
|
|
5719
|
-
}
|
|
5720
|
-
if (!options?.throttle) {
|
|
5721
|
-
doDispatch(detail);
|
|
5722
|
-
return;
|
|
5723
|
-
}
|
|
5724
|
-
const now = Date.now();
|
|
5725
|
-
const remaining = options.throttle - (now - timers.throttleLastCall);
|
|
5726
|
-
if (remaining <= 0) {
|
|
5727
|
-
clearTimeout(timers.throttleTimeout);
|
|
5728
|
-
timers.throttleLastCall = now;
|
|
5729
|
-
doDispatch(detail);
|
|
5730
|
-
return;
|
|
5731
|
-
}
|
|
5732
|
-
// Record the LATEST detail so the trailing dispatch carries fresh
|
|
5733
|
-
// data, not the first-suppressed-call detail captured by closure.
|
|
5734
|
-
timers.latestDetail = detail;
|
|
5735
|
-
if (!timers.throttleTimeout) {
|
|
5736
|
-
timers.throttleTimeout = setTimeout(() => {
|
|
5737
|
-
timers.throttleLastCall = Date.now();
|
|
5738
|
-
timers.throttleTimeout = null;
|
|
5739
|
-
const d = timers.latestDetail;
|
|
5740
|
-
timers.latestDetail = undefined;
|
|
5741
|
-
doDispatch(d);
|
|
5742
|
-
}, remaining);
|
|
5743
|
-
}
|
|
5744
|
-
};
|
|
5745
|
-
// Handle async methods
|
|
5746
|
-
if (result instanceof Promise) {
|
|
5747
|
-
return result.then((resolvedResult) => {
|
|
5748
|
-
timedDispatch(resolvedResult);
|
|
5749
|
-
return resolvedResult;
|
|
5750
|
-
});
|
|
5751
|
-
}
|
|
5752
|
-
// Sync method
|
|
5753
|
-
timedDispatch(result);
|
|
5754
|
-
return result;
|
|
5755
|
-
};
|
|
5756
|
-
};
|
|
5757
|
-
}
|
|
5758
|
-
|
|
5759
6231
|
/**
|
|
5760
6232
|
* createRequestHandler — vanilla JS utility for handling @request channels
|
|
5761
6233
|
* outside of the Snice element/controller decorator system.
|
|
@@ -6101,7 +6573,7 @@ if (typeof document !== 'undefined') {
|
|
|
6101
6573
|
}
|
|
6102
6574
|
|
|
6103
6575
|
/*!
|
|
6104
|
-
* snice v5.2.
|
|
6576
|
+
* snice v5.2.3
|
|
6105
6577
|
* A decorator-driven web component library with differential rendering, routing, controllers, and 130+ ready-made UI components. Use as much or as little as you want. Zero dependencies, works anywhere.
|
|
6106
6578
|
* (c) 2024
|
|
6107
6579
|
* Released under the MIT License.
|
|
@@ -6120,7 +6592,7 @@ if (!globalThis.snice) {
|
|
|
6120
6592
|
}
|
|
6121
6593
|
|
|
6122
6594
|
/*!
|
|
6123
|
-
* snice v5.2.
|
|
6595
|
+
* snice v5.2.3
|
|
6124
6596
|
* A decorator-driven web component library with differential rendering, routing, controllers, and 130+ ready-made UI components. Use as much or as little as you want. Zero dependencies, works anywhere.
|
|
6125
6597
|
* (c) 2024
|
|
6126
6598
|
* Released under the MIT License.
|