snice 5.2.3 → 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
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v5.2.
|
|
2
|
+
* snice v5.2.3
|
|
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.
|
|
@@ -35,6 +35,12 @@ const CHANNEL_HANDLERS = getSymbol('channel-handlers');
|
|
|
35
35
|
// Internal element state symbols
|
|
36
36
|
const READY_PROMISE = getSymbol('ready-promise');
|
|
37
37
|
const READY_RESOLVE = getSymbol('ready-resolve');
|
|
38
|
+
// Set when a render is requested but dropped because the element was
|
|
39
|
+
// disconnected; consumed on reconnect to replay exactly the missed render.
|
|
40
|
+
const PENDING_RECONNECT_RENDER = getSymbol('pending-reconnect-render');
|
|
41
|
+
// Per-element synchronous render-nesting depth, used to cap runaway re-entry
|
|
42
|
+
// without a shared global counter that one element could exhaust for others.
|
|
43
|
+
const RENDER_DEPTH = getSymbol('render-depth');
|
|
38
44
|
const CONTROLLER = getSymbol('controller');
|
|
39
45
|
const INITIALIZED = getSymbol('initialized');
|
|
40
46
|
// Controller symbols
|
|
@@ -106,6 +112,10 @@ const ADOPTED_METHODS = getSymbol('adopted-methods');
|
|
|
106
112
|
// Controller attach-abort — set to an AbortController while attachController
|
|
107
113
|
// is awaiting element.ready, so detachController can short-circuit the wait.
|
|
108
114
|
const CONTROLLER_ABORT = getSymbol('controller-abort');
|
|
115
|
+
// Set on a controller instance once it has gotten past element.ready and is
|
|
116
|
+
// being attached, so detachController knows not to run detach() on a controller
|
|
117
|
+
// whose attach() was aborted before it ever ran.
|
|
118
|
+
const CONTROLLER_ATTACHED = getSymbol('controller-attached');
|
|
109
119
|
const PROPERTY_DEFINERS = getSymbol('property-definers');
|
|
110
120
|
|
|
111
121
|
/**
|
|
@@ -226,11 +236,23 @@ class Context {
|
|
|
226
236
|
this[REGISTERED_ELEMENTS_SET].add(element);
|
|
227
237
|
}
|
|
228
238
|
/**
|
|
229
|
-
* Unregister an element from receiving context updates
|
|
239
|
+
* Unregister an element from receiving context updates. With a methodName,
|
|
240
|
+
* removes only that one handler (so a `once` handler tearing itself down
|
|
241
|
+
* doesn't kill the element's other @context handlers); without one, removes
|
|
242
|
+
* the element entirely (used on disconnect).
|
|
230
243
|
* @internal Used by @context decorator cleanup
|
|
231
244
|
*/
|
|
232
|
-
[CONTEXT_UNREGISTER](element) {
|
|
233
|
-
this[REGISTERED_ELEMENTS]
|
|
245
|
+
[CONTEXT_UNREGISTER](element, methodName) {
|
|
246
|
+
const map = this[REGISTERED_ELEMENTS];
|
|
247
|
+
if (methodName !== undefined) {
|
|
248
|
+
const names = map.get(element);
|
|
249
|
+
if (!names)
|
|
250
|
+
return;
|
|
251
|
+
names.delete(methodName);
|
|
252
|
+
if (names.size > 0)
|
|
253
|
+
return; // other handlers remain — keep the element
|
|
254
|
+
}
|
|
255
|
+
map.delete(element);
|
|
234
256
|
this[REGISTERED_ELEMENTS_SET].delete(element);
|
|
235
257
|
}
|
|
236
258
|
/**
|
|
@@ -395,11 +417,23 @@ function parseAttributeValue(attributeValue, propertyOptions, currentValue, init
|
|
|
395
417
|
* @param initialValue - The default value assigned to the property field (used for type detection)
|
|
396
418
|
* @returns The string representation for the HTML attribute, or null if value should remove attribute
|
|
397
419
|
*/
|
|
420
|
+
/**
|
|
421
|
+
* Default property dirty-check: has the value changed? Like `!==`, but treats
|
|
422
|
+
* NaN → NaN as unchanged (NaN !== NaN would otherwise re-fire forever).
|
|
423
|
+
*/
|
|
424
|
+
function notEqual(value, old) {
|
|
425
|
+
// Changed unless strictly equal, or both are NaN (each self-unequal).
|
|
426
|
+
return value !== old && (value === value || old === old);
|
|
427
|
+
}
|
|
398
428
|
/**
|
|
399
429
|
* Extract attribute name from property options
|
|
400
430
|
*/
|
|
401
431
|
function getAttrName(opts, propName) {
|
|
402
|
-
|
|
432
|
+
// Lowercase always: the DOM stores attribute names lowercase, so an explicit
|
|
433
|
+
// camelCase `attribute:` would otherwise never match observedAttributes and
|
|
434
|
+
// attributeChangedCallback would never fire. get/setAttribute are already
|
|
435
|
+
// case-insensitive, so this is safe for every caller.
|
|
436
|
+
return (typeof opts.attribute === 'string' ? opts.attribute : propName).toLowerCase();
|
|
403
437
|
}
|
|
404
438
|
/**
|
|
405
439
|
* Escape HTML text-node content. Use for text that will be interpolated
|
|
@@ -546,6 +580,42 @@ function invokeWatchers(instance, constructor, propName, oldValue, newValue) {
|
|
|
546
580
|
}
|
|
547
581
|
}
|
|
548
582
|
}
|
|
583
|
+
/**
|
|
584
|
+
* Fire the one-time initialization call for `@watch(..., { immediate: true })`
|
|
585
|
+
* handlers. Runs once per declared property after the element is initialized,
|
|
586
|
+
* passing the effective initial value with `oldValue` undefined. Non-immediate
|
|
587
|
+
* watchers are untouched — they only react to later changes.
|
|
588
|
+
*/
|
|
589
|
+
function invokeImmediateWatchers(instance, constructor) {
|
|
590
|
+
const watchers = constructor[PROPERTY_WATCHERS];
|
|
591
|
+
const properties = constructor[PROPERTIES];
|
|
592
|
+
if (!watchers || !properties)
|
|
593
|
+
return;
|
|
594
|
+
for (const [propName, propOptions] of properties) {
|
|
595
|
+
// A watcher may be registered under the JS name, the explicit `attribute:`
|
|
596
|
+
// name, or the `*` wildcard; fire each immediate one exactly once, keyed to
|
|
597
|
+
// this property's current value.
|
|
598
|
+
const keys = [propName];
|
|
599
|
+
const attrName = propOptions && typeof propOptions.attribute === 'string' ? propOptions.attribute : null;
|
|
600
|
+
if (attrName && attrName !== propName)
|
|
601
|
+
keys.push(attrName);
|
|
602
|
+
keys.push('*');
|
|
603
|
+
for (const key of keys) {
|
|
604
|
+
if (!watchers.has(key))
|
|
605
|
+
continue;
|
|
606
|
+
for (const w of watchers.get(key)) {
|
|
607
|
+
if (!w.immediate)
|
|
608
|
+
continue;
|
|
609
|
+
try {
|
|
610
|
+
w.method.call(instance, undefined, instance[propName], propName);
|
|
611
|
+
}
|
|
612
|
+
catch (e) {
|
|
613
|
+
console.error(`Error in @watch('${key}') method ${w.methodName}:`, e);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
549
619
|
/**
|
|
550
620
|
* Create a debounced version of a function
|
|
551
621
|
*/
|
|
@@ -579,6 +649,41 @@ function createThrottled(fn, delay) {
|
|
|
579
649
|
}
|
|
580
650
|
};
|
|
581
651
|
}
|
|
652
|
+
/**
|
|
653
|
+
* Resolve a scope option to a concrete EventTarget.
|
|
654
|
+
*
|
|
655
|
+
* - undefined → host element (default behavior)
|
|
656
|
+
* - 'global' → `document`
|
|
657
|
+
* - string → `host.closest(selector)` (nearest matching ancestor)
|
|
658
|
+
* - EventTarget instance → that target directly
|
|
659
|
+
* - resolver function → called with host as `this`, returns target or null
|
|
660
|
+
*
|
|
661
|
+
* Returns null when the scope cannot be resolved (e.g. selector matches no
|
|
662
|
+
* ancestor, resolver returns null). Caller decides how to handle null —
|
|
663
|
+
* typically warn in dev and skip listener attachment.
|
|
664
|
+
*/
|
|
665
|
+
function resolveScope(host, scope) {
|
|
666
|
+
if (scope === undefined || scope === null)
|
|
667
|
+
return host;
|
|
668
|
+
if (scope === 'global')
|
|
669
|
+
return typeof document !== 'undefined' ? document : null;
|
|
670
|
+
if (typeof scope === 'function') {
|
|
671
|
+
try {
|
|
672
|
+
return scope.call(host) || null;
|
|
673
|
+
}
|
|
674
|
+
catch {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (typeof scope === 'string') {
|
|
679
|
+
return host.closest?.(scope) || null;
|
|
680
|
+
}
|
|
681
|
+
// Direct EventTarget instance.
|
|
682
|
+
if (scope && typeof scope.addEventListener === 'function') {
|
|
683
|
+
return scope;
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
582
687
|
function valueToAttribute(value, propertyOptions, initialValue) {
|
|
583
688
|
// Handle null/undefined/false values and empty arrays
|
|
584
689
|
if (value === null || value === undefined || value === false ||
|
|
@@ -996,13 +1101,11 @@ function request(requestName, options) {
|
|
|
996
1101
|
return finalValue;
|
|
997
1102
|
}
|
|
998
1103
|
catch (error) {
|
|
999
|
-
//
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
throw generatorError;
|
|
1005
|
-
}
|
|
1104
|
+
// Drive the generator's own catch block and return whatever it recovers
|
|
1105
|
+
// with (e.g. a cached fallback). If the generator re-throws instead,
|
|
1106
|
+
// generator.throw rejects and the error propagates to the caller.
|
|
1107
|
+
const { value: recovered } = await generator.throw(error);
|
|
1108
|
+
return recovered;
|
|
1006
1109
|
}
|
|
1007
1110
|
}; // Close actualRequest function
|
|
1008
1111
|
// Debounce
|
|
@@ -1026,9 +1129,21 @@ function request(requestName, options) {
|
|
|
1026
1129
|
const now = Date.now();
|
|
1027
1130
|
const remaining = options.throttle - (now - throttleLastCall);
|
|
1028
1131
|
if (remaining <= 0) {
|
|
1132
|
+
// Cancelling the scheduled trailing call must not orphan the promises
|
|
1133
|
+
// queued for it — settle them with this fresh result. Also reset
|
|
1134
|
+
// throttleTimeout so later within-window calls don't attach to a dead
|
|
1135
|
+
// timer and hang.
|
|
1029
1136
|
clearTimeout(throttleTimeout);
|
|
1137
|
+
throttleTimeout = null;
|
|
1030
1138
|
throttleLastCall = now;
|
|
1031
|
-
|
|
1139
|
+
const orphaned = throttleTrailingResolvers.splice(0);
|
|
1140
|
+
const p = actualRequest();
|
|
1141
|
+
if (orphaned.length) {
|
|
1142
|
+
p.then((r) => { for (const x of orphaned)
|
|
1143
|
+
x.resolve(r); }, (e) => { for (const x of orphaned)
|
|
1144
|
+
x.reject(e); });
|
|
1145
|
+
}
|
|
1146
|
+
return p;
|
|
1032
1147
|
}
|
|
1033
1148
|
// Throttle: already has pending trailing call — attach to it
|
|
1034
1149
|
if (throttleTimeout) {
|
|
@@ -1129,10 +1244,20 @@ function setupResponseHandlers(instance, element) {
|
|
|
1129
1244
|
const now = Date.now();
|
|
1130
1245
|
const remaining = handler.options.throttle - (now - throttleLastCall);
|
|
1131
1246
|
if (remaining <= 0) {
|
|
1247
|
+
// Same as the @request side: settle any queued trailing callers with
|
|
1248
|
+
// this fresh result rather than orphaning their promises.
|
|
1132
1249
|
clearTimeout(throttleTimeout);
|
|
1133
1250
|
throttleTimeout = null;
|
|
1134
1251
|
throttleLastCall = now;
|
|
1135
|
-
|
|
1252
|
+
const orphaned = throttleTrailingResolvers.splice(0);
|
|
1253
|
+
throttleLatestArgs = null;
|
|
1254
|
+
const result = originalMethod(...args);
|
|
1255
|
+
if (orphaned.length) {
|
|
1256
|
+
Promise.resolve(result).then((r) => { for (const x of orphaned)
|
|
1257
|
+
x.resolve(r); }, (e) => { for (const x of orphaned)
|
|
1258
|
+
x.reject(e); });
|
|
1259
|
+
}
|
|
1260
|
+
return result;
|
|
1136
1261
|
}
|
|
1137
1262
|
// Remember the LATEST args so the trailing call uses fresh input,
|
|
1138
1263
|
// and queue every suppressed caller's resolver so they all receive
|
|
@@ -1239,7 +1364,10 @@ function css(strings, ...values) {
|
|
|
1239
1364
|
// Combine strings and values into final CSS text
|
|
1240
1365
|
let cssText = strings[0];
|
|
1241
1366
|
for (let i = 0; i < values.length; i++) {
|
|
1242
|
-
|
|
1367
|
+
// Inline a nested css`` result by its text so style fragments compose;
|
|
1368
|
+
// String() on a CSSResult object would yield "[object Object]".
|
|
1369
|
+
const value = values[i];
|
|
1370
|
+
cssText += (isCSSResult(value) ? value.cssText : String(value)) + strings[i + 1];
|
|
1243
1371
|
}
|
|
1244
1372
|
const result = {
|
|
1245
1373
|
_$litType$: CSS_RESULT,
|
|
@@ -1390,6 +1518,20 @@ class Template {
|
|
|
1390
1518
|
const attrStrings = value.split(marker);
|
|
1391
1519
|
// Number of expressions = number of markers = attrStrings.length - 1
|
|
1392
1520
|
const expressionCount = attrStrings.length - 1;
|
|
1521
|
+
// Property/boolean/event bindings take a SINGLE expression — they
|
|
1522
|
+
// can't concatenate static text (a `.prop` is an object, not a
|
|
1523
|
+
// string). They consume the first expression; any static text or
|
|
1524
|
+
// extra expressions are ignored. But the value index must still
|
|
1525
|
+
// advance by the true marker count, or every later binding shifts.
|
|
1526
|
+
const isSingleExpressionBinding = /^@@?|^[.?]/.test(originalName);
|
|
1527
|
+
if (isSingleExpressionBinding) {
|
|
1528
|
+
const hasStaticText = attrStrings.some((s) => s !== '');
|
|
1529
|
+
if (expressionCount > 1 || hasStaticText) {
|
|
1530
|
+
console.warn(`snice: binding "${originalName}" takes a single expression; ` +
|
|
1531
|
+
`static text and extra interpolations are ignored. ` +
|
|
1532
|
+
`Use a plain attribute for string interpolation.`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1393
1535
|
if (originalName.startsWith('@@')) {
|
|
1394
1536
|
// Escaped event binding for events with @ in the name (e.g., @@snice/event -> @snice/event)
|
|
1395
1537
|
this.parts.push({
|
|
@@ -1398,7 +1540,7 @@ class Template {
|
|
|
1398
1540
|
name: originalName.slice(1), // Keep the @ in the event name
|
|
1399
1541
|
element
|
|
1400
1542
|
});
|
|
1401
|
-
partIndex +=
|
|
1543
|
+
partIndex += expressionCount;
|
|
1402
1544
|
}
|
|
1403
1545
|
else if (originalName.startsWith('@')) {
|
|
1404
1546
|
// Event binding (single value only)
|
|
@@ -1408,7 +1550,7 @@ class Template {
|
|
|
1408
1550
|
name: originalName.slice(1),
|
|
1409
1551
|
element
|
|
1410
1552
|
});
|
|
1411
|
-
partIndex +=
|
|
1553
|
+
partIndex += expressionCount;
|
|
1412
1554
|
}
|
|
1413
1555
|
else if (originalName.startsWith('.')) {
|
|
1414
1556
|
// Property binding (single value only)
|
|
@@ -1418,7 +1560,7 @@ class Template {
|
|
|
1418
1560
|
name: originalName.slice(1),
|
|
1419
1561
|
element
|
|
1420
1562
|
});
|
|
1421
|
-
partIndex +=
|
|
1563
|
+
partIndex += expressionCount;
|
|
1422
1564
|
}
|
|
1423
1565
|
else if (originalName.startsWith('?')) {
|
|
1424
1566
|
// Boolean attribute (single value only)
|
|
@@ -1428,7 +1570,7 @@ class Template {
|
|
|
1428
1570
|
name: originalName.slice(1),
|
|
1429
1571
|
element
|
|
1430
1572
|
});
|
|
1431
|
-
partIndex +=
|
|
1573
|
+
partIndex += expressionCount;
|
|
1432
1574
|
}
|
|
1433
1575
|
else {
|
|
1434
1576
|
// Regular attribute - supports multiple interpolations
|
|
@@ -1903,7 +2045,13 @@ class NodePart extends Part {
|
|
|
1903
2045
|
* Commit unsafe HTML string
|
|
1904
2046
|
*/
|
|
1905
2047
|
_commitUnsafeHTML(value) {
|
|
1906
|
-
//
|
|
2048
|
+
// Can't diff arbitrary HTML, but when the STRING is unchanged the DOM is
|
|
2049
|
+
// already correct — skip the clear+reparse so live state (typed input
|
|
2050
|
+
// values, focus, scroll) inside the block survives unrelated re-renders.
|
|
2051
|
+
const prev = this._committedValue;
|
|
2052
|
+
if (isUnsafeHTML(prev) && prev.html === value.html) {
|
|
2053
|
+
return;
|
|
2054
|
+
}
|
|
1907
2055
|
this._clear();
|
|
1908
2056
|
const temp = document.createElement('template');
|
|
1909
2057
|
temp.innerHTML = value.html;
|
|
@@ -2088,7 +2236,9 @@ class EventPart extends Part {
|
|
|
2088
2236
|
// Supports both dot notation (@keydown.enter) and colon notation (@keydown:Enter) to match @on decorator
|
|
2089
2237
|
// Only parse colons for keyboard events, not custom events
|
|
2090
2238
|
const isKeyboardEvent = ['keydown', 'keyup', 'keypress'].includes(eventName.split('.')[0].split(':')[0]);
|
|
2091
|
-
|
|
2239
|
+
// Only keyboard events split on `.`/`:` into a key filter — a custom event
|
|
2240
|
+
// name may legitimately contain a dot (e.g. `app.ready`) and must be kept whole.
|
|
2241
|
+
const dotIndex = isKeyboardEvent ? eventName.indexOf('.') : -1;
|
|
2092
2242
|
const colonIndex = isKeyboardEvent ? eventName.indexOf(':') : -1;
|
|
2093
2243
|
// Use whichever delimiter comes first (dot or colon)
|
|
2094
2244
|
const delimiterIndex = dotIndex > 0 && colonIndex > 0
|
|
@@ -2102,6 +2252,7 @@ class EventPart extends Part {
|
|
|
2102
2252
|
}
|
|
2103
2253
|
else {
|
|
2104
2254
|
this.eventName = eventName;
|
|
2255
|
+
warnIfModifierMisuse(eventName);
|
|
2105
2256
|
}
|
|
2106
2257
|
}
|
|
2107
2258
|
commit(value) {
|
|
@@ -2127,17 +2278,19 @@ class EventPart extends Part {
|
|
|
2127
2278
|
}
|
|
2128
2279
|
if (typeof value !== 'function')
|
|
2129
2280
|
return;
|
|
2130
|
-
// Auto-bind to host element (the custom element with shadow root)
|
|
2131
|
-
if (!this.host) {
|
|
2132
|
-
const rootNode = this.element.getRootNode();
|
|
2133
|
-
this.host = rootNode.host || null;
|
|
2134
|
-
}
|
|
2135
|
-
const context = this.host || null;
|
|
2136
2281
|
const keyFilter = this.keyFilter;
|
|
2137
2282
|
this.listener = ((event) => {
|
|
2138
2283
|
if (keyFilter && !matchesKeyboardFilter(event, keyFilter))
|
|
2139
2284
|
return;
|
|
2140
|
-
|
|
2285
|
+
// Resolve the host (the custom element owning the shadow root) at dispatch
|
|
2286
|
+
// time, not bind time: a binding inside an initially-hidden <if>/<case>
|
|
2287
|
+
// branch commits while off-DOM, where getRootNode() has no host — caching
|
|
2288
|
+
// that null permanently would call the handler with `this = null` once the
|
|
2289
|
+
// branch is shown. By the time an event fires, the element is in the DOM.
|
|
2290
|
+
if (!this.host) {
|
|
2291
|
+
this.host = this.element.getRootNode().host || null;
|
|
2292
|
+
}
|
|
2293
|
+
value.call(this.host || null, event);
|
|
2141
2294
|
});
|
|
2142
2295
|
this.element.addEventListener(this.eventName, this.listener);
|
|
2143
2296
|
}
|
|
@@ -2156,6 +2309,25 @@ class EventPart extends Part {
|
|
|
2156
2309
|
* "ctrl+shift+s" -> { key: "s", ctrl: true, shift: true }
|
|
2157
2310
|
* "~enter" -> { key: "Enter", anyModifiers: true }
|
|
2158
2311
|
*/
|
|
2312
|
+
// once/preventDefault/stopPropagation/capture/passive are @on() OPTIONS, not
|
|
2313
|
+
// template modifiers. A dotted non-keyboard event whose suffix is one of these
|
|
2314
|
+
// is almost certainly a mistake (e.g. `@click.once`) — it binds a listener for
|
|
2315
|
+
// an event type the DOM never dispatches. Warn instead of failing silently. A
|
|
2316
|
+
// legitimate custom event name like `app.ready` won't match and stays quiet.
|
|
2317
|
+
const MODIFIER_WORDS = new Set([
|
|
2318
|
+
'once', 'prevent', 'preventdefault', 'stop', 'stoppropagation', 'capture', 'passive',
|
|
2319
|
+
]);
|
|
2320
|
+
function warnIfModifierMisuse(eventName) {
|
|
2321
|
+
const dot = eventName.lastIndexOf('.');
|
|
2322
|
+
if (dot < 0)
|
|
2323
|
+
return;
|
|
2324
|
+
const suffix = eventName.slice(dot + 1).toLowerCase();
|
|
2325
|
+
if (!MODIFIER_WORDS.has(suffix))
|
|
2326
|
+
return;
|
|
2327
|
+
console.warn(`snice: "@${eventName}" is not a valid modifier — once/preventDefault/` +
|
|
2328
|
+
`stopPropagation/capture are @on() options, not template modifiers. ` +
|
|
2329
|
+
`This registers a listener for event "${eventName}", which never fires.`);
|
|
2330
|
+
}
|
|
2159
2331
|
function parseKeyboardFilter(spec) {
|
|
2160
2332
|
// Handle ~ prefix for matching regardless of modifiers
|
|
2161
2333
|
const anyModifiers = spec.startsWith('~');
|
|
@@ -2258,9 +2430,14 @@ class ConditionalIfPart extends Part {
|
|
|
2258
2430
|
// Insert markers where <if> is
|
|
2259
2431
|
parent.insertBefore(this.startNode, ifElement);
|
|
2260
2432
|
parent.insertBefore(this.endNode, ifElement.nextSibling);
|
|
2261
|
-
//
|
|
2433
|
+
// Park <if>'s children in an off-DOM fragment immediately, rather than
|
|
2434
|
+
// leaving them in the live template fragment. Otherwise a branch that is
|
|
2435
|
+
// hidden on first render would still connect its children (when the
|
|
2436
|
+
// template mounts) and then disconnect them (on the first commit) — firing
|
|
2437
|
+
// mount side effects spuriously. commit(true) inserts them when shown.
|
|
2438
|
+
this.childFragment = document.createDocumentFragment();
|
|
2262
2439
|
while (ifElement.firstChild) {
|
|
2263
|
-
|
|
2440
|
+
this.childFragment.appendChild(ifElement.firstChild);
|
|
2264
2441
|
}
|
|
2265
2442
|
// Remove the <if> element from DOM
|
|
2266
2443
|
parent.removeChild(ifElement);
|
|
@@ -2341,16 +2518,24 @@ class ConditionalCasePart extends Part {
|
|
|
2341
2518
|
parent.removeChild(caseElement);
|
|
2342
2519
|
}
|
|
2343
2520
|
commit(value) {
|
|
2344
|
-
|
|
2521
|
+
// Dirty-check on the SELECTED BRANCH, not the raw value. A value whose type
|
|
2522
|
+
// changed but stringifies the same (number 1 vs string '1' — e.g. a numeric
|
|
2523
|
+
// default replaced by a router param, which is always a string) selects the
|
|
2524
|
+
// same branch, so tearing it down and re-inserting it would needlessly
|
|
2525
|
+
// restart media/animations and lose focus inside it.
|
|
2526
|
+
const valueStr = String(value);
|
|
2527
|
+
const hasBranch = this.branches.has(valueStr);
|
|
2528
|
+
const nextKey = hasBranch ? valueStr : (this.defaultBranch ? '__default__' : null);
|
|
2529
|
+
if (this.currentKey === nextKey) {
|
|
2530
|
+
this.value = value;
|
|
2345
2531
|
return;
|
|
2532
|
+
}
|
|
2346
2533
|
this.value = value;
|
|
2347
|
-
const valueStr = String(value);
|
|
2348
2534
|
// Move current content back to its fragment
|
|
2349
2535
|
this._collectCurrent();
|
|
2350
2536
|
// Insert matching branch
|
|
2351
|
-
const hasBranch = this.branches.has(valueStr);
|
|
2352
2537
|
const matchingFragment = hasBranch ? this.branches.get(valueStr) : this.defaultBranch;
|
|
2353
|
-
this.currentKey =
|
|
2538
|
+
this.currentKey = nextKey;
|
|
2354
2539
|
if (matchingFragment && matchingFragment.hasChildNodes()) {
|
|
2355
2540
|
this.startNode.parentNode.insertBefore(matchingFragment, this.endNode);
|
|
2356
2541
|
}
|
|
@@ -2528,7 +2713,9 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2528
2713
|
// Supports both dot notation (@keydown.enter) and colon notation (@keydown:Enter)
|
|
2529
2714
|
// Only parse colons for keyboard events, not custom events
|
|
2530
2715
|
const isKeyboardEvent = ['keydown', 'keyup', 'keypress'].includes(handler.eventName.split('.')[0].split(':')[0]);
|
|
2531
|
-
|
|
2716
|
+
// Only keyboard events split on `.`/`:` into a key filter — a custom event
|
|
2717
|
+
// name may legitimately contain a dot (e.g. `app.ready`) and must be kept whole.
|
|
2718
|
+
const dotIndex = isKeyboardEvent ? handler.eventName.indexOf('.') : -1;
|
|
2532
2719
|
const colonIndex = isKeyboardEvent ? handler.eventName.indexOf(':') : -1;
|
|
2533
2720
|
const delimiterIndex = dotIndex > 0 && colonIndex > 0
|
|
2534
2721
|
? Math.min(dotIndex, colonIndex)
|
|
@@ -2539,6 +2726,8 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2539
2726
|
const keyModifier = delimiterIndex > 0
|
|
2540
2727
|
? handler.eventName.substring(delimiterIndex + 1)
|
|
2541
2728
|
: null;
|
|
2729
|
+
if (delimiterIndex <= 0)
|
|
2730
|
+
warnIfModifierMisuse(handler.eventName);
|
|
2542
2731
|
// Apply debounce (takes precedence over throttle)
|
|
2543
2732
|
if (handlerOptions.debounce && handlerOptions.debounce > 0) {
|
|
2544
2733
|
boundMethod = createDebounced(boundMethod, handlerOptions.debounce);
|
|
@@ -2565,10 +2754,27 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2565
2754
|
};
|
|
2566
2755
|
// Apply key modifier wrapper
|
|
2567
2756
|
const keyModifierMethod = createKeyModifierHandler(boundMethod);
|
|
2757
|
+
// Resolve scope: explicit `scope` option redirects the listener target
|
|
2758
|
+
// to document / ancestor / arbitrary EventTarget. Default = host element.
|
|
2759
|
+
const hasExplicitScope = handlerOptions.scope !== undefined;
|
|
2760
|
+
let scopedTarget = null;
|
|
2761
|
+
if (hasExplicitScope) {
|
|
2762
|
+
scopedTarget = resolveScope(targetElement, handlerOptions.scope);
|
|
2763
|
+
if (!scopedTarget) {
|
|
2764
|
+
// Dev warning — listener silently dropped when scope cannot resolve.
|
|
2765
|
+
// Skip attachment so we don't bind to the wrong target.
|
|
2766
|
+
console.warn(`[snice/@on] scope did not resolve for "${handler.eventName}" on ${handler.methodName} — listener skipped.`);
|
|
2767
|
+
continue;
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2568
2770
|
// Main event handler with error handling and event delegation
|
|
2569
2771
|
if (handler.selector) {
|
|
2570
|
-
// Delegated event handling
|
|
2571
|
-
|
|
2772
|
+
// Delegated event handling.
|
|
2773
|
+
// - Default eventRoot: shadow root if present, else host element.
|
|
2774
|
+
// - With explicit scope: attach to the scoped target instead.
|
|
2775
|
+
const eventRoot = hasExplicitScope
|
|
2776
|
+
? scopedTarget
|
|
2777
|
+
: (targetElement.shadowRoot || targetElement);
|
|
2572
2778
|
const delegatedHandler = (event) => {
|
|
2573
2779
|
const target = event.target;
|
|
2574
2780
|
const matchingElement = (target.matches && target.matches(handler.selector) && target) ||
|
|
@@ -2608,10 +2814,11 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2608
2814
|
});
|
|
2609
2815
|
}
|
|
2610
2816
|
else {
|
|
2611
|
-
// Direct event handling
|
|
2612
|
-
//
|
|
2613
|
-
//
|
|
2614
|
-
|
|
2817
|
+
// Direct event handling.
|
|
2818
|
+
// - Default: shadow root + host element (so events inside shadow and on
|
|
2819
|
+
// the host itself both fire). A per-handler Symbol dedupes.
|
|
2820
|
+
// - With explicit scope: attach to the scoped target ONLY (no duplication).
|
|
2821
|
+
const shadowRoot = hasExplicitScope ? null : targetElement.shadowRoot;
|
|
2615
2822
|
// Per-handler private Symbol so dedup is scoped to THIS handler's two
|
|
2616
2823
|
// listeners (shadowRoot + host). Using Symbol.for() with the method name
|
|
2617
2824
|
// would collide across components that share a method name (e.g. a parent
|
|
@@ -2639,6 +2846,16 @@ function setupEventHandlers(instance, targetElement) {
|
|
|
2639
2846
|
once: handlerOptions.once || false,
|
|
2640
2847
|
passive: handlerOptions.passive || false,
|
|
2641
2848
|
};
|
|
2849
|
+
if (hasExplicitScope) {
|
|
2850
|
+
scopedTarget.addEventListener(baseEventName, wrappedMethod, listenerOptions);
|
|
2851
|
+
instance[CLEANUP].events.push({
|
|
2852
|
+
target: scopedTarget,
|
|
2853
|
+
eventName: baseEventName,
|
|
2854
|
+
handler: wrappedMethod,
|
|
2855
|
+
options: listenerOptions,
|
|
2856
|
+
});
|
|
2857
|
+
continue;
|
|
2858
|
+
}
|
|
2642
2859
|
if (shadowRoot) {
|
|
2643
2860
|
// Listen on shadow root for events inside shadow DOM
|
|
2644
2861
|
shadowRoot.addEventListener(baseEventName, wrappedMethod, listenerOptions);
|
|
@@ -2801,11 +3018,25 @@ async function attachController(element, controllerName) {
|
|
|
2801
3018
|
}),
|
|
2802
3019
|
]);
|
|
2803
3020
|
}
|
|
3021
|
+
catch (error) {
|
|
3022
|
+
// Attach never got past `ready` (aborted or deadline). Roll back the stored
|
|
3023
|
+
// references so nothing is left pointing at a controller that never
|
|
3024
|
+
// attached — otherwise a later attach short-circuits as "already attached"
|
|
3025
|
+
// and a detach would run detach() on it.
|
|
3026
|
+
if (element[CONTROLLER_KEY] === controllerInstance) {
|
|
3027
|
+
delete element[CONTROLLER_KEY];
|
|
3028
|
+
delete element[CONTROLLER_NAME_KEY];
|
|
3029
|
+
delete element[CONTROLLER_OPERATIONS];
|
|
3030
|
+
}
|
|
3031
|
+
throw error;
|
|
3032
|
+
}
|
|
2804
3033
|
finally {
|
|
2805
3034
|
if (element[CONTROLLER_ABORT] === abort) {
|
|
2806
3035
|
delete element[CONTROLLER_ABORT];
|
|
2807
3036
|
}
|
|
2808
3037
|
}
|
|
3038
|
+
// Past `ready` — mark the controller so detach knows attach was actually run.
|
|
3039
|
+
controllerInstance[CONTROLLER_ATTACHED] = true;
|
|
2809
3040
|
// Run attach in the controller's scope
|
|
2810
3041
|
await scope.runOperation(async () => {
|
|
2811
3042
|
await controllerInstance.attach(element);
|
|
@@ -2836,6 +3067,19 @@ async function detachController(element) {
|
|
|
2836
3067
|
if (!controllerInstance) {
|
|
2837
3068
|
return;
|
|
2838
3069
|
}
|
|
3070
|
+
// Claim the controller immediately — before any await — so a second detach
|
|
3071
|
+
// that overlaps this one (e.g. disconnect's fire-and-forget detach racing a
|
|
3072
|
+
// controller reassignment) finds no instance and returns, instead of running
|
|
3073
|
+
// detach() a second time on the same controller. The rest of this function
|
|
3074
|
+
// works off the locals captured above.
|
|
3075
|
+
delete element[CONTROLLER_KEY];
|
|
3076
|
+
delete element[CONTROLLER_NAME_KEY];
|
|
3077
|
+
delete element[CONTROLLER_OPERATIONS];
|
|
3078
|
+
// A controller whose attach() was aborted before it ever ran must not have
|
|
3079
|
+
// detach() (or the teardown/event) run on it.
|
|
3080
|
+
if (!controllerInstance[CONTROLLER_ATTACHED]) {
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
2839
3083
|
// Run detach in the controller's scope
|
|
2840
3084
|
if (scope) {
|
|
2841
3085
|
await scope.runOperation(async () => {
|
|
@@ -2858,9 +3102,6 @@ async function detachController(element) {
|
|
|
2858
3102
|
}
|
|
2859
3103
|
// Clean up router context reference
|
|
2860
3104
|
delete controllerInstance[ROUTER_CONTEXT];
|
|
2861
|
-
delete element[CONTROLLER_KEY];
|
|
2862
|
-
delete element[CONTROLLER_NAME_KEY];
|
|
2863
|
-
delete element[CONTROLLER_OPERATIONS];
|
|
2864
3105
|
element.dispatchEvent(new CustomEvent('controller-detached', {
|
|
2865
3106
|
detail: { name: controllerName, controller: controllerInstance }
|
|
2866
3107
|
}));
|
|
@@ -3023,19 +3264,23 @@ function setupContextHandler(element) {
|
|
|
3023
3264
|
const wrappedMethodName = `__wrapped_${methodName}`;
|
|
3024
3265
|
// Create wrapped method with timing controls
|
|
3025
3266
|
element[wrappedMethodName] = function (context) {
|
|
3026
|
-
//
|
|
3027
|
-
|
|
3267
|
+
// `once` is tracked PER method (not a single element-level flag) so
|
|
3268
|
+
// multiple once handlers on the same element each fire.
|
|
3269
|
+
const called = element[CONTEXT_CALLED] ||
|
|
3270
|
+
(element[CONTEXT_CALLED] = new Set());
|
|
3271
|
+
if (options.once && called.has(methodName)) {
|
|
3028
3272
|
return;
|
|
3029
3273
|
}
|
|
3030
3274
|
const callMethod = () => {
|
|
3031
3275
|
method.call(element, context);
|
|
3032
3276
|
// Handle once option
|
|
3033
3277
|
if (options.once) {
|
|
3034
|
-
|
|
3035
|
-
// Unregister
|
|
3278
|
+
called.add(methodName);
|
|
3279
|
+
// Unregister ONLY this handler, not the whole element — otherwise the
|
|
3280
|
+
// element's other @context handlers would stop receiving updates.
|
|
3036
3281
|
const ctx = element[NAVIGATION_CONTEXT_INSTANCE];
|
|
3037
3282
|
if (ctx && typeof ctx[CONTEXT_UNREGISTER] === 'function') {
|
|
3038
|
-
ctx[CONTEXT_UNREGISTER](element);
|
|
3283
|
+
ctx[CONTEXT_UNREGISTER](element, wrappedMethodName);
|
|
3039
3284
|
}
|
|
3040
3285
|
}
|
|
3041
3286
|
};
|
|
@@ -3138,9 +3383,14 @@ class RenderScheduler {
|
|
|
3138
3383
|
this.pending.clear();
|
|
3139
3384
|
this.scheduled = false;
|
|
3140
3385
|
for (const element of elements) {
|
|
3141
|
-
// Skip elements that were disconnected between scheduling and flush
|
|
3142
|
-
|
|
3386
|
+
// Skip elements that were disconnected between scheduling and flush, but
|
|
3387
|
+
// remember the dropped render so reconnect can replay it — otherwise the
|
|
3388
|
+
// shadow DOM stays stale for a property changed while detached. The flag
|
|
3389
|
+
// lives on the element, so it's GC'd with it (no leak if never reattached).
|
|
3390
|
+
if (!element.isConnected) {
|
|
3391
|
+
element[PENDING_RECONNECT_RENDER] = true;
|
|
3143
3392
|
continue;
|
|
3393
|
+
}
|
|
3144
3394
|
const options = element[RENDER_OPTIONS] || {};
|
|
3145
3395
|
performRender(element, options);
|
|
3146
3396
|
}
|
|
@@ -3159,6 +3409,19 @@ function flushRenderCallbacks(element) {
|
|
|
3159
3409
|
element[RENDER_CALLBACKS] = [];
|
|
3160
3410
|
cbs.forEach(cb => cb());
|
|
3161
3411
|
}
|
|
3412
|
+
/**
|
|
3413
|
+
* Backstop against a render() that mutates an observed property, which makes
|
|
3414
|
+
* the property setter request another render from inside the current one. In
|
|
3415
|
+
* `sync` mode that re-enters performRender synchronously and, unchecked,
|
|
3416
|
+
* recurses to a stack overflow. The counter caps synchronous nesting and turns
|
|
3417
|
+
* a silent crash into an actionable error. Normal renders never nest (child
|
|
3418
|
+
* renders defer to a microtask), so this stays at depth 1 in the common case.
|
|
3419
|
+
*
|
|
3420
|
+
* The depth is tracked PER ELEMENT (on RENDER_DEPTH), not globally: a runaway
|
|
3421
|
+
* component must not consume a shared budget and reject an unrelated component
|
|
3422
|
+
* that happens to render inside its call stack.
|
|
3423
|
+
*/
|
|
3424
|
+
const MAX_RENDER_DEPTH = 50;
|
|
3162
3425
|
/**
|
|
3163
3426
|
* Perform the actual render of an element
|
|
3164
3427
|
*/
|
|
@@ -3170,6 +3433,14 @@ function performRender(element, options, precomputedResult) {
|
|
|
3170
3433
|
if (options.once && element[RENDER_INSTANCE]) {
|
|
3171
3434
|
return;
|
|
3172
3435
|
}
|
|
3436
|
+
const depth = (element[RENDER_DEPTH] ?? 0);
|
|
3437
|
+
if (depth >= MAX_RENDER_DEPTH) {
|
|
3438
|
+
const tag = element.tagName ? element.tagName.toLowerCase() : 'element';
|
|
3439
|
+
console.error(`snice: maximum render depth (${MAX_RENDER_DEPTH}) exceeded for <${tag}>. ` +
|
|
3440
|
+
`render() is mutating an observed property, causing an infinite render loop.`);
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
element[RENDER_DEPTH] = depth + 1;
|
|
3173
3444
|
try {
|
|
3174
3445
|
const result = precomputedResult !== undefined ? precomputedResult : renderMethod.call(element);
|
|
3175
3446
|
if (!element.shadowRoot)
|
|
@@ -3195,14 +3466,18 @@ function performRender(element, options, precomputedResult) {
|
|
|
3195
3466
|
flushRenderCallbacks(element);
|
|
3196
3467
|
return;
|
|
3197
3468
|
}
|
|
3198
|
-
// Different template or first render.
|
|
3199
|
-
//
|
|
3200
|
-
//
|
|
3469
|
+
// Different template or first render. Keep only the framework's own
|
|
3470
|
+
// fallback <style> tags (marked data-snice-style) so the @styles path
|
|
3471
|
+
// (Safari <=16 / jsdom / SSR — no adoptedStyleSheets) survives a template
|
|
3472
|
+
// switch; template-emitted <style> tags are removed and re-created by the
|
|
3473
|
+
// new instance, so they don't accumulate.
|
|
3201
3474
|
if (instance) {
|
|
3202
3475
|
const root = element.shadowRoot;
|
|
3203
3476
|
const toRemove = [];
|
|
3204
3477
|
for (const child of Array.from(root.childNodes)) {
|
|
3205
|
-
if (child.nodeType === 1 &&
|
|
3478
|
+
if (child.nodeType === 1 &&
|
|
3479
|
+
child.tagName === 'STYLE' &&
|
|
3480
|
+
child.hasAttribute('data-snice-style'))
|
|
3206
3481
|
continue;
|
|
3207
3482
|
toRemove.push(child);
|
|
3208
3483
|
}
|
|
@@ -3218,6 +3493,9 @@ function performRender(element, options, precomputedResult) {
|
|
|
3218
3493
|
catch (error) {
|
|
3219
3494
|
console.error('Error rendering element:', error);
|
|
3220
3495
|
}
|
|
3496
|
+
finally {
|
|
3497
|
+
element[RENDER_DEPTH] = depth;
|
|
3498
|
+
}
|
|
3221
3499
|
}
|
|
3222
3500
|
/**
|
|
3223
3501
|
* Request a render for an element
|
|
@@ -3271,6 +3549,33 @@ function requestRender(element, immediate = false) {
|
|
|
3271
3549
|
// Normal rendering (with microtask batching unless sync)
|
|
3272
3550
|
renderScheduler.schedule(element, options);
|
|
3273
3551
|
}
|
|
3552
|
+
/**
|
|
3553
|
+
* Clear any pending debounce/throttle render timers on an element (e.g. on
|
|
3554
|
+
* disconnect, so they don't fire on a dead element and retain it until they
|
|
3555
|
+
* expire). Returns true if a render was actually pending, so the caller can
|
|
3556
|
+
* arrange a replay on reconnect via PENDING_RECONNECT_RENDER.
|
|
3557
|
+
*/
|
|
3558
|
+
function clearRenderTimers(element) {
|
|
3559
|
+
const timers = element[RENDER_TIMERS];
|
|
3560
|
+
if (!timers)
|
|
3561
|
+
return false;
|
|
3562
|
+
let hadPending = false;
|
|
3563
|
+
if (timers.debounce) {
|
|
3564
|
+
clearTimeout(timers.debounce);
|
|
3565
|
+
timers.debounce = null;
|
|
3566
|
+
hadPending = true;
|
|
3567
|
+
}
|
|
3568
|
+
if (timers.throttleTimer) {
|
|
3569
|
+
clearTimeout(timers.throttleTimer);
|
|
3570
|
+
timers.throttleTimer = null;
|
|
3571
|
+
hadPending = true;
|
|
3572
|
+
}
|
|
3573
|
+
// Deliberately do NOT reset timers.lastThrottle: a plain DOM move is a
|
|
3574
|
+
// disconnect+reconnect, and zeroing the cooldown would let the next render
|
|
3575
|
+
// fire inside the throttle window. Preserving it keeps throttle honest
|
|
3576
|
+
// across moves; on a true disconnect the element is discarded anyway.
|
|
3577
|
+
return hadPending;
|
|
3578
|
+
}
|
|
3274
3579
|
/**
|
|
3275
3580
|
* @render decorator for component rendering
|
|
3276
3581
|
*
|
|
@@ -3393,9 +3698,12 @@ function applyStyles(element) {
|
|
|
3393
3698
|
element.shadowRoot.adoptedStyleSheets = allResults.map(r => r.styleSheet);
|
|
3394
3699
|
return;
|
|
3395
3700
|
}
|
|
3396
|
-
// Fallback — one <style> tag per stylesheet, preserving cascade order
|
|
3701
|
+
// Fallback — one <style> tag per stylesheet, preserving cascade order.
|
|
3702
|
+
// Marked so the template-switch cleanup keeps these (framework styles) but
|
|
3703
|
+
// still removes template-emitted <style> tags (which would otherwise pile up).
|
|
3397
3704
|
for (const r of allResults) {
|
|
3398
3705
|
const style = document.createElement('style');
|
|
3706
|
+
style.setAttribute('data-snice-style', '');
|
|
3399
3707
|
style.textContent = r.cssText;
|
|
3400
3708
|
element.shadowRoot.appendChild(style);
|
|
3401
3709
|
}
|
|
@@ -3405,6 +3713,125 @@ function applyStyles(element) {
|
|
|
3405
3713
|
}
|
|
3406
3714
|
}
|
|
3407
3715
|
|
|
3716
|
+
// @dispatch decorator - auto-dispatches custom events from method return values
|
|
3717
|
+
/**
|
|
3718
|
+
* Decorator that automatically dispatches a custom event after a method is called.
|
|
3719
|
+
* The return value of the method becomes the event detail.
|
|
3720
|
+
*
|
|
3721
|
+
* @param eventName The name of the event to dispatch
|
|
3722
|
+
* @param options Optional configuration extending EventInit
|
|
3723
|
+
*/
|
|
3724
|
+
function dispatch(eventName, options) {
|
|
3725
|
+
return function (originalMethod, _context) {
|
|
3726
|
+
return function (...args) {
|
|
3727
|
+
// Create timing wrappers for dispatch (per-instance)
|
|
3728
|
+
if (!this[DISPATCH_TIMERS]) {
|
|
3729
|
+
this[DISPATCH_TIMERS] = new Map();
|
|
3730
|
+
}
|
|
3731
|
+
const timerKey = `${eventName}_${_context.name}`;
|
|
3732
|
+
if (!this[DISPATCH_TIMERS].has(timerKey)) {
|
|
3733
|
+
this[DISPATCH_TIMERS].set(timerKey, {
|
|
3734
|
+
debounceTimeout: null,
|
|
3735
|
+
throttleLastCall: 0,
|
|
3736
|
+
throttleTimeout: null
|
|
3737
|
+
});
|
|
3738
|
+
}
|
|
3739
|
+
const timers = this[DISPATCH_TIMERS].get(timerKey);
|
|
3740
|
+
// Call the original method with preserved this context
|
|
3741
|
+
const result = originalMethod.apply(this, args);
|
|
3742
|
+
// Helper to dispatch the event
|
|
3743
|
+
const doDispatch = (detail) => {
|
|
3744
|
+
// Skip dispatch if result is undefined and dispatchOnUndefined is false
|
|
3745
|
+
if (detail === undefined && options?.dispatchOnUndefined === false) {
|
|
3746
|
+
return;
|
|
3747
|
+
}
|
|
3748
|
+
// Create event with spread operator for options
|
|
3749
|
+
const event = new CustomEvent(eventName, {
|
|
3750
|
+
bubbles: true, // Default to true for component events
|
|
3751
|
+
composed: true, // Allow crossing shadow DOM boundaries
|
|
3752
|
+
...options, // Spread all EventInit options
|
|
3753
|
+
detail
|
|
3754
|
+
});
|
|
3755
|
+
// Resolve scope target. Default is `this` (host element).
|
|
3756
|
+
// Explicit scope dispatches the event on another target — useful for
|
|
3757
|
+
// 'global' bus events on document, or routing through an ancestor.
|
|
3758
|
+
if (options?.scope !== undefined) {
|
|
3759
|
+
const target = resolveScope(this, options.scope);
|
|
3760
|
+
if (!target) {
|
|
3761
|
+
console.warn(`[snice/@dispatch] scope did not resolve for "${eventName}" — event not dispatched.`);
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
target.dispatchEvent(event);
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
this.dispatchEvent(event);
|
|
3768
|
+
};
|
|
3769
|
+
// Helper to handle timed dispatch
|
|
3770
|
+
const timedDispatch = (detail) => {
|
|
3771
|
+
if (options?.debounce) {
|
|
3772
|
+
clearTimeout(timers.debounceTimeout);
|
|
3773
|
+
timers.debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
|
|
3774
|
+
return;
|
|
3775
|
+
}
|
|
3776
|
+
if (!options?.throttle) {
|
|
3777
|
+
doDispatch(detail);
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
const now = Date.now();
|
|
3781
|
+
const remaining = options.throttle - (now - timers.throttleLastCall);
|
|
3782
|
+
if (remaining <= 0) {
|
|
3783
|
+
clearTimeout(timers.throttleTimeout);
|
|
3784
|
+
timers.throttleLastCall = now;
|
|
3785
|
+
doDispatch(detail);
|
|
3786
|
+
return;
|
|
3787
|
+
}
|
|
3788
|
+
// Record the LATEST detail so the trailing dispatch carries fresh
|
|
3789
|
+
// data, not the first-suppressed-call detail captured by closure.
|
|
3790
|
+
timers.latestDetail = detail;
|
|
3791
|
+
if (!timers.throttleTimeout) {
|
|
3792
|
+
timers.throttleTimeout = setTimeout(() => {
|
|
3793
|
+
timers.throttleLastCall = Date.now();
|
|
3794
|
+
timers.throttleTimeout = null;
|
|
3795
|
+
const d = timers.latestDetail;
|
|
3796
|
+
timers.latestDetail = undefined;
|
|
3797
|
+
doDispatch(d);
|
|
3798
|
+
}, remaining);
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
3801
|
+
// Handle async methods
|
|
3802
|
+
if (result instanceof Promise) {
|
|
3803
|
+
return result.then((resolvedResult) => {
|
|
3804
|
+
timedDispatch(resolvedResult);
|
|
3805
|
+
return resolvedResult;
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3808
|
+
// Sync method
|
|
3809
|
+
timedDispatch(result);
|
|
3810
|
+
return result;
|
|
3811
|
+
};
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3814
|
+
/**
|
|
3815
|
+
* Clear any pending debounce/throttle dispatch timers on an instance (e.g. on
|
|
3816
|
+
* disconnect, so a queued event doesn't fire into a detached node). A dispatch
|
|
3817
|
+
* is a one-shot signal, so pending ones are dropped, not replayed.
|
|
3818
|
+
*/
|
|
3819
|
+
function clearDispatchTimers(instance) {
|
|
3820
|
+
const timers = instance[DISPATCH_TIMERS];
|
|
3821
|
+
if (!timers)
|
|
3822
|
+
return;
|
|
3823
|
+
for (const t of timers.values()) {
|
|
3824
|
+
if (t.debounceTimeout)
|
|
3825
|
+
clearTimeout(t.debounceTimeout);
|
|
3826
|
+
if (t.throttleTimeout)
|
|
3827
|
+
clearTimeout(t.throttleTimeout);
|
|
3828
|
+
t.debounceTimeout = null;
|
|
3829
|
+
t.throttleTimeout = null;
|
|
3830
|
+
t.throttleLastCall = 0;
|
|
3831
|
+
t.latestDetail = undefined;
|
|
3832
|
+
}
|
|
3833
|
+
}
|
|
3834
|
+
|
|
3408
3835
|
/**
|
|
3409
3836
|
* Method decorators for common patterns
|
|
3410
3837
|
* @debounce, @throttle, @once, @memoize
|
|
@@ -3831,6 +4258,14 @@ function applyElementFunctionality(constructor) {
|
|
|
3831
4258
|
}
|
|
3832
4259
|
}
|
|
3833
4260
|
}
|
|
4261
|
+
// Replay a render that was dropped while disconnected. The scheduler
|
|
4262
|
+
// sets this flag when it skips a detached element, so a property changed
|
|
4263
|
+
// while detached still reaches the DOM on reattach. A plain move (no
|
|
4264
|
+
// pending render) leaves the flag unset, so we don't re-render needlessly.
|
|
4265
|
+
if (this[RENDER_METHOD] && this[PENDING_RECONNECT_RENDER]) {
|
|
4266
|
+
this[PENDING_RECONNECT_RENDER] = false;
|
|
4267
|
+
requestRender(this);
|
|
4268
|
+
}
|
|
3834
4269
|
return;
|
|
3835
4270
|
}
|
|
3836
4271
|
// Mark that properties are being initialized from attributes
|
|
@@ -3878,6 +4313,9 @@ function applyElementFunctionality(constructor) {
|
|
|
3878
4313
|
setupResponseHandlers(this, this);
|
|
3879
4314
|
setupContextHandler(this);
|
|
3880
4315
|
this[INITIALIZED] = true;
|
|
4316
|
+
// Now that the initial value is settled, give @watch immediate handlers
|
|
4317
|
+
// their one init call (before the first render, so derived state is ready).
|
|
4318
|
+
invokeImmediateWatchers(this, constructor);
|
|
3881
4319
|
if (originalConnectedCallback) {
|
|
3882
4320
|
originalConnectedCallback.call(this);
|
|
3883
4321
|
}
|
|
@@ -3972,6 +4410,17 @@ function applyElementFunctionality(constructor) {
|
|
|
3972
4410
|
// Cleanup pending @debounce / @throttle timers so they don't fire on a dead element
|
|
3973
4411
|
clearDebounceTimers(this);
|
|
3974
4412
|
clearThrottleTimers(this);
|
|
4413
|
+
// The render/dispatch debounce-throttle timers live in separate stores.
|
|
4414
|
+
// A pending render is replayed on reconnect (same flag the scheduler
|
|
4415
|
+
// uses); a pending dispatch is a one-shot signal, so it's dropped.
|
|
4416
|
+
if (clearRenderTimers(this)) {
|
|
4417
|
+
this[PENDING_RECONNECT_RENDER] = true;
|
|
4418
|
+
}
|
|
4419
|
+
clearDispatchTimers(this);
|
|
4420
|
+
// @moved / @adopted debounce-throttle timers, likewise dropped so they
|
|
4421
|
+
// don't fire on a dead element.
|
|
4422
|
+
clearLifecycleTimers(this, MOVED_TIMERS);
|
|
4423
|
+
clearLifecycleTimers(this, ADOPTED_TIMERS);
|
|
3975
4424
|
};
|
|
3976
4425
|
constructor.prototype.attributeChangedCallback = function (name, oldValue, newValue) {
|
|
3977
4426
|
originalAttributeChangedCallback?.call(this, name, oldValue, newValue);
|
|
@@ -3987,13 +4436,27 @@ function applyElementFunctionality(constructor) {
|
|
|
3987
4436
|
if (attributeName.toLowerCase() !== name.toLowerCase())
|
|
3988
4437
|
continue;
|
|
3989
4438
|
const currentValue = this[PROPERTY_VALUES]?.[propName];
|
|
3990
|
-
|
|
3991
|
-
|
|
4439
|
+
// A removed attribute reverts the property to its field default. Read it
|
|
4440
|
+
// back through the getter (attribute is already gone at this point) so the
|
|
4441
|
+
// watcher's newValue and PROPERTY_VALUES match what this[propName] now
|
|
4442
|
+
// returns — parseAttributeValue(null) would diverge to null for String/Number.
|
|
4443
|
+
const parsedValue = newValue === null
|
|
4444
|
+
? this[propName]
|
|
4445
|
+
: parseAttributeValue(newValue, propOptions, currentValue, undefined);
|
|
4446
|
+
const changed = propOptions?.hasChanged
|
|
4447
|
+
? propOptions.hasChanged(parsedValue, currentValue)
|
|
4448
|
+
: notEqual(parsedValue, currentValue);
|
|
4449
|
+
if (!changed)
|
|
3992
4450
|
break;
|
|
3993
4451
|
ensureSet(this, EXPLICITLY_SET_PROPERTIES).add(propName);
|
|
3994
4452
|
ensureObj(this, PROPERTY_VALUES)[propName] = parsedValue;
|
|
3995
4453
|
if (!this[SETTING_FROM_PROPERTY]?.has(name.toLowerCase())) {
|
|
3996
|
-
|
|
4454
|
+
// Watchers react to changes only. During upgrade the initial value
|
|
4455
|
+
// is not a change, so suppress until INITIALIZED; @watch immediate
|
|
4456
|
+
// handlers get their one init call from invokeImmediateWatchers.
|
|
4457
|
+
if (this[INITIALIZED]) {
|
|
4458
|
+
invokeWatchers(this, constructor, propName, currentValue, parsedValue);
|
|
4459
|
+
}
|
|
3997
4460
|
if (this[RENDER_METHOD] && this[INITIALIZED]) {
|
|
3998
4461
|
requestRender(this);
|
|
3999
4462
|
}
|
|
@@ -4003,12 +4466,14 @@ function applyElementFunctionality(constructor) {
|
|
|
4003
4466
|
};
|
|
4004
4467
|
// Add connectedMoveCallback for handling DOM moves
|
|
4005
4468
|
constructor.prototype.connectedMoveCallback = async function () {
|
|
4006
|
-
// Call @moved handlers
|
|
4469
|
+
// Call @moved handlers via the prototype method (the debounce/throttle
|
|
4470
|
+
// wrapper), not the raw handler — otherwise @moved({debounce}) options
|
|
4471
|
+
// are ignored for real moves and only work when called directly.
|
|
4007
4472
|
const movedHandlers = constructor[MOVED_HANDLERS];
|
|
4008
4473
|
if (movedHandlers) {
|
|
4009
4474
|
for (const handler of movedHandlers) {
|
|
4010
4475
|
try {
|
|
4011
|
-
await handler.
|
|
4476
|
+
await this[handler.methodName]();
|
|
4012
4477
|
}
|
|
4013
4478
|
catch (error) {
|
|
4014
4479
|
console.error(`Error in @moved handler ${handler.methodName}:`, error);
|
|
@@ -4018,12 +4483,13 @@ function applyElementFunctionality(constructor) {
|
|
|
4018
4483
|
};
|
|
4019
4484
|
// Add adoptedCallback for handling document adoption
|
|
4020
4485
|
constructor.prototype.adoptedCallback = async function () {
|
|
4021
|
-
// Call @adopted handlers
|
|
4486
|
+
// Call @adopted handlers via the prototype method (the debounce/throttle
|
|
4487
|
+
// wrapper), not the raw handler — same reason as connectedMoveCallback.
|
|
4022
4488
|
const adoptedHandlers = constructor[ADOPTED_HANDLERS];
|
|
4023
4489
|
if (adoptedHandlers) {
|
|
4024
4490
|
for (const handler of adoptedHandlers) {
|
|
4025
4491
|
try {
|
|
4026
|
-
await handler.
|
|
4492
|
+
await this[handler.methodName]();
|
|
4027
4493
|
}
|
|
4028
4494
|
catch (error) {
|
|
4029
4495
|
console.error(`Error in @adopted handler ${handler.methodName}:`, error);
|
|
@@ -4188,7 +4654,12 @@ function property(options) {
|
|
|
4188
4654
|
},
|
|
4189
4655
|
set(newValue) {
|
|
4190
4656
|
const oldValue = this[propertyKey];
|
|
4191
|
-
|
|
4657
|
+
// A custom hasChanged comparator overrides the default === check
|
|
4658
|
+
// (e.g. deep-equal objects, or forcing an update on a mutated ref).
|
|
4659
|
+
const changed = finalOptions?.hasChanged
|
|
4660
|
+
? finalOptions.hasChanged(newValue, oldValue)
|
|
4661
|
+
: notEqual(newValue, oldValue);
|
|
4662
|
+
if (!changed)
|
|
4192
4663
|
return;
|
|
4193
4664
|
// Pre-init: store for later, don't reflect to DOM yet
|
|
4194
4665
|
if (!this[PROPERTIES_INITIALIZED]) {
|
|
@@ -4205,18 +4676,27 @@ function property(options) {
|
|
|
4205
4676
|
const attributeName = getAttrName(finalOptions, propertyKey);
|
|
4206
4677
|
const attributeValue = valueToAttribute(newValue, finalOptions, initialValue);
|
|
4207
4678
|
ensureSet(this, EXPLICITLY_SET_PROPERTIES).add(propertyKey);
|
|
4208
|
-
|
|
4679
|
+
const attrKey = attributeName.toLowerCase();
|
|
4680
|
+
ensureSet(this, SETTING_FROM_PROPERTY).add(attrKey);
|
|
4681
|
+
// attributeChangedCallback runs synchronously inside setAttribute /
|
|
4682
|
+
// removeAttribute (custom element reactions are synchronous), so the
|
|
4683
|
+
// guard only needs to bracket this one reflection. Clear it right
|
|
4684
|
+
// after — not on a microtask — otherwise an external attribute change
|
|
4685
|
+
// in the same tick is mistaken for our own echo and silently dropped.
|
|
4209
4686
|
if (attributeValue === null) {
|
|
4210
4687
|
this.removeAttribute?.(attributeName);
|
|
4211
4688
|
}
|
|
4212
4689
|
else {
|
|
4213
4690
|
this.setAttribute?.(attributeName, attributeValue);
|
|
4214
4691
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4692
|
+
this[SETTING_FROM_PROPERTY].delete(attrKey);
|
|
4693
|
+
}
|
|
4694
|
+
// See attributeChangedCallback: the initial value is not a change,
|
|
4695
|
+
// so suppress watchers until INITIALIZED. @watch immediate handlers
|
|
4696
|
+
// get their one init call from invokeImmediateWatchers.
|
|
4697
|
+
if (this[INITIALIZED]) {
|
|
4698
|
+
invokeWatchers(this, this.constructor, propertyKey, oldValue, newValue);
|
|
4218
4699
|
}
|
|
4219
|
-
invokeWatchers(this, this.constructor, propertyKey, oldValue, newValue);
|
|
4220
4700
|
if (this[RENDER_METHOD] && this[INITIALIZED]) {
|
|
4221
4701
|
requestRender(this);
|
|
4222
4702
|
}
|
|
@@ -4286,7 +4766,12 @@ function queryAll(selector, options = {}) {
|
|
|
4286
4766
|
};
|
|
4287
4767
|
};
|
|
4288
4768
|
}
|
|
4289
|
-
function watch(...
|
|
4769
|
+
function watch(...args) {
|
|
4770
|
+
// A trailing options object is separated from the watched property names.
|
|
4771
|
+
const propertyNames = args.filter((a) => typeof a === 'string');
|
|
4772
|
+
const options = args.find((a) => typeof a === 'object' && a !== null);
|
|
4773
|
+
// Fire on init by default; opt out of the init call with { immediate: false }.
|
|
4774
|
+
const immediate = options?.immediate !== false;
|
|
4290
4775
|
return function (target, context) {
|
|
4291
4776
|
const methodName = context.name;
|
|
4292
4777
|
context.addInitializer(function () {
|
|
@@ -4326,7 +4811,8 @@ function watch(...propertyNames) {
|
|
|
4326
4811
|
}
|
|
4327
4812
|
constructor[PROPERTY_WATCHERS].get(propertyName).push({
|
|
4328
4813
|
methodName,
|
|
4329
|
-
method: target
|
|
4814
|
+
method: target,
|
|
4815
|
+
immediate
|
|
4330
4816
|
});
|
|
4331
4817
|
}
|
|
4332
4818
|
});
|
|
@@ -4435,6 +4921,24 @@ function reconnect() {
|
|
|
4435
4921
|
registerHandler(RECONNECT_HANDLERS, RECONNECT_METHODS, target, context);
|
|
4436
4922
|
};
|
|
4437
4923
|
}
|
|
4924
|
+
/**
|
|
4925
|
+
* Clear any pending @moved / @adopted debounce/throttle timers on an instance
|
|
4926
|
+
* (e.g. on disconnect), so they don't fire on a dead element.
|
|
4927
|
+
*/
|
|
4928
|
+
function clearLifecycleTimers(instance, timersSymbol) {
|
|
4929
|
+
const map = instance[timersSymbol];
|
|
4930
|
+
if (!map)
|
|
4931
|
+
return;
|
|
4932
|
+
for (const t of map.values()) {
|
|
4933
|
+
if (t.debounceTimer)
|
|
4934
|
+
clearTimeout(t.debounceTimer);
|
|
4935
|
+
if (t.throttleTimer)
|
|
4936
|
+
clearTimeout(t.throttleTimer);
|
|
4937
|
+
t.debounceTimer = null;
|
|
4938
|
+
t.throttleTimer = null;
|
|
4939
|
+
t.lastThrottleCall = 0;
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4438
4942
|
function createLifecycleDecorator(handlersSymbol, timersSymbol, methodsSymbol) {
|
|
4439
4943
|
return function (options = {}) {
|
|
4440
4944
|
return function (originalMethod, context) {
|
|
@@ -4448,9 +4952,23 @@ function createLifecycleDecorator(handlersSymbol, timersSymbol, methodsSymbol) {
|
|
|
4448
4952
|
}
|
|
4449
4953
|
const timers = this[timersSymbol].get(methodName);
|
|
4450
4954
|
const exec = (...a) => originalMethod.apply(this, a);
|
|
4955
|
+
// Deferred (debounce/throttle) calls run inside a setTimeout, outside
|
|
4956
|
+
// the caller's try/catch — so catch their sync throws and async
|
|
4957
|
+
// rejections here and log, rather than surface an uncaught error.
|
|
4958
|
+
const execDeferred = (...a) => {
|
|
4959
|
+
try {
|
|
4960
|
+
const result = originalMethod.apply(this, a);
|
|
4961
|
+
if (result && typeof result.then === 'function') {
|
|
4962
|
+
result.catch((error) => console.error(`Error in deferred lifecycle handler ${methodName}:`, error));
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
catch (error) {
|
|
4966
|
+
console.error(`Error in deferred lifecycle handler ${methodName}:`, error);
|
|
4967
|
+
}
|
|
4968
|
+
};
|
|
4451
4969
|
if (options.debounce > 0) {
|
|
4452
4970
|
clearTimeout(timers.debounceTimer);
|
|
4453
|
-
timers.debounceTimer = setTimeout(() =>
|
|
4971
|
+
timers.debounceTimer = setTimeout(() => execDeferred(...args), options.debounce);
|
|
4454
4972
|
return undefined;
|
|
4455
4973
|
}
|
|
4456
4974
|
if (options.throttle > 0) {
|
|
@@ -4464,7 +4982,7 @@ function createLifecycleDecorator(handlersSymbol, timersSymbol, methodsSymbol) {
|
|
|
4464
4982
|
timers.throttleTimer = setTimeout(() => {
|
|
4465
4983
|
timers.throttleTimer = null;
|
|
4466
4984
|
timers.lastThrottleCall = Date.now();
|
|
4467
|
-
|
|
4985
|
+
execDeferred(...args);
|
|
4468
4986
|
}, remaining);
|
|
4469
4987
|
}
|
|
4470
4988
|
return undefined;
|
|
@@ -5096,6 +5614,40 @@ class Route {
|
|
|
5096
5614
|
}
|
|
5097
5615
|
}
|
|
5098
5616
|
|
|
5617
|
+
/**
|
|
5618
|
+
* Score a route pattern by specificity so that more specific routes are tried
|
|
5619
|
+
* first. Scoring is per-segment by ROLE, not by string length: a static segment
|
|
5620
|
+
* outweighs a dynamic (`:param`) segment, which outweighs a wildcard (`*` /
|
|
5621
|
+
* `*name`). So `/post/new` beats `/post/:id`, and a splat always ranks last —
|
|
5622
|
+
* regardless of how long the pattern string happens to be. This mirrors the
|
|
5623
|
+
* segment-scoring model mainstream routers (React Router, Remix) use.
|
|
5624
|
+
*
|
|
5625
|
+
* Sort callers descending by this score (highest = most specific = first).
|
|
5626
|
+
*
|
|
5627
|
+
* NOTE: mirrored in `src/react/route-specificity.ts` for the isolated React
|
|
5628
|
+
* bundle — keep the two in sync.
|
|
5629
|
+
*/
|
|
5630
|
+
const STATIC_SEGMENT = 10;
|
|
5631
|
+
const DYNAMIC_SEGMENT = 3;
|
|
5632
|
+
const WILDCARD_SEGMENT = -2;
|
|
5633
|
+
function routeSpecificity(spec) {
|
|
5634
|
+
const segments = spec.split('/').filter(Boolean);
|
|
5635
|
+
// Base on segment count so a deeper path outranks a shallower one on ties.
|
|
5636
|
+
let score = segments.length;
|
|
5637
|
+
for (const segment of segments) {
|
|
5638
|
+
if (segment.startsWith('*')) {
|
|
5639
|
+
score += WILDCARD_SEGMENT;
|
|
5640
|
+
}
|
|
5641
|
+
else if (segment.startsWith(':')) {
|
|
5642
|
+
score += DYNAMIC_SEGMENT;
|
|
5643
|
+
}
|
|
5644
|
+
else {
|
|
5645
|
+
score += STATIC_SEGMENT;
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
return score;
|
|
5649
|
+
}
|
|
5650
|
+
|
|
5099
5651
|
/**
|
|
5100
5652
|
* Generic transition system for animating between elements
|
|
5101
5653
|
*/
|
|
@@ -5363,7 +5915,7 @@ function Router(options) {
|
|
|
5363
5915
|
}
|
|
5364
5916
|
const needsSorting = !is_sorted;
|
|
5365
5917
|
if (needsSorting) {
|
|
5366
|
-
routes.sort((a, b) => b.route.spec
|
|
5918
|
+
routes.sort((a, b) => routeSpecificity(b.route.spec) - routeSpecificity(a.route.spec));
|
|
5367
5919
|
is_sorted = true;
|
|
5368
5920
|
}
|
|
5369
5921
|
// Collect placards from registered routes
|
|
@@ -5434,7 +5986,7 @@ function Router(options) {
|
|
|
5434
5986
|
</div>`;
|
|
5435
5987
|
return container;
|
|
5436
5988
|
}
|
|
5437
|
-
async function checkGuards(guards, params, target) {
|
|
5989
|
+
async function checkGuards(guards, params, target, stale) {
|
|
5438
5990
|
const hasGuards = !!guards;
|
|
5439
5991
|
if (!hasGuards) {
|
|
5440
5992
|
return true;
|
|
@@ -5442,8 +5994,11 @@ function Router(options) {
|
|
|
5442
5994
|
const guardsArray = Array.isArray(guards) ? guards : [guards];
|
|
5443
5995
|
let spinner = null;
|
|
5444
5996
|
let spinnerTimer = null;
|
|
5445
|
-
// Show spinner after a short delay (avoids flash for fast sync guards)
|
|
5997
|
+
// Show spinner after a short delay (avoids flash for fast sync guards).
|
|
5998
|
+
// Skip if a newer navigation has already superseded this one.
|
|
5446
5999
|
spinnerTimer = setTimeout(() => {
|
|
6000
|
+
if (stale && stale())
|
|
6001
|
+
return;
|
|
5447
6002
|
spinner = createLoadingSpinner();
|
|
5448
6003
|
target.appendChild(spinner);
|
|
5449
6004
|
}, 50);
|
|
@@ -5451,7 +6006,11 @@ function Router(options) {
|
|
|
5451
6006
|
for (const guard of guardsArray) {
|
|
5452
6007
|
const allowed = await guard(context, params);
|
|
5453
6008
|
if (!allowed) {
|
|
5454
|
-
|
|
6009
|
+
// Don't stomp the current page with a 403 if a newer navigation
|
|
6010
|
+
// already took over while this guard was resolving.
|
|
6011
|
+
if (!stale || !stale()) {
|
|
6012
|
+
renderForbiddenPage(target);
|
|
6013
|
+
}
|
|
5455
6014
|
return false;
|
|
5456
6015
|
}
|
|
5457
6016
|
}
|
|
@@ -5487,14 +6046,14 @@ function Router(options) {
|
|
|
5487
6046
|
div.innerHTML = /*html*/ `<h1>404</h1><p>Page not found</p>`;
|
|
5488
6047
|
return { element: div, transition: undefined, layout: undefined };
|
|
5489
6048
|
}
|
|
5490
|
-
async function resolveRoute(path, target) {
|
|
6049
|
+
async function resolveRoute(path, target, stale) {
|
|
5491
6050
|
for (const route of routes) {
|
|
5492
6051
|
const params = route.route.match(path);
|
|
5493
6052
|
const isMatch = params !== false;
|
|
5494
6053
|
if (!isMatch) {
|
|
5495
6054
|
continue;
|
|
5496
6055
|
}
|
|
5497
|
-
const guardsAllowed = await checkGuards(route.guards, params, target);
|
|
6056
|
+
const guardsAllowed = await checkGuards(route.guards, params, target, stale);
|
|
5498
6057
|
if (!guardsAllowed) {
|
|
5499
6058
|
return { result: RouteResult.GUARDS_FAILED };
|
|
5500
6059
|
}
|
|
@@ -5622,7 +6181,7 @@ function Router(options) {
|
|
|
5622
6181
|
const isHomePath = (path?.trim() === '' || path === '/') && !!home;
|
|
5623
6182
|
if (isHomePath) {
|
|
5624
6183
|
const homeRoute = routes.find(r => r.route.match('/'));
|
|
5625
|
-
if (!(await checkGuards(homeRoute?.guards, {}, target)))
|
|
6184
|
+
if (!(await checkGuards(homeRoute?.guards, {}, target, stale)))
|
|
5626
6185
|
return;
|
|
5627
6186
|
if (stale())
|
|
5628
6187
|
return;
|
|
@@ -5634,7 +6193,7 @@ function Router(options) {
|
|
|
5634
6193
|
if (!path)
|
|
5635
6194
|
return;
|
|
5636
6195
|
// Resolve route
|
|
5637
|
-
const routeResult = await resolveRoute(path, target);
|
|
6196
|
+
const routeResult = await resolveRoute(path, target, stale);
|
|
5638
6197
|
if (stale())
|
|
5639
6198
|
return;
|
|
5640
6199
|
// Guards failed (403 already rendered by checkGuards)
|
|
@@ -5648,7 +6207,7 @@ function Router(options) {
|
|
|
5648
6207
|
}
|
|
5649
6208
|
// 404 fallthrough
|
|
5650
6209
|
const { element, transition, layout } = create404Element();
|
|
5651
|
-
await renderPage(target, element, transition, layout, path, {});
|
|
6210
|
+
await renderPage(target, element, transition, layout, path, {}, stale);
|
|
5652
6211
|
}
|
|
5653
6212
|
async function performTransition$1(container, oldElement, newElement, transition, atGen = navGeneration) {
|
|
5654
6213
|
// `isStale` returns true when a newer navigate() has been kicked off.
|
|
@@ -5665,93 +6224,6 @@ function Router(options) {
|
|
|
5665
6224
|
};
|
|
5666
6225
|
}
|
|
5667
6226
|
|
|
5668
|
-
// @dispatch decorator - auto-dispatches custom events from method return values
|
|
5669
|
-
/**
|
|
5670
|
-
* Decorator that automatically dispatches a custom event after a method is called.
|
|
5671
|
-
* The return value of the method becomes the event detail.
|
|
5672
|
-
*
|
|
5673
|
-
* @param eventName The name of the event to dispatch
|
|
5674
|
-
* @param options Optional configuration extending EventInit
|
|
5675
|
-
*/
|
|
5676
|
-
function dispatch(eventName, options) {
|
|
5677
|
-
return function (originalMethod, _context) {
|
|
5678
|
-
return function (...args) {
|
|
5679
|
-
// Create timing wrappers for dispatch (per-instance)
|
|
5680
|
-
if (!this[DISPATCH_TIMERS]) {
|
|
5681
|
-
this[DISPATCH_TIMERS] = new Map();
|
|
5682
|
-
}
|
|
5683
|
-
const timerKey = `${eventName}_${_context.name}`;
|
|
5684
|
-
if (!this[DISPATCH_TIMERS].has(timerKey)) {
|
|
5685
|
-
this[DISPATCH_TIMERS].set(timerKey, {
|
|
5686
|
-
debounceTimeout: null,
|
|
5687
|
-
throttleLastCall: 0,
|
|
5688
|
-
throttleTimeout: null
|
|
5689
|
-
});
|
|
5690
|
-
}
|
|
5691
|
-
const timers = this[DISPATCH_TIMERS].get(timerKey);
|
|
5692
|
-
// Call the original method with preserved this context
|
|
5693
|
-
const result = originalMethod.apply(this, args);
|
|
5694
|
-
// Helper to dispatch the event
|
|
5695
|
-
const doDispatch = (detail) => {
|
|
5696
|
-
// Skip dispatch if result is undefined and dispatchOnUndefined is false
|
|
5697
|
-
if (detail === undefined && options?.dispatchOnUndefined === false) {
|
|
5698
|
-
return;
|
|
5699
|
-
}
|
|
5700
|
-
// Create event with spread operator for options
|
|
5701
|
-
const event = new CustomEvent(eventName, {
|
|
5702
|
-
bubbles: true, // Default to true for component events
|
|
5703
|
-
composed: true, // Allow crossing shadow DOM boundaries
|
|
5704
|
-
...options, // Spread all EventInit options
|
|
5705
|
-
detail
|
|
5706
|
-
});
|
|
5707
|
-
this.dispatchEvent(event);
|
|
5708
|
-
};
|
|
5709
|
-
// Helper to handle timed dispatch
|
|
5710
|
-
const timedDispatch = (detail) => {
|
|
5711
|
-
if (options?.debounce) {
|
|
5712
|
-
clearTimeout(timers.debounceTimeout);
|
|
5713
|
-
timers.debounceTimeout = setTimeout(() => doDispatch(detail), options.debounce);
|
|
5714
|
-
return;
|
|
5715
|
-
}
|
|
5716
|
-
if (!options?.throttle) {
|
|
5717
|
-
doDispatch(detail);
|
|
5718
|
-
return;
|
|
5719
|
-
}
|
|
5720
|
-
const now = Date.now();
|
|
5721
|
-
const remaining = options.throttle - (now - timers.throttleLastCall);
|
|
5722
|
-
if (remaining <= 0) {
|
|
5723
|
-
clearTimeout(timers.throttleTimeout);
|
|
5724
|
-
timers.throttleLastCall = now;
|
|
5725
|
-
doDispatch(detail);
|
|
5726
|
-
return;
|
|
5727
|
-
}
|
|
5728
|
-
// Record the LATEST detail so the trailing dispatch carries fresh
|
|
5729
|
-
// data, not the first-suppressed-call detail captured by closure.
|
|
5730
|
-
timers.latestDetail = detail;
|
|
5731
|
-
if (!timers.throttleTimeout) {
|
|
5732
|
-
timers.throttleTimeout = setTimeout(() => {
|
|
5733
|
-
timers.throttleLastCall = Date.now();
|
|
5734
|
-
timers.throttleTimeout = null;
|
|
5735
|
-
const d = timers.latestDetail;
|
|
5736
|
-
timers.latestDetail = undefined;
|
|
5737
|
-
doDispatch(d);
|
|
5738
|
-
}, remaining);
|
|
5739
|
-
}
|
|
5740
|
-
};
|
|
5741
|
-
// Handle async methods
|
|
5742
|
-
if (result instanceof Promise) {
|
|
5743
|
-
return result.then((resolvedResult) => {
|
|
5744
|
-
timedDispatch(resolvedResult);
|
|
5745
|
-
return resolvedResult;
|
|
5746
|
-
});
|
|
5747
|
-
}
|
|
5748
|
-
// Sync method
|
|
5749
|
-
timedDispatch(result);
|
|
5750
|
-
return result;
|
|
5751
|
-
};
|
|
5752
|
-
};
|
|
5753
|
-
}
|
|
5754
|
-
|
|
5755
6227
|
/**
|
|
5756
6228
|
* createRequestHandler — vanilla JS utility for handling @request channels
|
|
5757
6229
|
* outside of the Snice element/controller decorator system.
|