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