@elurjs/ionic 2.0.7
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/CHANGELOG.md +272 -0
- package/MIGRATION.md +221 -0
- package/README.md +695 -0
- package/dist/lib/IonRouterOutlet.d.ts +134 -0
- package/dist/lib/__tests__/IonRouterOutlet.test.d.ts +1 -0
- package/dist/lib/__tests__/bundles.test.d.ts +1 -0
- package/dist/lib/__tests__/cache-policy.test.d.ts +1 -0
- package/dist/lib/__tests__/capacitor.test.d.ts +1 -0
- package/dist/lib/__tests__/components.test.d.ts +1 -0
- package/dist/lib/__tests__/lifecycle.test.d.ts +1 -0
- package/dist/lib/__tests__/mocks/ionic.d.ts +61 -0
- package/dist/lib/__tests__/navigation.test.d.ts +1 -0
- package/dist/lib/__tests__/overlays.test.d.ts +1 -0
- package/dist/lib/__tests__/page-state.test.d.ts +1 -0
- package/dist/lib/__tests__/phase0-bugs.test.d.ts +1 -0
- package/dist/lib/__tests__/setup.d.ts +1 -0
- package/dist/lib/__tests__/setup.test.d.ts +1 -0
- package/dist/lib/__tests__/tabs.test.d.ts +1 -0
- package/dist/lib/__tests__/vite-plugin.test.d.ts +1 -0
- package/dist/lib/bundles/all.cjs +2 -0
- package/dist/lib/bundles/all.cjs.map +1 -0
- package/dist/lib/bundles/all.d.ts +16 -0
- package/dist/lib/bundles/all.js +21 -0
- package/dist/lib/bundles/all.js.map +1 -0
- package/dist/lib/bundles/buttons.cjs +2 -0
- package/dist/lib/bundles/buttons.cjs.map +1 -0
- package/dist/lib/bundles/buttons.d.ts +7 -0
- package/dist/lib/bundles/buttons.js +17 -0
- package/dist/lib/bundles/buttons.js.map +1 -0
- package/dist/lib/bundles/feedback.cjs +2 -0
- package/dist/lib/bundles/feedback.cjs.map +1 -0
- package/dist/lib/bundles/feedback.d.ts +8 -0
- package/dist/lib/bundles/feedback.js +19 -0
- package/dist/lib/bundles/feedback.js.map +1 -0
- package/dist/lib/bundles/forms.cjs +2 -0
- package/dist/lib/bundles/forms.cjs.map +1 -0
- package/dist/lib/bundles/forms.d.ts +11 -0
- package/dist/lib/bundles/forms.js +47 -0
- package/dist/lib/bundles/forms.js.map +1 -0
- package/dist/lib/bundles/layout.cjs +2 -0
- package/dist/lib/bundles/layout.cjs.map +1 -0
- package/dist/lib/bundles/layout.d.ts +9 -0
- package/dist/lib/bundles/layout.js +33 -0
- package/dist/lib/bundles/layout.js.map +1 -0
- package/dist/lib/bundles/lists.cjs +2 -0
- package/dist/lib/bundles/lists.cjs.map +1 -0
- package/dist/lib/bundles/lists.d.ts +15 -0
- package/dist/lib/bundles/lists.js +65 -0
- package/dist/lib/bundles/lists.js.map +1 -0
- package/dist/lib/bundles/navigation.cjs +2 -0
- package/dist/lib/bundles/navigation.cjs.map +1 -0
- package/dist/lib/bundles/navigation.d.ts +9 -0
- package/dist/lib/bundles/navigation.js +29 -0
- package/dist/lib/bundles/navigation.js.map +1 -0
- package/dist/lib/bundles/overlays.cjs +2 -0
- package/dist/lib/bundles/overlays.cjs.map +1 -0
- package/dist/lib/bundles/overlays.d.ts +9 -0
- package/dist/lib/bundles/overlays.js +25 -0
- package/dist/lib/bundles/overlays.js.map +1 -0
- package/dist/lib/capacitor.cjs +2 -0
- package/dist/lib/capacitor.cjs.map +1 -0
- package/dist/lib/capacitor.d.ts +217 -0
- package/dist/lib/capacitor.js +223 -0
- package/dist/lib/capacitor.js.map +1 -0
- package/dist/lib/components/accordion-group.cjs +1 -0
- package/dist/lib/components/accordion-group.d.ts +11 -0
- package/dist/lib/components/accordion-group.js +2 -0
- package/dist/lib/components/accordion.cjs +1 -0
- package/dist/lib/components/accordion.d.ts +11 -0
- package/dist/lib/components/accordion.js +2 -0
- package/dist/lib/components/action-sheet.cjs +1 -0
- package/dist/lib/components/action-sheet.d.ts +11 -0
- package/dist/lib/components/action-sheet.js +2 -0
- package/dist/lib/components/alert.cjs +1 -0
- package/dist/lib/components/alert.d.ts +11 -0
- package/dist/lib/components/alert.js +2 -0
- package/dist/lib/components/avatar.cjs +1 -0
- package/dist/lib/components/avatar.d.ts +11 -0
- package/dist/lib/components/avatar.js +2 -0
- package/dist/lib/components/backdrop.cjs +1 -0
- package/dist/lib/components/backdrop.d.ts +11 -0
- package/dist/lib/components/backdrop.js +2 -0
- package/dist/lib/components/badge.cjs +1 -0
- package/dist/lib/components/badge.d.ts +11 -0
- package/dist/lib/components/badge.js +2 -0
- package/dist/lib/components/breadcrumb.cjs +1 -0
- package/dist/lib/components/breadcrumb.d.ts +11 -0
- package/dist/lib/components/breadcrumb.js +2 -0
- package/dist/lib/components/breadcrumbs.cjs +1 -0
- package/dist/lib/components/breadcrumbs.d.ts +11 -0
- package/dist/lib/components/breadcrumbs.js +2 -0
- package/dist/lib/components/button.cjs +1 -0
- package/dist/lib/components/button.d.ts +11 -0
- package/dist/lib/components/button.js +2 -0
- package/dist/lib/components/buttons.cjs +1 -0
- package/dist/lib/components/buttons.d.ts +11 -0
- package/dist/lib/components/buttons.js +2 -0
- package/dist/lib/components/card-content.cjs +1 -0
- package/dist/lib/components/card-content.d.ts +11 -0
- package/dist/lib/components/card-content.js +2 -0
- package/dist/lib/components/card-header.cjs +1 -0
- package/dist/lib/components/card-header.d.ts +11 -0
- package/dist/lib/components/card-header.js +2 -0
- package/dist/lib/components/card-subtitle.cjs +1 -0
- package/dist/lib/components/card-subtitle.d.ts +11 -0
- package/dist/lib/components/card-subtitle.js +2 -0
- package/dist/lib/components/card-title.cjs +1 -0
- package/dist/lib/components/card-title.d.ts +11 -0
- package/dist/lib/components/card-title.js +2 -0
- package/dist/lib/components/card.cjs +1 -0
- package/dist/lib/components/card.d.ts +11 -0
- package/dist/lib/components/card.js +2 -0
- package/dist/lib/components/checkbox.cjs +1 -0
- package/dist/lib/components/checkbox.d.ts +11 -0
- package/dist/lib/components/checkbox.js +2 -0
- package/dist/lib/components/chip.cjs +1 -0
- package/dist/lib/components/chip.d.ts +11 -0
- package/dist/lib/components/chip.js +2 -0
- package/dist/lib/components/col.cjs +1 -0
- package/dist/lib/components/col.d.ts +11 -0
- package/dist/lib/components/col.js +2 -0
- package/dist/lib/components/content.cjs +1 -0
- package/dist/lib/components/content.d.ts +11 -0
- package/dist/lib/components/content.js +2 -0
- package/dist/lib/components/datetime-button.cjs +1 -0
- package/dist/lib/components/datetime-button.d.ts +11 -0
- package/dist/lib/components/datetime-button.js +2 -0
- package/dist/lib/components/datetime.cjs +1 -0
- package/dist/lib/components/datetime.d.ts +11 -0
- package/dist/lib/components/datetime.js +2 -0
- package/dist/lib/components/fab-button.cjs +1 -0
- package/dist/lib/components/fab-button.d.ts +11 -0
- package/dist/lib/components/fab-button.js +2 -0
- package/dist/lib/components/fab-list.cjs +1 -0
- package/dist/lib/components/fab-list.d.ts +11 -0
- package/dist/lib/components/fab-list.js +2 -0
- package/dist/lib/components/fab.cjs +1 -0
- package/dist/lib/components/fab.d.ts +11 -0
- package/dist/lib/components/fab.js +2 -0
- package/dist/lib/components/footer.cjs +1 -0
- package/dist/lib/components/footer.d.ts +11 -0
- package/dist/lib/components/footer.js +2 -0
- package/dist/lib/components/grid.cjs +1 -0
- package/dist/lib/components/grid.d.ts +11 -0
- package/dist/lib/components/grid.js +2 -0
- package/dist/lib/components/header.cjs +1 -0
- package/dist/lib/components/header.d.ts +11 -0
- package/dist/lib/components/header.js +2 -0
- package/dist/lib/components/img.cjs +1 -0
- package/dist/lib/components/img.d.ts +11 -0
- package/dist/lib/components/img.js +2 -0
- package/dist/lib/components/infinite-scroll-content.cjs +1 -0
- package/dist/lib/components/infinite-scroll-content.d.ts +11 -0
- package/dist/lib/components/infinite-scroll-content.js +2 -0
- package/dist/lib/components/infinite-scroll.cjs +1 -0
- package/dist/lib/components/infinite-scroll.d.ts +11 -0
- package/dist/lib/components/infinite-scroll.js +2 -0
- package/dist/lib/components/input-otp.cjs +1 -0
- package/dist/lib/components/input-otp.d.ts +11 -0
- package/dist/lib/components/input-otp.js +2 -0
- package/dist/lib/components/input-password-toggle.cjs +1 -0
- package/dist/lib/components/input-password-toggle.d.ts +11 -0
- package/dist/lib/components/input-password-toggle.js +2 -0
- package/dist/lib/components/input.cjs +1 -0
- package/dist/lib/components/input.d.ts +11 -0
- package/dist/lib/components/input.js +2 -0
- package/dist/lib/components/item-divider.cjs +1 -0
- package/dist/lib/components/item-divider.d.ts +11 -0
- package/dist/lib/components/item-divider.js +2 -0
- package/dist/lib/components/item-group.cjs +1 -0
- package/dist/lib/components/item-group.d.ts +11 -0
- package/dist/lib/components/item-group.js +2 -0
- package/dist/lib/components/item-option.cjs +1 -0
- package/dist/lib/components/item-option.d.ts +11 -0
- package/dist/lib/components/item-option.js +2 -0
- package/dist/lib/components/item-options.cjs +1 -0
- package/dist/lib/components/item-options.d.ts +11 -0
- package/dist/lib/components/item-options.js +2 -0
- package/dist/lib/components/item-sliding.cjs +1 -0
- package/dist/lib/components/item-sliding.d.ts +11 -0
- package/dist/lib/components/item-sliding.js +2 -0
- package/dist/lib/components/item.cjs +1 -0
- package/dist/lib/components/item.d.ts +11 -0
- package/dist/lib/components/item.js +2 -0
- package/dist/lib/components/label.cjs +1 -0
- package/dist/lib/components/label.d.ts +11 -0
- package/dist/lib/components/label.js +2 -0
- package/dist/lib/components/list-header.cjs +1 -0
- package/dist/lib/components/list-header.d.ts +11 -0
- package/dist/lib/components/list-header.js +2 -0
- package/dist/lib/components/list.cjs +1 -0
- package/dist/lib/components/list.d.ts +11 -0
- package/dist/lib/components/list.js +2 -0
- package/dist/lib/components/loading.cjs +1 -0
- package/dist/lib/components/loading.d.ts +11 -0
- package/dist/lib/components/loading.js +2 -0
- package/dist/lib/components/manifest.cjs +2 -0
- package/dist/lib/components/manifest.cjs.map +1 -0
- package/dist/lib/components/manifest.d.ts +43 -0
- package/dist/lib/components/manifest.js +647 -0
- package/dist/lib/components/manifest.js.map +1 -0
- package/dist/lib/components/menu-button.cjs +1 -0
- package/dist/lib/components/menu-button.d.ts +11 -0
- package/dist/lib/components/menu-button.js +2 -0
- package/dist/lib/components/menu-toggle.cjs +1 -0
- package/dist/lib/components/menu-toggle.d.ts +11 -0
- package/dist/lib/components/menu-toggle.js +2 -0
- package/dist/lib/components/menu.cjs +1 -0
- package/dist/lib/components/menu.d.ts +11 -0
- package/dist/lib/components/menu.js +2 -0
- package/dist/lib/components/modal.cjs +1 -0
- package/dist/lib/components/modal.d.ts +11 -0
- package/dist/lib/components/modal.js +2 -0
- package/dist/lib/components/nav-link.cjs +1 -0
- package/dist/lib/components/nav-link.d.ts +11 -0
- package/dist/lib/components/nav-link.js +2 -0
- package/dist/lib/components/nav.cjs +1 -0
- package/dist/lib/components/nav.d.ts +11 -0
- package/dist/lib/components/nav.js +2 -0
- package/dist/lib/components/note.cjs +1 -0
- package/dist/lib/components/note.d.ts +11 -0
- package/dist/lib/components/note.js +2 -0
- package/dist/lib/components/picker-column-option.cjs +1 -0
- package/dist/lib/components/picker-column-option.d.ts +11 -0
- package/dist/lib/components/picker-column-option.js +2 -0
- package/dist/lib/components/picker-column.cjs +1 -0
- package/dist/lib/components/picker-column.d.ts +11 -0
- package/dist/lib/components/picker-column.js +2 -0
- package/dist/lib/components/picker.cjs +1 -0
- package/dist/lib/components/picker.d.ts +11 -0
- package/dist/lib/components/picker.js +2 -0
- package/dist/lib/components/popover.cjs +1 -0
- package/dist/lib/components/popover.d.ts +11 -0
- package/dist/lib/components/popover.js +2 -0
- package/dist/lib/components/progress-bar.cjs +1 -0
- package/dist/lib/components/progress-bar.d.ts +11 -0
- package/dist/lib/components/progress-bar.js +2 -0
- package/dist/lib/components/radio-group.cjs +1 -0
- package/dist/lib/components/radio-group.d.ts +11 -0
- package/dist/lib/components/radio-group.js +2 -0
- package/dist/lib/components/radio.cjs +1 -0
- package/dist/lib/components/radio.d.ts +11 -0
- package/dist/lib/components/radio.js +2 -0
- package/dist/lib/components/range.cjs +1 -0
- package/dist/lib/components/range.d.ts +11 -0
- package/dist/lib/components/range.js +2 -0
- package/dist/lib/components/refresher-content.cjs +1 -0
- package/dist/lib/components/refresher-content.d.ts +11 -0
- package/dist/lib/components/refresher-content.js +2 -0
- package/dist/lib/components/refresher.cjs +1 -0
- package/dist/lib/components/refresher.d.ts +11 -0
- package/dist/lib/components/refresher.js +2 -0
- package/dist/lib/components/reorder-group.cjs +1 -0
- package/dist/lib/components/reorder-group.d.ts +11 -0
- package/dist/lib/components/reorder-group.js +2 -0
- package/dist/lib/components/reorder.cjs +1 -0
- package/dist/lib/components/reorder.d.ts +11 -0
- package/dist/lib/components/reorder.js +2 -0
- package/dist/lib/components/ripple-effect.cjs +1 -0
- package/dist/lib/components/ripple-effect.d.ts +11 -0
- package/dist/lib/components/ripple-effect.js +2 -0
- package/dist/lib/components/router-link.cjs +1 -0
- package/dist/lib/components/router-link.d.ts +11 -0
- package/dist/lib/components/router-link.js +2 -0
- package/dist/lib/components/row.cjs +1 -0
- package/dist/lib/components/row.d.ts +11 -0
- package/dist/lib/components/row.js +2 -0
- package/dist/lib/components/searchbar.cjs +1 -0
- package/dist/lib/components/searchbar.d.ts +11 -0
- package/dist/lib/components/searchbar.js +2 -0
- package/dist/lib/components/segment-button.cjs +1 -0
- package/dist/lib/components/segment-button.d.ts +11 -0
- package/dist/lib/components/segment-button.js +2 -0
- package/dist/lib/components/segment-content.cjs +1 -0
- package/dist/lib/components/segment-content.d.ts +11 -0
- package/dist/lib/components/segment-content.js +2 -0
- package/dist/lib/components/segment-view.cjs +1 -0
- package/dist/lib/components/segment-view.d.ts +11 -0
- package/dist/lib/components/segment-view.js +2 -0
- package/dist/lib/components/segment.cjs +1 -0
- package/dist/lib/components/segment.d.ts +11 -0
- package/dist/lib/components/segment.js +2 -0
- package/dist/lib/components/select-modal.cjs +1 -0
- package/dist/lib/components/select-modal.d.ts +11 -0
- package/dist/lib/components/select-modal.js +2 -0
- package/dist/lib/components/select-option.cjs +1 -0
- package/dist/lib/components/select-option.d.ts +11 -0
- package/dist/lib/components/select-option.js +2 -0
- package/dist/lib/components/select-popover.cjs +1 -0
- package/dist/lib/components/select-popover.d.ts +11 -0
- package/dist/lib/components/select-popover.js +2 -0
- package/dist/lib/components/select.cjs +1 -0
- package/dist/lib/components/select.d.ts +11 -0
- package/dist/lib/components/select.js +2 -0
- package/dist/lib/components/skeleton-text.cjs +1 -0
- package/dist/lib/components/skeleton-text.d.ts +11 -0
- package/dist/lib/components/skeleton-text.js +2 -0
- package/dist/lib/components/spinner.cjs +1 -0
- package/dist/lib/components/spinner.d.ts +11 -0
- package/dist/lib/components/spinner.js +2 -0
- package/dist/lib/components/split-pane.cjs +1 -0
- package/dist/lib/components/split-pane.d.ts +11 -0
- package/dist/lib/components/split-pane.js +2 -0
- package/dist/lib/components/tab-bar.cjs +1 -0
- package/dist/lib/components/tab-bar.d.ts +11 -0
- package/dist/lib/components/tab-bar.js +2 -0
- package/dist/lib/components/tab-button.cjs +1 -0
- package/dist/lib/components/tab-button.d.ts +11 -0
- package/dist/lib/components/tab-button.js +2 -0
- package/dist/lib/components/tab.cjs +1 -0
- package/dist/lib/components/tab.d.ts +11 -0
- package/dist/lib/components/tab.js +2 -0
- package/dist/lib/components/tabs.cjs +1 -0
- package/dist/lib/components/tabs.d.ts +11 -0
- package/dist/lib/components/tabs.js +2 -0
- package/dist/lib/components/text.cjs +1 -0
- package/dist/lib/components/text.d.ts +11 -0
- package/dist/lib/components/text.js +2 -0
- package/dist/lib/components/textarea.cjs +1 -0
- package/dist/lib/components/textarea.d.ts +11 -0
- package/dist/lib/components/textarea.js +2 -0
- package/dist/lib/components/thumbnail.cjs +1 -0
- package/dist/lib/components/thumbnail.d.ts +11 -0
- package/dist/lib/components/thumbnail.js +2 -0
- package/dist/lib/components/title.cjs +1 -0
- package/dist/lib/components/title.d.ts +11 -0
- package/dist/lib/components/title.js +2 -0
- package/dist/lib/components/toast.cjs +1 -0
- package/dist/lib/components/toast.d.ts +11 -0
- package/dist/lib/components/toast.js +2 -0
- package/dist/lib/components/toggle.cjs +1 -0
- package/dist/lib/components/toggle.d.ts +11 -0
- package/dist/lib/components/toggle.js +2 -0
- package/dist/lib/components/toolbar.cjs +1 -0
- package/dist/lib/components/toolbar.d.ts +11 -0
- package/dist/lib/components/toolbar.js +2 -0
- package/dist/lib/components.cjs +1 -0
- package/dist/lib/components.d.ts +110 -0
- package/dist/lib/components.js +94 -0
- package/dist/lib/definitions-DZ-6I7Fj.js +282 -0
- package/dist/lib/definitions-DZ-6I7Fj.js.map +1 -0
- package/dist/lib/definitions-Lm2GaF-Z.cjs +2 -0
- package/dist/lib/definitions-Lm2GaF-Z.cjs.map +1 -0
- package/dist/lib/elur-ionic.cjs +1 -0
- package/dist/lib/elur-ionic.cjs.map +1 -0
- package/dist/lib/elur-ionic.js +1 -0
- package/dist/lib/elur-ionic.js.map +1 -0
- package/dist/lib/index.d.ts +47 -0
- package/dist/lib/lifecycle.d.ts +62 -0
- package/dist/lib/navigation.cjs +2 -0
- package/dist/lib/navigation.cjs.map +1 -0
- package/dist/lib/navigation.d.ts +214 -0
- package/dist/lib/navigation.js +193 -0
- package/dist/lib/navigation.js.map +1 -0
- package/dist/lib/overlays.cjs +2 -0
- package/dist/lib/overlays.cjs.map +1 -0
- package/dist/lib/overlays.d.ts +400 -0
- package/dist/lib/overlays.js +161 -0
- package/dist/lib/overlays.js.map +1 -0
- package/dist/lib/page-state.cjs +2 -0
- package/dist/lib/page-state.cjs.map +1 -0
- package/dist/lib/page-state.d.ts +160 -0
- package/dist/lib/page-state.js +91 -0
- package/dist/lib/page-state.js.map +1 -0
- package/dist/lib/setup.d.ts +105 -0
- package/dist/lib/tabs-ByPLrzax.cjs +50 -0
- package/dist/lib/tabs-ByPLrzax.cjs.map +1 -0
- package/dist/lib/tabs-DjD1_PTM.js +178 -0
- package/dist/lib/tabs-DjD1_PTM.js.map +1 -0
- package/dist/lib/tabs.cjs +1 -0
- package/dist/lib/tabs.d.ts +83 -0
- package/dist/lib/tabs.js +2 -0
- package/dist/lib/vite-plugin.cjs +39 -0
- package/dist/lib/vite-plugin.cjs.map +1 -0
- package/dist/lib/vite-plugin.d.ts +61 -0
- package/dist/lib/vite-plugin.js +26703 -0
- package/dist/lib/vite-plugin.js.map +1 -0
- package/dist/lib/web-C-maW8Tl.cjs +2 -0
- package/dist/lib/web-C-maW8Tl.cjs.map +1 -0
- package/dist/lib/web-jkqBWgLD.js +56 -0
- package/dist/lib/web-jkqBWgLD.js.map +1 -0
- package/package.json +204 -0
package/README.md
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
# @elurjs/ionic
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@elurjs/ionic)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
> Ionic mobile integration for [Elur](https://elur.dev/) — tree-shakeable components, reactive overlays, cache policies, page-state persistence, optional Capacitor, and a Vite plugin for auto-registration.
|
|
7
|
+
|
|
8
|
+
## Why?
|
|
9
|
+
|
|
10
|
+
`@elurjs/ionic` bridges Elur signal-based reactivity with Ionic Core 8's native routing, transitions, and overlays. Unlike `@ionic/angular` or `@ionic/react`, it adds **zero framework runtime overhead** — no virtual DOM, no dependency arrays, no hooks rules.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @elurjs/ionic @elurjs/core @ionic/core ionicons
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For native mobile (optional):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @capacitor/core @capacitor/app @capacitor/status-bar @capacitor/splash-screen @capacitor/keyboard @capacitor/haptics
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// main.ts
|
|
28
|
+
import "@ionic/core/css/core.css";
|
|
29
|
+
import "@ionic/core/css/normalize.css";
|
|
30
|
+
import "@ionic/core/css/structure.css";
|
|
31
|
+
import "@ionic/core/css/typography.css";
|
|
32
|
+
import "@ionic/core/css/padding.css";
|
|
33
|
+
import "@ionic/core/css/flex-utils.css";
|
|
34
|
+
import "@ionic/core/css/display.css";
|
|
35
|
+
|
|
36
|
+
import { ElurComponent, html, mount } from "@elurjs/core";
|
|
37
|
+
import { IonRouterOutlet, IonPage, IonBackButton } from "@elurjs/ionic";
|
|
38
|
+
import { initializeElurIonic, registerIonicComponents } from "@elurjs/ionic";
|
|
39
|
+
import { defineIonHeader, defineIonToolbar, defineIonTitle, defineIonContent, defineIonButton } from "@elurjs/ionic/components";
|
|
40
|
+
import { home, homeOutline } from "ionicons/icons";
|
|
41
|
+
|
|
42
|
+
// 1. Initialize + register only what you use
|
|
43
|
+
initializeElurIonic();
|
|
44
|
+
registerIonicComponents(defineIonHeader, defineIonToolbar, defineIonTitle, defineIonContent, defineIonButton);
|
|
45
|
+
|
|
46
|
+
// 2. Define routes
|
|
47
|
+
const outlet = new IonRouterOutlet([
|
|
48
|
+
{ path: "/", component: () => html`<ion-content><h1>Home</h1></ion-content>` },
|
|
49
|
+
{ path: "/detail/:id", component: (ctx) => new DetailPage(ctx) },
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
// 3. Mount
|
|
53
|
+
class App extends ElurComponent {
|
|
54
|
+
override render() {
|
|
55
|
+
return html`<ion-app>${outlet}</ion-app>`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
mount(new App(), "#app");
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Subpaths
|
|
62
|
+
|
|
63
|
+
| Import | What it gives you |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `@elurjs/ionic` | Core: router outlet, pages, lifecycle, setup, overlays, page-state |
|
|
66
|
+
| `@elurjs/ionic/components/*` | Individual component definers (tree-shakeable) |
|
|
67
|
+
| `@elurjs/ionic/bundles/*` | Category bundles (layout, forms, lists, etc.) |
|
|
68
|
+
| `@elurjs/ionic/overlays` | Reactive overlay controllers |
|
|
69
|
+
| `@elurjs/ionic/page-state` | Page-state persistence protocol |
|
|
70
|
+
| `@elurjs/ionic/navigation` | NavigationManager (single authority, hooks, tab switching) |
|
|
71
|
+
| `@elurjs/ionic/capacitor` | Optional Capacitor integration (zero web bundle cost) |
|
|
72
|
+
|
|
73
|
+
## Testing
|
|
74
|
+
|
|
75
|
+
- **Unit tests**: `npm test` — 234 tests with happy-dom mocks
|
|
76
|
+
- **E2E tests**: `npm run e2e` — 12 Playwright tests with real `@ionic/core`
|
|
77
|
+
- Navigation, lifecycle, overlays, back button, contract tests
|
|
78
|
+
- Chromium mobile viewport, `prefers-reduced-motion: reduce`
|
|
79
|
+
- No mocks — real `ion-router-outlet.commit()`, real custom elements
|
|
80
|
+
| `@elurjs/ionic/vite-plugin` | Vite plugin for auto component/icon registration |
|
|
81
|
+
|
|
82
|
+
## Setup
|
|
83
|
+
|
|
84
|
+
### Incremental (recommended)
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { initializeElurIonic, registerIonicComponents, registerIonicons } from "@elurjs/ionic";
|
|
88
|
+
import { defineIonButton, defineIonCard } from "@elurjs/ionic/components/button";
|
|
89
|
+
import { star, starOutline } from "ionicons/icons";
|
|
90
|
+
|
|
91
|
+
initializeElurIonic();
|
|
92
|
+
registerIonicComponents(defineIonButton, defineIonCard);
|
|
93
|
+
registerIonicons({ star, "star-outline": starOutline });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Compatibility facade (1.x migration)
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { setupElurIonic } from "@elurjs/ionic";
|
|
100
|
+
import { allComponents } from "@elurjs/ionic/bundles/all";
|
|
101
|
+
|
|
102
|
+
setupElurIonic({ components: allComponents });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Vite plugin (auto-registration)
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// vite.config.ts
|
|
109
|
+
import { defineConfig } from "vite";
|
|
110
|
+
import { elur } from "@elurjs/vite-plugin-elur";
|
|
111
|
+
import { elurIonic } from "@elurjs/ionic/vite-plugin";
|
|
112
|
+
|
|
113
|
+
export default defineConfig({
|
|
114
|
+
plugins: [
|
|
115
|
+
elur(),
|
|
116
|
+
elurIonic(), // scans html`` for <ion-*> tags + static icons
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Then in your app entry:
|
|
121
|
+
import "virtual:elur-ionic/registration";
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The plugin scans `html\`\`` templates for `<ion-*>` tags and `name="icon-name"` attributes, then generates a virtual module that imports and registers only what you use.
|
|
125
|
+
|
|
126
|
+
## Pages
|
|
127
|
+
|
|
128
|
+
### Class component with lifecycle
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { html, signal } from "@elurjs/core";
|
|
132
|
+
import { IonPage, IonBackButton, type PageContext } from "@elurjs/ionic";
|
|
133
|
+
|
|
134
|
+
class DetailPage extends IonPage {
|
|
135
|
+
private data = signal<unknown>(null);
|
|
136
|
+
private id: string;
|
|
137
|
+
|
|
138
|
+
constructor({ lc, params }: PageContext) {
|
|
139
|
+
super(lc);
|
|
140
|
+
this.id = params.id;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
override ionViewWillEnter() {
|
|
144
|
+
// Runs on every activation — even from cache
|
|
145
|
+
fetch(`/api/items/${this.id}`).then(r => r.json()).then(d => this.data.value = d);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override ionViewWillLeave() {
|
|
149
|
+
// Pause timers, subscriptions, etc.
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
override render() {
|
|
153
|
+
return html`
|
|
154
|
+
<ion-header>
|
|
155
|
+
<ion-toolbar>
|
|
156
|
+
<ion-buttons slot="start">${IonBackButton()}</ion-buttons>
|
|
157
|
+
<ion-title>Detail</ion-title>
|
|
158
|
+
</ion-toolbar>
|
|
159
|
+
</ion-header>
|
|
160
|
+
<ion-content class="ion-padding">
|
|
161
|
+
<p>${() => JSON.stringify(this.data.value)}</p>
|
|
162
|
+
</ion-content>
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Function component with composables
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { html, signal } from "@elurjs/core";
|
|
172
|
+
import { useIonViewWillEnter, useIonViewWillLeave, type PageContext } from "@elurjs/ionic";
|
|
173
|
+
|
|
174
|
+
function ProfilePage({ lc }: PageContext) {
|
|
175
|
+
const visits = signal(0);
|
|
176
|
+
useIonViewWillEnter(lc, () => visits.value++);
|
|
177
|
+
useIonViewWillLeave(lc, () => console.log("leaving"));
|
|
178
|
+
return html`<ion-content><p>Visits: ${() => visits.value}</p></ion-content>`;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Lifecycle hooks
|
|
183
|
+
|
|
184
|
+
| Hook | When | Use for |
|
|
185
|
+
|---|---|---|
|
|
186
|
+
| `ionViewWillEnter` | Before visible (every activation) | Data refresh, restart timers |
|
|
187
|
+
| `ionViewDidEnter` | After fully visible | Analytics, scroll position |
|
|
188
|
+
| `ionViewWillLeave` | Before hidden | Pause timers, save state |
|
|
189
|
+
| `ionViewDidLeave` | After hidden | Cleanup subscriptions |
|
|
190
|
+
|
|
191
|
+
> **Key**: `onMount`/`onInit` fire once. `ionViewWillEnter` fires on every visit — even from cache.
|
|
192
|
+
|
|
193
|
+
## Navigation
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import { elurRouter } from "@elurjs/core";
|
|
197
|
+
|
|
198
|
+
const router = elurRouter();
|
|
199
|
+
router.navigate("/detail/42");
|
|
200
|
+
router.navigate("/search", { query: { q: "hello" } });
|
|
201
|
+
router.replace("/home");
|
|
202
|
+
router.back();
|
|
203
|
+
|
|
204
|
+
router.current.value; // "/detail/42"
|
|
205
|
+
router.params.value; // { id: "42" }
|
|
206
|
+
router.query.value; // { q: "hello" }
|
|
207
|
+
router.canGoBack.value; // true/false
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Route guards
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
new IonRouterOutlet([
|
|
214
|
+
{
|
|
215
|
+
path: "/admin",
|
|
216
|
+
component: (ctx) => new AdminPage(ctx),
|
|
217
|
+
beforeEnter: ({ params }) => {
|
|
218
|
+
if (!isLoggedIn()) return "/login"; // redirect
|
|
219
|
+
if (!isAdmin()) return false; // cancel
|
|
220
|
+
// void/undefined = allow
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
]);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Cache policies
|
|
227
|
+
|
|
228
|
+
Control how many pages stay cached and for how long.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
new IonRouterOutlet(routes, {
|
|
232
|
+
cachePolicy: { max: 10, ttl: 60000, strategy: "lru" },
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
| Option | Default | Description |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| `max` | unlimited | Max cached entries per tab. Excess evicted by strategy. |
|
|
239
|
+
| `ttl` | unlimited | Time-to-live in ms. Entries auto-evicted on expiry. |
|
|
240
|
+
| `strategy` | `"lru"` | `"lru"` = evict least-recently-used, `"fifo"` = evict oldest |
|
|
241
|
+
|
|
242
|
+
### Per-route override
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
new IonRouterOutlet([
|
|
246
|
+
{ path: "/", component: () => html`...` },
|
|
247
|
+
{ path: "/transient", cache: false, component: () => html`...` }, // never cache
|
|
248
|
+
{ path: "/heavy", cache: { max: 1 }, component: () => html`...` }, // max 1 instance
|
|
249
|
+
{ path: "/volatile", cache: { ttl: 5000 }, component: () => html`...` }, // 5s TTL
|
|
250
|
+
]);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## NavigationManager
|
|
254
|
+
|
|
255
|
+
A single coordination authority for navigation — hooks, tab switching, and cache invalidation by route pattern.
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { NavigationManager, IonRouterOutlet } from "@elurjs/ionic";
|
|
259
|
+
|
|
260
|
+
const nav = new NavigationManager({ tabs: ["/home", "/search", "/profile"] });
|
|
261
|
+
|
|
262
|
+
// Navigation hooks
|
|
263
|
+
nav.beforeNav((path, intent) => {
|
|
264
|
+
console.log("navigating to", path);
|
|
265
|
+
// return false to cancel
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
nav.afterNav((path, direction) => {
|
|
269
|
+
analytics.track("page_view", { path, direction });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
nav.onTabChange((tab, prev) => {
|
|
273
|
+
console.log("tab changed:", prev, "→", tab);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Programmatic tab switching
|
|
277
|
+
const target = nav.switchTab("/search");
|
|
278
|
+
if (target) elurRouter().navigate(target);
|
|
279
|
+
|
|
280
|
+
// Cache invalidation by route pattern
|
|
281
|
+
nav.invalidateRoute("/user/:id", { id: "42" }); // invalidate user 42
|
|
282
|
+
nav.invalidatePattern("/admin/*"); // invalidate all admin pages
|
|
283
|
+
|
|
284
|
+
// Pass to outlet
|
|
285
|
+
const outlet = new IonRouterOutlet(routes, { navigation: nav });
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Stack inspection
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
nav.stackDepth(); // current tab stack depth
|
|
292
|
+
nav.stackTop(); // current tab stack top path
|
|
293
|
+
nav.stackEntries(); // copy of current tab stack
|
|
294
|
+
nav.activeTab; // active tab prefix
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Overlays
|
|
298
|
+
|
|
299
|
+
Reactive overlay controllers using the `create*` pattern (like `createStore`, `createRouter`).
|
|
300
|
+
|
|
301
|
+
`createPopover()` and `createModal()` automatically inject a Elur framework
|
|
302
|
+
delegate when `component` is a function (e.g. `() => html\`...\``), so you can
|
|
303
|
+
pass Elur templates as overlay content without any extra setup.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { html } from "@elurjs/core";
|
|
307
|
+
import { createToast, createAlert, createModal, createPopover, confirm, withLoading } from "@elurjs/ionic";
|
|
308
|
+
|
|
309
|
+
function MyPage() {
|
|
310
|
+
const toast = createToast();
|
|
311
|
+
const modal = createModal();
|
|
312
|
+
const popover = createPopover();
|
|
313
|
+
|
|
314
|
+
const save = async () => {
|
|
315
|
+
await withLoading({ message: "Saving..." }, async () => {
|
|
316
|
+
await fetch("/api/save", { method: "POST" });
|
|
317
|
+
});
|
|
318
|
+
toast.present({ message: "Saved!", duration: 1500 });
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const openModal = () => modal.present({
|
|
322
|
+
component: () => html`<ion-content><h1>Modal content mounted by Elur!</h1></ion-content>`,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const openPopover = (event: Event) => popover.present({
|
|
326
|
+
event,
|
|
327
|
+
component: () => html`
|
|
328
|
+
<div style="padding: 20px;">
|
|
329
|
+
<ion-button @click=${() => popover.dismiss()}>Close</ion-button>
|
|
330
|
+
</div>
|
|
331
|
+
`,
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return html`
|
|
335
|
+
<ion-content>
|
|
336
|
+
<ion-button @click=${save}>Save</ion-button>
|
|
337
|
+
<ion-button @click=${openModal}>Open Modal</ion-button>
|
|
338
|
+
<ion-button @click=${(e: Event) => openPopover(e)}>Open Popover</ion-button>
|
|
339
|
+
${() => modal.presented.value ? html`<p>Modal is open</p>` : null}
|
|
340
|
+
</ion-content>
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
> **Note:** The framework delegate is injected automatically when
|
|
346
|
+
> `component` is a function. If you pass a string (tag name) or
|
|
347
|
+
> `HTMLElement`, no delegate is needed and Ionic handles it natively.
|
|
348
|
+
|
|
349
|
+
### Available controllers
|
|
350
|
+
|
|
351
|
+
| Controller | Description |
|
|
352
|
+
|---|---|
|
|
353
|
+
| `createToast()` | Reactive toast |
|
|
354
|
+
| `createAlert()` | Reactive alert |
|
|
355
|
+
| `createLoading()` | Reactive loading spinner |
|
|
356
|
+
| `createActionSheet()` | Reactive action sheet |
|
|
357
|
+
| `createPopover()` | Reactive popover (auto-injects Elur delegate for `component: () => html\`...\``) |
|
|
358
|
+
| `createModal()` | Reactive modal (auto-injects Elur delegate for `component: () => html\`...\``) |
|
|
359
|
+
| `createPicker()` | Column-based picker (lazy-registers `ion-picker-legacy`) |
|
|
360
|
+
|
|
361
|
+
### One-shot helpers
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import { showToast, withLoading, confirm } from "@elurjs/ionic";
|
|
365
|
+
|
|
366
|
+
showToast({ message: "Done!", duration: 1000 });
|
|
367
|
+
|
|
368
|
+
const data = await withLoading({ message: "Fetching..." },
|
|
369
|
+
() => fetch("/api/data").then(r => r.json()));
|
|
370
|
+
|
|
371
|
+
const yes = await confirm({ header: "Delete", message: "Sure?", confirmText: "Delete" });
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Page-state persistence
|
|
375
|
+
|
|
376
|
+
Opt-in persistence of serializable state across navigation and app restarts.
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
import { signal } from "@elurjs/core";
|
|
380
|
+
import { createPageState, IonPage } from "@elurjs/ionic";
|
|
381
|
+
|
|
382
|
+
class SearchPage extends IonPage {
|
|
383
|
+
private query = signal("");
|
|
384
|
+
private results = signal<string[]>([]);
|
|
385
|
+
private state = createPageState("search", {
|
|
386
|
+
query: this.query,
|
|
387
|
+
results: this.results,
|
|
388
|
+
}, { storage: "local" }); // persists across app restarts
|
|
389
|
+
|
|
390
|
+
override ionViewWillEnter() { this.state.restore(); }
|
|
391
|
+
override ionViewWillLeave() { this.state.save(); }
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Rules:**
|
|
396
|
+
- Only serializable data (primitives, plain arrays/objects)
|
|
397
|
+
- DOM nodes, functions, symbols, class instances → rejected with warning
|
|
398
|
+
- `sessionStorage` (default) or `localStorage` (opt-in)
|
|
399
|
+
- `clearAllPageState()` for logout flows
|
|
400
|
+
|
|
401
|
+
## Capacitor (optional native)
|
|
402
|
+
|
|
403
|
+
Isolated behind `@elurjs/ionic/capacitor` — **zero web bundle cost** (0 bytes of `@capacitor/*` in main bundle).
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
import { createCapacitorApp } from "@elurjs/ionic/capacitor";
|
|
407
|
+
|
|
408
|
+
const app = createCapacitorApp({
|
|
409
|
+
statusBar: { style: "dark", backgroundColor: "#1a1a2e" },
|
|
410
|
+
splashScreen: { fadeOutDuration: 200 },
|
|
411
|
+
backButton: { defaultHref: "/home" },
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
await app.ready(); // configures status bar, hides splash, wires back button
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Individual plugins
|
|
418
|
+
|
|
419
|
+
```ts
|
|
420
|
+
import { Haptics, StatusBar, App, Keyboard } from "@elurjs/ionic/capacitor";
|
|
421
|
+
|
|
422
|
+
await Haptics.impact("medium"); // no-op on web
|
|
423
|
+
await StatusBar.setStyle({ style: "dark" }); // no-op on web
|
|
424
|
+
App.onBackButton((info) => { /* ... */ }); // no-op on web
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
All methods are **no-ops on web** — safe to call unconditionally.
|
|
428
|
+
|
|
429
|
+
## Tabs
|
|
430
|
+
|
|
431
|
+
```ts
|
|
432
|
+
import { createBottomTabBar, createTabsLayout, IonRouterOutlet, NavigationManager } from "@elurjs/ionic";
|
|
433
|
+
import { home, search, person, settings } from "ionicons/icons";
|
|
434
|
+
|
|
435
|
+
const nav = new NavigationManager({ tabs: ["/", "/search", "/profile", "/settings"] });
|
|
436
|
+
|
|
437
|
+
const outlet = new IonRouterOutlet(routes, {
|
|
438
|
+
tabs: ["/", "/search", "/profile", "/settings"],
|
|
439
|
+
navigation: nav,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const tabBar = createBottomTabBar([
|
|
443
|
+
{ path: "/", label: "Home", icon: "home", exact: true },
|
|
444
|
+
{ path: "/search", label: "Search", icon: "search" },
|
|
445
|
+
{ path: "/profile", label: "Profile", icon: "person", badge: "!" },
|
|
446
|
+
{ path: "/settings", label: "Settings", icon: "settings" },
|
|
447
|
+
], {
|
|
448
|
+
hiddenPaths: ["/detail/*", "/profile/edit"],
|
|
449
|
+
icons: { home, search, person, settings },
|
|
450
|
+
cssVars: {
|
|
451
|
+
"--background": "var(--app-tab-bg)",
|
|
452
|
+
"--color-selected": "var(--ion-color-primary)",
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Wrap outlet + tab bar in <ion-tabs> with correct CSS layout
|
|
457
|
+
const tabsLayout = createTabsLayout(outlet, tabBar);
|
|
458
|
+
|
|
459
|
+
html`<ion-app>${tabsLayout}</ion-app>`;
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### How tabs work
|
|
463
|
+
|
|
464
|
+
- **Navigation** is driven by the Elur router, not Ionic's internal tab
|
|
465
|
+
selection. Each `ion-tab-button` has `@click.prevent.stop` to prevent
|
|
466
|
+
Ionic's `select()` (which expects `<ion-tab>` children we don't use).
|
|
467
|
+
- **`createTabsLayout()`** wraps the outlet and tab bar in `<ion-tabs>`
|
|
468
|
+
and injects a small CSS snippet to ensure the tab bar sits at the
|
|
469
|
+
bottom and the outlet fills the remaining space.
|
|
470
|
+
- **Tab IDs** are derived from the path: `/` → `root`, `/search` →
|
|
471
|
+
`search`, `/profile/edit` → `profile-edit`. Override with `tabId`.
|
|
472
|
+
- **`selected` state** is set via JS property (`(btn as any).selected =
|
|
473
|
+
isActive`) using a `ref` + `effect` + `nextTick`, because Stencil
|
|
474
|
+
boolean props can't be set via HTML attributes with Elur.
|
|
475
|
+
|
|
476
|
+
### Tab bar options
|
|
477
|
+
|
|
478
|
+
| Option | Type | Description |
|
|
479
|
+
|---|---|---|
|
|
480
|
+
| `slot` | `"top" \| "bottom"` | Tab bar position (default: `"bottom"`) |
|
|
481
|
+
| `layout` | `TabButtonLayout` | Icon/label layout (default: `"icon-top"`) |
|
|
482
|
+
| `hiddenPaths` | `string[]` | Paths where tab bar is hidden (supports `*` wildcards) |
|
|
483
|
+
| `hideWhen` | `(path: string) => boolean` | Dynamic hide callback |
|
|
484
|
+
| `icons` | `Record<string, IconDefinition>` | Icon SVG data for tab icons |
|
|
485
|
+
| `cssVars` | `Record<string, string>` | CSS custom properties on `ion-tab-bar` |
|
|
486
|
+
| `direction` | `NavigationDirection` | Navigation direction on tab switch (default: `"none"`) |
|
|
487
|
+
|
|
488
|
+
### Tab item options
|
|
489
|
+
|
|
490
|
+
| Option | Type | Description |
|
|
491
|
+
|---|---|---|
|
|
492
|
+
| `path` | `string` | Route path |
|
|
493
|
+
| `label` | `string` | Tab label text |
|
|
494
|
+
| `icon` | `string` | Icon name (kebab-case) |
|
|
495
|
+
| `activeIcon` | `string` | Icon name when active (optional) |
|
|
496
|
+
| `exact` | `boolean` | Exact path match (default: `false`) |
|
|
497
|
+
| `tabId` | `string` | Override auto-generated tab ID |
|
|
498
|
+
| `badge` | `string \| number` | Badge content |
|
|
499
|
+
| `badgeColor` | `string` | Badge color (default: `"danger"`) |
|
|
500
|
+
|
|
501
|
+
## Vite plugin
|
|
502
|
+
|
|
503
|
+
Auto-registers only the Ionic components and icons you actually use in `html\`\`` templates.
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
// vite.config.ts
|
|
507
|
+
import { elurIonic } from "@elurjs/ionic/vite-plugin";
|
|
508
|
+
|
|
509
|
+
export default defineConfig({
|
|
510
|
+
plugins: [elurIonic()],
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
// app entry — imports the auto-generated virtual module
|
|
516
|
+
import "virtual:elur-ionic/registration";
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Features:
|
|
520
|
+
- Scans `html\`\`` for `<ion-*>` tags → generates direct subpath imports
|
|
521
|
+
- Scans `name="icon-name"` on `<ion-icon>` → generates `ionicons/icons` imports
|
|
522
|
+
- Warns on dynamic tags/icons (with allowlist suppression)
|
|
523
|
+
- `elurIonic({ allowTags: [...], allowIcons: [...] })` for dynamic usage
|
|
524
|
+
|
|
525
|
+
### `allowTags` and `allowIcons` (important for lazy-loaded pages)
|
|
526
|
+
|
|
527
|
+
The Vite plugin scans your source files for `<ion-*>` tags and `name="icon-name"`
|
|
528
|
+
attributes, then generates a virtual module that imports and registers only what
|
|
529
|
+
you use. However, in dev mode, Vite loads modules on-demand. The virtual module
|
|
530
|
+
is served when your app entry imports it at startup, but pages loaded lazily by
|
|
531
|
+
the router may not have been scanned yet.
|
|
532
|
+
|
|
533
|
+
**Use `allowTags` and `allowIcons` to explicitly list tags/icons used in
|
|
534
|
+
lazy-loaded pages.** The plugin emits warnings when it detects tags or icons
|
|
535
|
+
that are not in the allowlists, so you know exactly what to add:
|
|
536
|
+
|
|
537
|
+
```text
|
|
538
|
+
[elur-ionic] Tags used in src/pages/HomePage.ts but not in allowTags:
|
|
539
|
+
ion-header, ion-toolbar, ion-title, ion-content, ion-button, ...
|
|
540
|
+
Add them to `elurIonic({ allowTags: [...] })` to ensure they are
|
|
541
|
+
registered before first use.
|
|
542
|
+
|
|
543
|
+
[elur-ionic] Icons used in src/pages/HomePage.ts but not in allowIcons:
|
|
544
|
+
flash-outline, leaf-outline, toast-outline, ...
|
|
545
|
+
Add them to `elurIonic({ allowIcons: [...] })` to ensure they are
|
|
546
|
+
registered before first use.
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
If new tags/icons are discovered after the registration module was already
|
|
550
|
+
served, an additional warning is emitted:
|
|
551
|
+
|
|
552
|
+
```text
|
|
553
|
+
[elur-ionic] New tags discovered after registration: ion-header, ...
|
|
554
|
+
These were NOT included in the registration module. Add them to
|
|
555
|
+
allowTags and reload.
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Example with full allowlists:
|
|
559
|
+
|
|
560
|
+
```ts
|
|
561
|
+
// vite.config.ts
|
|
562
|
+
import { defineConfig } from "vite";
|
|
563
|
+
import { elur } from "@elurjs/vite-plugin-elur";
|
|
564
|
+
import { elurIonic } from "@elurjs/ionic/vite-plugin";
|
|
565
|
+
|
|
566
|
+
export default defineConfig({
|
|
567
|
+
plugins: [
|
|
568
|
+
elur(),
|
|
569
|
+
elurIonic({
|
|
570
|
+
allowTags: [
|
|
571
|
+
"ion-app", "ion-header", "ion-toolbar", "ion-title", "ion-content",
|
|
572
|
+
"ion-buttons", "ion-button", "ion-back-button",
|
|
573
|
+
"ion-tab-bar", "ion-tab-button",
|
|
574
|
+
"ion-list", "ion-item", "ion-label",
|
|
575
|
+
"ion-card", "ion-card-content",
|
|
576
|
+
"ion-input", "ion-toggle", "ion-select", "ion-select-option",
|
|
577
|
+
"ion-icon", "ion-chip", "ion-badge",
|
|
578
|
+
// Overlays (programmatic)
|
|
579
|
+
"ion-toast", "ion-alert", "ion-loading",
|
|
580
|
+
"ion-action-sheet", "ion-popover", "ion-modal",
|
|
581
|
+
],
|
|
582
|
+
allowIcons: [
|
|
583
|
+
"home", "search", "person", "settings",
|
|
584
|
+
"arrow-back", "arrow-forward",
|
|
585
|
+
"share-outline", "link-outline",
|
|
586
|
+
// ... all icons used in your templates
|
|
587
|
+
],
|
|
588
|
+
}),
|
|
589
|
+
],
|
|
590
|
+
});
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## API reference
|
|
594
|
+
|
|
595
|
+
### Core
|
|
596
|
+
|
|
597
|
+
| Export | Description |
|
|
598
|
+
|---|---|
|
|
599
|
+
| `IonRouterOutlet` | Router outlet with cache policies, guards, tabs |
|
|
600
|
+
| `IonPage` | Base class for pages with lifecycle hooks |
|
|
601
|
+
| `IonBackButton(defaultHref?)` | Back button component |
|
|
602
|
+
| `createBottomTabBar(tabs, opts?)` | Bottom tab bar |
|
|
603
|
+
| `initializeElurIonic()` | Initialize Ionic Core (incremental) |
|
|
604
|
+
| `registerIonicComponents(...definers)` | Register specific components |
|
|
605
|
+
| `registerIonicons(icons)` | Register specific icons |
|
|
606
|
+
| `setupElurIonic(opts?)` | 1.x compatibility facade |
|
|
607
|
+
| `createPageState(pageId, signals, opts?)` | Page-state persistence |
|
|
608
|
+
| `clearAllPageState(backend?, ns?)` | Clear all persisted state |
|
|
609
|
+
| `NavigationManager` | Single navigation authority (hooks, tabs, invalidation) |
|
|
610
|
+
| `StackManager` | Per-tab navigation stack management |
|
|
611
|
+
|
|
612
|
+
### Overlays
|
|
613
|
+
|
|
614
|
+
| Export | Description |
|
|
615
|
+
|---|---|
|
|
616
|
+
| `createToast()` / `createAlert()` / `createLoading()` | Reactive overlay controllers |
|
|
617
|
+
| `createActionSheet()` / `createPopover()` / `createModal()` | Reactive overlay controllers |
|
|
618
|
+
| `createPicker()` | Column-based picker (lazy-registers `ion-picker-legacy`) |
|
|
619
|
+
| `showToast(opts)` | One-shot toast |
|
|
620
|
+
| `withLoading(opts, task)` | Loading + async task + auto-dismiss |
|
|
621
|
+
| `confirm(opts)` | Promise-based confirm dialog |
|
|
622
|
+
|
|
623
|
+
### Capacitor (`@elurjs/ionic/capacitor`)
|
|
624
|
+
|
|
625
|
+
| Export | Description |
|
|
626
|
+
|---|---|
|
|
627
|
+
| `createCapacitorApp(opts)` | Bootstrap helper (status bar, splash, back button) |
|
|
628
|
+
| `StatusBar` / `SplashScreen` / `Keyboard` | Plugin wrappers (no-op on web) |
|
|
629
|
+
| `Haptics` / `App` | Plugin wrappers (no-op on web) |
|
|
630
|
+
| `isNative()` / `isWeb()` | Platform detection |
|
|
631
|
+
|
|
632
|
+
### Vite plugin (`@elurjs/ionic/vite-plugin`)
|
|
633
|
+
|
|
634
|
+
| Export | Description |
|
|
635
|
+
|---|---|
|
|
636
|
+
| `elurIonic(opts?)` | Vite plugin for auto-registration |
|
|
637
|
+
| `generateRegistrationModule(tags, icons, opts)` | Code generator (for testing) |
|
|
638
|
+
|
|
639
|
+
## Comparison
|
|
640
|
+
|
|
641
|
+
| Feature | `@ionic/angular` | `@ionic/react` | `@elurjs/ionic` |
|
|
642
|
+
|---|---|---|---|
|
|
643
|
+
| Virtual DOM | Angular | React | **None** |
|
|
644
|
+
| Bundle size overhead | Angular runtime | React runtime | **Zero** |
|
|
645
|
+
| Tree-shakeable components | No | No | **Yes** |
|
|
646
|
+
| Auto-registration plugin | No | No | **Yes** (Vite) |
|
|
647
|
+
| Cache policies (LRU/TTL) | No | No | **Yes** |
|
|
648
|
+
| Page-state persistence | Manual | Manual | **Built-in** |
|
|
649
|
+
| Capacitor integration | External | External | **Optional subpath** |
|
|
650
|
+
| Reactive overlays | Manual | Manual | **`create*` pattern** |
|
|
651
|
+
|
|
652
|
+
## Testing
|
|
653
|
+
|
|
654
|
+
All tests run against real `@ionic/core` (no mocks).
|
|
655
|
+
|
|
656
|
+
| Suite | Tests | What it covers |
|
|
657
|
+
|---|---|---|
|
|
658
|
+
| Unit (Vitest) | 235 | Cache policies, navigation, overlays, page-state, Capacitor, Vite plugin, bundles |
|
|
659
|
+
| E2E application | 19 | Navigation, lifecycle, overlays (toast/modal/alert/loading/action-sheet/picker), back button |
|
|
660
|
+
| E2E contract | 24 | Custom elements, `commit()`, lifecycle events, overlay present/dismiss, properties, form events, network isolation |
|
|
661
|
+
| E2E accessibility/leaks | 13 | ARIA roles, shadow DOM, focus management, repeated navigation, overlay disposal, listener cleanup |
|
|
662
|
+
|
|
663
|
+
**Total: 291 tests, all passing.**
|
|
664
|
+
|
|
665
|
+
### Bundle validation
|
|
666
|
+
|
|
667
|
+
```bash
|
|
668
|
+
npm run measure-bundles
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Validates tree-shaking with 4 fixtures:
|
|
672
|
+
|
|
673
|
+
| Fixture | Gzip | Purpose |
|
|
674
|
+
|---|---|---|
|
|
675
|
+
| minimal (1 component) | 0.82 KB | Only `ion-button` — no other component code |
|
|
676
|
+
| partial (layout + buttons) | 1.05 KB | Two bundles — no forms/lists/overlays |
|
|
677
|
+
| full (all + overlays + nav) | 7.26 KB | Worst-case bundle |
|
|
678
|
+
| capacitor-only | 0.59 KB | Zero `@capacitor/*` in web bundle |
|
|
679
|
+
|
|
680
|
+
Minimal = 11.3% of full bundle (gzip) — tree-shaking works.
|
|
681
|
+
|
|
682
|
+
## Limitations
|
|
683
|
+
|
|
684
|
+
### iOS swipe-back gesture
|
|
685
|
+
|
|
686
|
+
iOS swipe-back (interactive pop gesture) is **not supported**. The integration
|
|
687
|
+
does not assign `swipeHandler` on `ion-router-outlet`. Native swipe-back requires
|
|
688
|
+
a WebKit/mobile test that reproduces both a completed swipe and a cancelled
|
|
689
|
+
swipe with correct lifecycle rollback — this has not been demonstrated.
|
|
690
|
+
|
|
691
|
+
Use `ion-back-button` or programmatic `router.back()` for back navigation.
|
|
692
|
+
|
|
693
|
+
## License
|
|
694
|
+
|
|
695
|
+
MIT
|