voyager-ionic-core 8.8.4 → 8.8.6
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/components/ion-action-sheet.js +1 -1
- package/components/ion-content.js +1 -1
- package/components/ion-datetime.js +1 -1
- package/components/ion-modal.js +1 -1
- package/components/ion-radio-group.js +1 -1
- package/components/ion-segment.js +1 -1
- package/components/ion-select-modal.js +1 -1
- package/components/ion-select-popover.js +1 -1
- package/components/ion-select.js +1 -1
- package/components/p-0z8QSI5b.js +4 -0
- package/components/{p-ApmKVjaE.js → p-BGHGpkPX.js} +1 -1
- package/components/p-BlNv564p.js +4 -0
- package/components/p-D-cP12ZN.js +4 -0
- package/components/p-DZhbcvo5.js +4 -0
- package/components/{p-Bk2zuNWT.js → p-DvOO1fxp.js} +1 -1
- package/dist/cjs/ion-action-sheet.cjs.entry.js +4 -4
- package/dist/cjs/ion-app_8.cjs.entry.js +1 -1
- package/dist/cjs/ion-datetime_3.cjs.entry.js +30 -14
- package/dist/cjs/ion-modal.cjs.entry.js +99 -45
- package/dist/cjs/ion-radio_2.cjs.entry.js +13 -1
- package/dist/cjs/ion-segment_2.cjs.entry.js +6 -2
- package/dist/cjs/ion-select-modal.cjs.entry.js +18 -7
- package/dist/cjs/ion-select_3.cjs.entry.js +18 -7
- package/dist/collection/components/action-sheet/action-sheet.js +4 -4
- package/dist/collection/components/content/content.css +1 -1
- package/dist/collection/components/datetime/datetime.js +30 -14
- package/dist/collection/components/modal/modal.js +73 -44
- package/dist/collection/components/modal/safe-area-utils.js +27 -2
- package/dist/collection/components/radio-group/radio-group.js +13 -1
- package/dist/collection/components/radio-group/test/fixtures.js +2 -2
- package/dist/collection/components/segment/segment.js +6 -2
- package/dist/collection/components/select-modal/select-modal.js +18 -7
- package/dist/collection/components/select-modal/test/fixtures.js +4 -0
- package/dist/collection/components/select-popover/select-popover.js +18 -7
- package/dist/collection/components/select-popover/test/fixtures.js +4 -0
- package/dist/docs.json +1 -1
- package/dist/esm/ion-action-sheet.entry.js +4 -4
- package/dist/esm/ion-app_8.entry.js +1 -1
- package/dist/esm/ion-datetime_3.entry.js +30 -14
- package/dist/esm/ion-modal.entry.js +99 -45
- package/dist/esm/ion-radio_2.entry.js +13 -1
- package/dist/esm/ion-segment_2.entry.js +6 -2
- package/dist/esm/ion-select-modal.entry.js +18 -7
- package/dist/esm/ion-select_3.entry.js +18 -7
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-1ca9c36b.entry.js +4 -0
- package/dist/ionic/p-28a9e720.entry.js +4 -0
- package/dist/ionic/p-7761ef65.entry.js +4 -0
- package/dist/ionic/{p-4dd5e8e0.entry.js → p-8fda6a62.entry.js} +1 -1
- package/dist/ionic/p-a893c61c.entry.js +4 -0
- package/dist/ionic/{p-9eac4eb1.entry.js → p-aa812c4b.entry.js} +1 -1
- package/dist/ionic/p-cb27fe68.entry.js +4 -0
- package/dist/ionic/p-ce2edb36.entry.js +4 -0
- package/dist/types/components/datetime/datetime.d.ts +7 -0
- package/dist/types/components/modal/modal.d.ts +41 -3
- package/dist/types/components/modal/safe-area-utils.d.ts +16 -0
- package/dist/types/components/radio-group/test/fixtures.d.ts +1 -1
- package/dist/types/components/select-modal/select-modal.d.ts +1 -0
- package/dist/types/components/select-modal/test/fixtures.d.ts +1 -0
- package/dist/types/components/select-popover/select-popover.d.ts +1 -0
- package/dist/types/components/select-popover/test/fixtures.d.ts +1 -0
- package/hydrate/index.js +189 -81
- package/hydrate/index.mjs +189 -81
- package/package.json +1 -1
- package/components/p-1KVKSLu5.js +0 -4
- package/components/p-BI7WNErr.js +0 -4
- package/components/p-BTF2nRLo.js +0 -4
- package/components/p-EK4xUz-q.js +0 -4
- package/dist/ionic/p-4c67ce4c.entry.js +0 -4
- package/dist/ionic/p-51c11c47.entry.js +0 -4
- package/dist/ionic/p-5681dde4.entry.js +0 -4
- package/dist/ionic/p-9cdbabbb.entry.js +0 -4
- package/dist/ionic/p-cb78f5a0.entry.js +0 -4
- package/dist/ionic/p-e6c5f060.entry.js +0 -4
|
@@ -1648,6 +1648,12 @@ const createSheetGesture = (baseEl, backdropEl, wrapperEl, initialBreakpoint, ba
|
|
|
1648
1648
|
const MODAL_INSET_MIN_WIDTH = 768;
|
|
1649
1649
|
const MODAL_INSET_MIN_HEIGHT = 600;
|
|
1650
1650
|
const EDGE_THRESHOLD = 5;
|
|
1651
|
+
/**
|
|
1652
|
+
* CSS values for `--width` / `--height` that are treated as fullscreen
|
|
1653
|
+
* (modal touches the corresponding screen edges). Empty string means the
|
|
1654
|
+
* property was not overridden. See `hasCustomModalDimensions()`.
|
|
1655
|
+
*/
|
|
1656
|
+
const FULLSCREEN_SIZE_VALUES = new Set(['', '100%', '100vw', '100vh', '100dvw', '100dvh', '100svw', '100svh']);
|
|
1651
1657
|
/**
|
|
1652
1658
|
* Cache for resolved root safe-area-top value, invalidated once per frame.
|
|
1653
1659
|
*/
|
|
@@ -1696,6 +1702,22 @@ const getRootSafeAreaTop = () => {
|
|
|
1696
1702
|
}
|
|
1697
1703
|
return value;
|
|
1698
1704
|
};
|
|
1705
|
+
/**
|
|
1706
|
+
* True when the modal host declares BOTH a non-fullscreen `--width` AND a
|
|
1707
|
+
* non-fullscreen `--height` (i.e. a centered-dialog-like modal that doesn't
|
|
1708
|
+
* touch any screen edge).
|
|
1709
|
+
*
|
|
1710
|
+
* The conservative "both axes" check avoids mis-zeroing safe-area for
|
|
1711
|
+
* partial-custom modals where the modal still touches top/bottom edges
|
|
1712
|
+
* (e.g. only `--width` overridden). Partial cases fall through to the
|
|
1713
|
+
* existing position-based post-animation correction.
|
|
1714
|
+
*/
|
|
1715
|
+
const hasCustomModalDimensions = (hostEl) => {
|
|
1716
|
+
const styles = getComputedStyle(hostEl);
|
|
1717
|
+
const width = styles.getPropertyValue('--width').trim();
|
|
1718
|
+
const height = styles.getPropertyValue('--height').trim();
|
|
1719
|
+
return !FULLSCREEN_SIZE_VALUES.has(width) && !FULLSCREEN_SIZE_VALUES.has(height);
|
|
1720
|
+
};
|
|
1699
1721
|
/**
|
|
1700
1722
|
* Returns the initial safe-area configuration based on modal type.
|
|
1701
1723
|
* This is called before animation starts and uses configuration-based prediction.
|
|
@@ -1730,8 +1752,11 @@ const getInitialSafeAreaConfig = (context) => {
|
|
|
1730
1752
|
}
|
|
1731
1753
|
// On viewports that meet the centered dialog media query breakpoints,
|
|
1732
1754
|
// regular modals render as centered dialogs (not fullscreen), so they
|
|
1733
|
-
// don't touch any screen edges and don't need safe-area insets.
|
|
1734
|
-
|
|
1755
|
+
// don't touch any screen edges and don't need safe-area insets. Also
|
|
1756
|
+
// applies to phone viewports when the modal declares custom --width and
|
|
1757
|
+
// --height; these don't touch screen edges either, so the initial
|
|
1758
|
+
// prediction must be zero to avoid a post-animation correction flash.
|
|
1759
|
+
if (isCenteredDialogViewport() || context.hasCustomDimensions) {
|
|
1735
1760
|
return {
|
|
1736
1761
|
top: '0px',
|
|
1737
1762
|
bottom: '0px',
|
|
@@ -2033,12 +2058,10 @@ const Modal = class {
|
|
|
2033
2058
|
// since the viewport may have crossed the centered-dialog breakpoint.
|
|
2034
2059
|
if (!context.isSheetModal && !context.isCardModal) {
|
|
2035
2060
|
this.updateSafeAreaOverrides();
|
|
2036
|
-
// Re-evaluate fullscreen safe-area padding: clear first, then re-apply
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
}
|
|
2041
|
-
this.applyFullscreenSafeArea();
|
|
2061
|
+
// Re-evaluate fullscreen safe-area padding: clear first, then re-apply.
|
|
2062
|
+
const { contentEl, hasFooter } = this.findContentAndFooter();
|
|
2063
|
+
this.clearContentSafeAreaPadding(contentEl);
|
|
2064
|
+
this.applyFullscreenSafeAreaTo(contentEl, hasFooter);
|
|
2042
2065
|
}
|
|
2043
2066
|
}, 50); // Debounce to avoid excessive calls during active resizing
|
|
2044
2067
|
}
|
|
@@ -2785,6 +2808,11 @@ const Modal = class {
|
|
|
2785
2808
|
}
|
|
2786
2809
|
/**
|
|
2787
2810
|
* Creates the context object for safe-area utilities.
|
|
2811
|
+
*
|
|
2812
|
+
* `hasCustomDimensions` is only set by `setInitialSafeAreaOverrides()`
|
|
2813
|
+
* because it is only read by `getInitialSafeAreaConfig()`. Other callers
|
|
2814
|
+
* (resize handler, post-animation update, fullscreen-padding apply) would
|
|
2815
|
+
* pay a `getComputedStyle()` cost for a value they never consult.
|
|
2788
2816
|
*/
|
|
2789
2817
|
getSafeAreaContext() {
|
|
2790
2818
|
return {
|
|
@@ -2806,7 +2834,7 @@ const Modal = class {
|
|
|
2806
2834
|
* sheets to prevent header content from getting double-offset padding).
|
|
2807
2835
|
*/
|
|
2808
2836
|
setInitialSafeAreaOverrides() {
|
|
2809
|
-
const context = this.getSafeAreaContext();
|
|
2837
|
+
const context = Object.assign(Object.assign({}, this.getSafeAreaContext()), { hasCustomDimensions: hasCustomModalDimensions(this.el) });
|
|
2810
2838
|
const safeAreaConfig = getInitialSafeAreaConfig(context);
|
|
2811
2839
|
applySafeAreaOverrides(this.el, safeAreaConfig);
|
|
2812
2840
|
// Set the internal offset property with the resolved root safe-area-top value
|
|
@@ -2846,59 +2874,85 @@ const Modal = class {
|
|
|
2846
2874
|
applySafeAreaOverrides(el, safeAreaConfig);
|
|
2847
2875
|
}
|
|
2848
2876
|
/**
|
|
2849
|
-
* Applies
|
|
2850
|
-
*
|
|
2877
|
+
* Applies safe-area-bottom scroll padding to ion-content inside
|
|
2878
|
+
* fullscreen modals that have no ion-footer. This prevents content
|
|
2879
|
+
* from being hidden behind the system navigation bar while keeping
|
|
2880
|
+
* the modal background edge-to-edge (no visible gap).
|
|
2851
2881
|
*/
|
|
2852
2882
|
applyFullscreenSafeArea() {
|
|
2853
|
-
const { wrapperEl, el } = this;
|
|
2854
|
-
if (!wrapperEl)
|
|
2855
|
-
return;
|
|
2856
2883
|
const context = this.getSafeAreaContext();
|
|
2857
2884
|
if (context.isSheetModal || context.isCardModal)
|
|
2858
2885
|
return;
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2886
|
+
const { contentEl, hasFooter } = this.findContentAndFooter();
|
|
2887
|
+
this.applyFullscreenSafeAreaTo(contentEl, hasFooter);
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Sets --ion-content-safe-area-padding-bottom on the given ion-content
|
|
2891
|
+
* when no footer is present, so ion-content's .inner-scroll includes
|
|
2892
|
+
* safe-area-bottom in its scroll padding. This keeps the modal background
|
|
2893
|
+
* edge-to-edge while ensuring content scrolls clear of the system nav bar.
|
|
2894
|
+
*
|
|
2895
|
+
* --ion-content-safe-area-padding-bottom is an internal CSS property used
|
|
2896
|
+
* only by this code path. It is not part of ion-content's public API and
|
|
2897
|
+
* should not be set by consumers. The default of 0px makes it a no-op
|
|
2898
|
+
* when unset, which is the expected state for ion-content used outside of
|
|
2899
|
+
* a fullscreen modal without a footer.
|
|
2900
|
+
*/
|
|
2901
|
+
applyFullscreenSafeAreaTo(contentEl, hasFooter) {
|
|
2902
|
+
// Only apply for standard Ionic layouts (has ion-content but no
|
|
2903
|
+
// ion-footer). When a footer is present it handles its own safe-area
|
|
2904
|
+
// padding. Custom modals with raw HTML are developer-controlled.
|
|
2905
|
+
if (!contentEl || hasFooter)
|
|
2906
|
+
return;
|
|
2907
|
+
contentEl.style.setProperty('--ion-content-safe-area-padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Removes the internal --ion-content-safe-area-padding-bottom property
|
|
2911
|
+
* from an already-located ion-content. Callers do their own
|
|
2912
|
+
* findContentAndFooter() so they can also read hasFooter if needed.
|
|
2913
|
+
*/
|
|
2914
|
+
clearContentSafeAreaPadding(contentEl) {
|
|
2915
|
+
if (!contentEl)
|
|
2916
|
+
return;
|
|
2917
|
+
contentEl.style.removeProperty('--ion-content-safe-area-padding-bottom');
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Finds ion-content and ion-footer among direct children and one level of
|
|
2921
|
+
* grandchildren (for wrapped components like <app-footer><ion-footer>).
|
|
2922
|
+
*
|
|
2923
|
+
* Intentionally does NOT use findIonContent() or querySelector() because
|
|
2924
|
+
* those search the full subtree and would match ion-content inside nested
|
|
2925
|
+
* routes/pages. We only want direct slot children (+ one wrapper level).
|
|
2926
|
+
*
|
|
2927
|
+
* Uses a manual loop instead of querySelector(':scope > ...') because
|
|
2928
|
+
* Stencil's mock-doc (used in spec tests) does not support :scope.
|
|
2929
|
+
*/
|
|
2930
|
+
findContentAndFooter() {
|
|
2931
|
+
let contentEl = null;
|
|
2865
2932
|
let hasFooter = false;
|
|
2866
|
-
for (const child of Array.from(el.children)) {
|
|
2933
|
+
for (const child of Array.from(this.el.children)) {
|
|
2867
2934
|
if (child.tagName === 'ION-CONTENT')
|
|
2868
|
-
|
|
2935
|
+
contentEl = child;
|
|
2869
2936
|
if (child.tagName === 'ION-FOOTER')
|
|
2870
2937
|
hasFooter = true;
|
|
2871
2938
|
for (const grandchild of Array.from(child.children)) {
|
|
2872
|
-
if (grandchild.tagName === 'ION-CONTENT')
|
|
2873
|
-
|
|
2939
|
+
if (grandchild.tagName === 'ION-CONTENT' && !contentEl)
|
|
2940
|
+
contentEl = grandchild;
|
|
2874
2941
|
if (grandchild.tagName === 'ION-FOOTER')
|
|
2875
2942
|
hasFooter = true;
|
|
2876
2943
|
}
|
|
2877
2944
|
}
|
|
2878
|
-
|
|
2879
|
-
// but no ion-footer). Custom modals with raw HTML are fully
|
|
2880
|
-
// developer-controlled and should not be modified.
|
|
2881
|
-
if (!hasContent || hasFooter)
|
|
2882
|
-
return;
|
|
2883
|
-
// Reduce wrapper height by safe-area and add equivalent padding so the
|
|
2884
|
-
// total visual size stays the same but the flex content area shrinks.
|
|
2885
|
-
// Using height + padding instead of box-sizing: border-box avoids
|
|
2886
|
-
// breaking custom modals that set --border-width (border-box would
|
|
2887
|
-
// include the border inside the height, changing the layout).
|
|
2888
|
-
wrapperEl.style.setProperty('height', 'calc(var(--height) - var(--ion-safe-area-bottom, 0px))');
|
|
2889
|
-
wrapperEl.style.setProperty('padding-bottom', 'var(--ion-safe-area-bottom, 0px)');
|
|
2945
|
+
return { contentEl, hasFooter };
|
|
2890
2946
|
}
|
|
2891
2947
|
/**
|
|
2892
|
-
* Clears all safe-area overrides and padding
|
|
2948
|
+
* Clears all safe-area overrides and padding.
|
|
2893
2949
|
*/
|
|
2894
2950
|
cleanupSafeAreaOverrides() {
|
|
2895
2951
|
clearSafeAreaOverrides(this.el);
|
|
2896
2952
|
// Remove internal sheet offset property
|
|
2897
2953
|
this.el.style.removeProperty('--ion-modal-offset-top');
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
this.wrapperEl.style.removeProperty('padding-bottom');
|
|
2901
|
-
}
|
|
2954
|
+
const { contentEl } = this.findContentAndFooter();
|
|
2955
|
+
this.clearContentSafeAreaPadding(contentEl);
|
|
2902
2956
|
}
|
|
2903
2957
|
render() {
|
|
2904
2958
|
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
|
|
@@ -2907,20 +2961,20 @@ const Modal = class {
|
|
|
2907
2961
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2908
2962
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2909
2963
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2910
|
-
return (index$3.h(index$3.Host, Object.assign({ key: '
|
|
2964
|
+
return (index$3.h(index$3.Host, Object.assign({ key: '4bf38aa67df9a3f977163bba5423960bbafd16de', "no-router": true,
|
|
2911
2965
|
// Allow the modal to be navigable when the handle is focusable
|
|
2912
2966
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2913
2967
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2914
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '
|
|
2968
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, theme.getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), index$3.h("ion-backdrop", { key: '866da40cc5fc8d3e36637098fb3066a5bc9f4e0f', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && index$3.h("div", { key: '5a2a05514ea8592c8feb0465e504aa7c7af17963', class: "modal-shadow" }), index$3.h("div", Object.assign({ key: '4d327115306451f57d190b06ab8cbb6191a6f1d7',
|
|
2915
2969
|
/*
|
|
2916
2970
|
role and aria-modal must be used on the
|
|
2917
2971
|
same element. They must also be set inside the
|
|
2918
2972
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2919
2973
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2920
2974
|
*/
|
|
2921
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: '
|
|
2975
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (index$3.h("button", { key: 'd1882835cc049232c0d957e3ba1e79676a07d179', class: "modal-handle",
|
|
2922
2976
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2923
|
-
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), index$3.h("slot", { key: '
|
|
2977
|
+
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), index$3.h("slot", { key: '81dc58b09cf7d7022b04cd170f53113604364d5e', onSlotchange: this.onSlotChange }))));
|
|
2924
2978
|
}
|
|
2925
2979
|
get el() { return index$3.getElement(this); }
|
|
2926
2980
|
static get watchers() { return {
|
|
@@ -375,6 +375,18 @@ const RadioGroup = class {
|
|
|
375
375
|
// to the bottom of the screen
|
|
376
376
|
ev.preventDefault();
|
|
377
377
|
}
|
|
378
|
+
// Inside a select interface, Enter commits the focused radio
|
|
379
|
+
// value (matching native <select>). The !ev.repeat guard stops
|
|
380
|
+
// a held Enter on the triggering ion-select from re-committing
|
|
381
|
+
// once focus lands in the opened popover/modal.
|
|
382
|
+
if (ev.key === 'Enter' && inSelectInterface && !ev.repeat) {
|
|
383
|
+
const previousValue = this.value;
|
|
384
|
+
this.value = current.value;
|
|
385
|
+
if (previousValue !== this.value) {
|
|
386
|
+
this.emitValueChange(ev);
|
|
387
|
+
}
|
|
388
|
+
ev.preventDefault();
|
|
389
|
+
}
|
|
378
390
|
}
|
|
379
391
|
}
|
|
380
392
|
/** @internal */
|
|
@@ -407,7 +419,7 @@ const RadioGroup = class {
|
|
|
407
419
|
const { label, labelId, el, name, value } = this;
|
|
408
420
|
const mode = ionicGlobal.getIonMode(this);
|
|
409
421
|
helpers.renderHiddenInput(true, el, name, value, false);
|
|
410
|
-
return (index.h(index.Host, { key: '
|
|
422
|
+
return (index.h(index.Host, { key: '377e4aa3a656cc84b742f9d7a7d4be65d20c69f5', role: "radiogroup", "aria-labelledby": label ? labelId : null, "aria-describedby": this.hintTextId, "aria-invalid": this.isInvalid ? 'true' : undefined, onClick: this.onClick, class: mode }, this.renderHintText(), index.h("slot", { key: 'c3187a2497773b4f15cea3b413b036502bcec8c0' })));
|
|
411
423
|
}
|
|
412
424
|
get el() { return index.getElement(this); }
|
|
413
425
|
static get watchers() { return {
|
|
@@ -323,6 +323,7 @@ const Segment = class {
|
|
|
323
323
|
return segmentContent === null || segmentContent === void 0 ? void 0 : segmentContent.closest('ion-segment-view');
|
|
324
324
|
}
|
|
325
325
|
handleSegmentViewScroll(ev) {
|
|
326
|
+
var _a;
|
|
326
327
|
const { scrollRatio, isManualScroll } = ev.detail;
|
|
327
328
|
if (!isManualScroll) {
|
|
328
329
|
return;
|
|
@@ -339,6 +340,9 @@ const Segment = class {
|
|
|
339
340
|
const index = buttons.findIndex((button) => button.value === this.value);
|
|
340
341
|
const current = buttons[index];
|
|
341
342
|
const nextIndex = Math.round(scrollRatio * (buttons.length - 1));
|
|
343
|
+
if ((_a = buttons[nextIndex]) === null || _a === void 0 ? void 0 : _a.disabled) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
342
346
|
if (this.lastNextIndex === undefined || this.lastNextIndex !== nextIndex) {
|
|
343
347
|
this.lastNextIndex = nextIndex;
|
|
344
348
|
this.triggerScrollOnValueChange = false;
|
|
@@ -549,14 +553,14 @@ const Segment = class {
|
|
|
549
553
|
}
|
|
550
554
|
render() {
|
|
551
555
|
const mode = ionicGlobal.getIonMode(this);
|
|
552
|
-
return (index.h(index.Host, { key: '
|
|
556
|
+
return (index.h(index.Host, { key: 'eda6b7b88b7967b55cf9098c59b655b348a42224', role: "tablist", onClick: this.onClick, class: theme.createColorClasses(this.color, {
|
|
553
557
|
[mode]: true,
|
|
554
558
|
'in-toolbar': theme.hostContext('ion-toolbar', this.el),
|
|
555
559
|
'in-toolbar-color': theme.hostContext('ion-toolbar[color]', this.el),
|
|
556
560
|
'segment-activated': this.activated,
|
|
557
561
|
'segment-disabled': this.disabled,
|
|
558
562
|
'segment-scrollable': this.scrollable,
|
|
559
|
-
}) }, index.h("slot", { key: '
|
|
563
|
+
}) }, index.h("slot", { key: 'fdb451f235ce59c5bb50c61a13c69160ece2d5df', onSlotchange: this.onSlottedItemsChange })));
|
|
560
564
|
}
|
|
561
565
|
get el() { return index.getElement(this); }
|
|
562
566
|
static get watchers() { return {
|
|
@@ -22,6 +22,10 @@ const selectModalMdCss = () => `.sc-ion-select-modal-md-h{height:100%}ion-list.s
|
|
|
22
22
|
const SelectModal = class {
|
|
23
23
|
constructor(hostRef) {
|
|
24
24
|
index.registerInstance(this, hostRef);
|
|
25
|
+
// Tracks the option that received Enter-keydown so keyup only
|
|
26
|
+
// dismisses when the press started on the same option. Prevents
|
|
27
|
+
// Enter on the triggering ion-select from auto-dismissing.
|
|
28
|
+
this.pendingEnterTarget = null;
|
|
25
29
|
/**
|
|
26
30
|
* The text to display on the cancel button.
|
|
27
31
|
*/
|
|
@@ -71,15 +75,22 @@ const SelectModal = class {
|
|
|
71
75
|
return (index.h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, this.options.map((option) => (index.h("ion-item", { lines: "none", class: Object.assign({
|
|
72
76
|
// TODO FW-4784
|
|
73
77
|
'item-radio-checked': option.value === checked
|
|
74
|
-
}, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(),
|
|
78
|
+
}, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, justify: "start", labelPlacement: "end", onClick: () => this.closeModal(), onKeyDown: (ev) => {
|
|
79
|
+
if (ev.key === 'Enter' && !ev.repeat) {
|
|
80
|
+
this.pendingEnterTarget = ev.currentTarget;
|
|
81
|
+
}
|
|
82
|
+
}, onKeyUp: (ev) => {
|
|
75
83
|
if (ev.key === ' ') {
|
|
76
|
-
|
|
77
|
-
* Selecting a radio option with keyboard navigation,
|
|
78
|
-
* either through the Enter or Space keys, should
|
|
79
|
-
* dismiss the modal.
|
|
80
|
-
*/
|
|
84
|
+
// Space selects and dismisses in one press.
|
|
81
85
|
this.closeModal();
|
|
82
86
|
}
|
|
87
|
+
else if (ev.key === 'Enter') {
|
|
88
|
+
const shouldClose = this.pendingEnterTarget === ev.currentTarget;
|
|
89
|
+
this.pendingEnterTarget = null;
|
|
90
|
+
if (shouldClose) {
|
|
91
|
+
this.closeModal();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
83
94
|
} }, option.text))))));
|
|
84
95
|
}
|
|
85
96
|
renderCheckboxOptions() {
|
|
@@ -94,7 +105,7 @@ const SelectModal = class {
|
|
|
94
105
|
} }, option.text))));
|
|
95
106
|
}
|
|
96
107
|
render() {
|
|
97
|
-
return (index.h(index.Host, { key: '
|
|
108
|
+
return (index.h(index.Host, { key: 'fda0bf6f93cd5ec9f3c64f88a52de849e0e140a2', class: ionicGlobal.getIonMode(this) }, index.h("ion-header", { key: '27c0b17175a53db9ff159feeeb96451a3f011dab' }, index.h("ion-toolbar", { key: '91a4155ebc317fbc9f1bb3e26a7e94754b953c9b' }, this.header !== undefined && index.h("ion-title", { key: 'f6dae8e4e381f322cc90efefd9bb6ef81d4d2f3e' }, this.header), index.h("ion-buttons", { key: 'e7760532fb2e7e7385ed6e62097d92d96ff20148', slot: "end" }, index.h("ion-button", { key: '4999b6fc46cba138186546dca67b7950855e6fb7', onClick: () => this.closeModal() }, this.cancelText)))), index.h("ion-content", { key: 'c73f80a4bc25b9061ea65cf11e5d811c1a4d8704' }, index.h("ion-list", { key: 'b21905d15b36ad5eb45845e768918d2763cf48b1' }, this.multiple === true ? this.renderCheckboxOptions() : this.renderRadioOptions()))));
|
|
98
109
|
}
|
|
99
110
|
get el() { return index.getElement(this); }
|
|
100
111
|
};
|
|
@@ -892,6 +892,10 @@ const selectPopoverMdCss = () => `.sc-ion-select-popover-md-h ion-list.sc-ion-se
|
|
|
892
892
|
const SelectPopover = class {
|
|
893
893
|
constructor(hostRef) {
|
|
894
894
|
index.registerInstance(this, hostRef);
|
|
895
|
+
// Tracks the option that received Enter-keydown so keyup only
|
|
896
|
+
// dismisses when the press started on the same option. Prevents
|
|
897
|
+
// Enter on the triggering ion-select from auto-dismissing.
|
|
898
|
+
this.pendingEnterTarget = null;
|
|
895
899
|
/**
|
|
896
900
|
* An array of options for the popover
|
|
897
901
|
*/
|
|
@@ -969,21 +973,28 @@ const SelectPopover = class {
|
|
|
969
973
|
return (index.h("ion-radio-group", { value: checked, onIonChange: (ev) => this.callOptionHandler(ev) }, options.map((option) => (index.h("ion-item", { class: Object.assign({
|
|
970
974
|
// TODO FW-4784
|
|
971
975
|
'item-radio-checked': option.value === checked
|
|
972
|
-
}, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(),
|
|
976
|
+
}, theme.getClassMap(option.cssClass)) }, index.h("ion-radio", { value: option.value, disabled: option.disabled, onClick: () => this.dismissParentPopover(), onKeyDown: (ev) => {
|
|
977
|
+
if (ev.key === 'Enter' && !ev.repeat) {
|
|
978
|
+
this.pendingEnterTarget = ev.currentTarget;
|
|
979
|
+
}
|
|
980
|
+
}, onKeyUp: (ev) => {
|
|
973
981
|
if (ev.key === ' ') {
|
|
974
|
-
|
|
975
|
-
* Selecting a radio option with keyboard navigation,
|
|
976
|
-
* either through the Enter or Space keys, should
|
|
977
|
-
* dismiss the popover.
|
|
978
|
-
*/
|
|
982
|
+
// Space selects and dismisses in one press.
|
|
979
983
|
this.dismissParentPopover();
|
|
980
984
|
}
|
|
985
|
+
else if (ev.key === 'Enter') {
|
|
986
|
+
const shouldDismiss = this.pendingEnterTarget === ev.currentTarget;
|
|
987
|
+
this.pendingEnterTarget = null;
|
|
988
|
+
if (shouldDismiss) {
|
|
989
|
+
this.dismissParentPopover();
|
|
990
|
+
}
|
|
991
|
+
}
|
|
981
992
|
} }, option.text))))));
|
|
982
993
|
}
|
|
983
994
|
render() {
|
|
984
995
|
const { header, message, options, subHeader } = this;
|
|
985
996
|
const hasSubHeaderOrMessage = subHeader !== undefined || message !== undefined;
|
|
986
|
-
return (index.h(index.Host, { key: '
|
|
997
|
+
return (index.h(index.Host, { key: 'e7449a1ecfcdbf45a79f8e26a00253c4e146448a', class: ionicGlobal.getIonMode(this) }, index.h("ion-list", { key: '52abdfc8668c3429a0dcefef8ddedb6647fdd894' }, header !== undefined && index.h("ion-list-header", { key: '978e5c03728756feafcc60a0e10e6ec59bf2ae11' }, header), hasSubHeaderOrMessage && (index.h("ion-item", { key: 'e93c44e7f07a76def16e4b11f0fb4780d84ed402' }, index.h("ion-label", { key: 'bba1aac43b0bc7f4f00978dd8301985233f3725c', class: "ion-text-wrap" }, subHeader !== undefined && index.h("h3", { key: 'ad96f6017cf2cc5219540bded2c4f1ca3b532de2' }, subHeader), message !== undefined && index.h("p", { key: '3fd038921dc40c4d0c29734433984b279ccaeec3' }, message)))), this.renderOptions(options))));
|
|
987
998
|
}
|
|
988
999
|
get el() { return index.getElement(this); }
|
|
989
1000
|
};
|
|
@@ -372,7 +372,7 @@ export class ActionSheet {
|
|
|
372
372
|
if (isRadio) {
|
|
373
373
|
htmlAttrs['aria-checked'] = isActiveRadio ? 'true' : 'false';
|
|
374
374
|
}
|
|
375
|
-
return (h("button", Object.assign({}, htmlAttrs, { role: isRadio ? 'radio' : undefined, type: "button", id: buttonId, class: Object.assign(Object.assign({}, buttonClass(b)), { 'action-sheet-selected': isActiveRadio }), onClick: () => {
|
|
375
|
+
return (h("button", Object.assign({}, htmlAttrs, { role: isRadio ? 'radio' : undefined, type: "button", id: buttonId, class: Object.assign(Object.assign({}, buttonClass(b)), (isRadio && { 'action-sheet-selected': isActiveRadio })), onClick: () => {
|
|
376
376
|
if (isRadio) {
|
|
377
377
|
this.selectRadioButton(b);
|
|
378
378
|
}
|
|
@@ -387,12 +387,12 @@ export class ActionSheet {
|
|
|
387
387
|
const cancelButton = allButtons.find((b) => b.role === 'cancel');
|
|
388
388
|
const buttons = allButtons.filter((b) => b.role !== 'cancel');
|
|
389
389
|
const headerID = `action-sheet-${overlayIndex}-header`;
|
|
390
|
-
return (h(Host, Object.assign({ key: '
|
|
390
|
+
return (h(Host, Object.assign({ key: '48b63b870f2816b4cad3c606f3d9956854cee79a', role: "dialog", "aria-modal": "true", "aria-labelledby": header !== undefined ? headerID : null, tabindex: "-1" }, htmlAttributes, { style: {
|
|
391
391
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
392
|
-
}, class: Object.assign(Object.assign({ [mode]: true }, getClassMap(this.cssClass)), { 'overlay-hidden': true, 'action-sheet-translucent': this.translucent }), onIonActionSheetWillDismiss: this.dispatchCancelHandler, onIonBackdropTap: this.onBackdropTap }), h("ion-backdrop", { key: '
|
|
392
|
+
}, class: Object.assign(Object.assign({ [mode]: true }, getClassMap(this.cssClass)), { 'overlay-hidden': true, 'action-sheet-translucent': this.translucent }), onIonActionSheetWillDismiss: this.dispatchCancelHandler, onIonBackdropTap: this.onBackdropTap }), h("ion-backdrop", { key: '41dd5781f139d26b3feea33ab387451aeafacd51', tappable: this.backdropDismiss }), h("div", { key: 'f797c2657782e4e83adf90d2d796108e857a1fc0', tabindex: "0", "aria-hidden": "true" }), h("div", { key: '8e74209321fc5e8712e3e293c91a6fa036ea45ab', class: "action-sheet-wrapper ion-overlay-wrapper", ref: (el) => (this.wrapperEl = el) }, h("div", { key: 'c811860be2eed0b6c73fc2cc5a59cb94db2e8912', class: "action-sheet-container" }, h("div", { key: 'd36ee14b70d73eb5cd69e0c57bc5cc31daf86ab3', class: "action-sheet-group", ref: (el) => (this.groupEl = el), role: hasRadioButtons ? 'radiogroup' : undefined }, header !== undefined && (h("div", { key: '922695de191edc86451eed2552acc4f54993ea52', id: headerID, class: {
|
|
393
393
|
'action-sheet-title': true,
|
|
394
394
|
'action-sheet-has-sub-title': this.subHeader !== undefined,
|
|
395
|
-
} }, header, this.subHeader && h("div", { key: '
|
|
395
|
+
} }, header, this.subHeader && h("div", { key: 'a119d4a93668e829f7106f11cbbdd437310f3e80', class: "action-sheet-sub-title" }, this.subHeader))), this.renderActionSheetButtons(buttons)), cancelButton && (h("div", { key: '1d27f36d09bedd2e4c61fea9dd9d05f8f9271aef', class: "action-sheet-group action-sheet-group-cancel" }, h("button", Object.assign({ key: 'ed13658176c54cc45808a54a0331c297430d9bc6' }, cancelButton.htmlAttributes, { type: "button", class: buttonClass(cancelButton), onClick: () => this.buttonClick(cancelButton) }), h("span", { key: '5208e3e3535b775a443cef6fb1decd790db69b0c', class: "action-sheet-button-inner" }, cancelButton.icon && (h("ion-icon", { key: 'b7ba489e1ee50524a5b4af670eef787844a16286', icon: cancelButton.icon, "aria-hidden": "true", lazy: false, class: "action-sheet-icon" })), cancelButton.text), mode === 'md' && h("ion-ripple-effect", { key: 'b3f88898114855853259f0001def26b4bd6e4a98' })))))), h("div", { key: '0dfd0fcdc633bf565990eaa06679608e749cf8f9', tabindex: "0", "aria-hidden": "true" })));
|
|
396
396
|
}
|
|
397
397
|
static get is() { return "ion-action-sheet"; }
|
|
398
398
|
static get encapsulation() { return "scoped"; }
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
-webkit-padding-end: var(--padding-end);
|
|
117
117
|
padding-inline-end: var(--padding-end);
|
|
118
118
|
padding-top: calc(var(--padding-top) + var(--offset-top));
|
|
119
|
-
padding-bottom: calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom));
|
|
119
|
+
padding-bottom: calc(var(--padding-bottom) + var(--keyboard-offset) + var(--offset-bottom) + var(--ion-content-safe-area-padding-bottom, 0px));
|
|
120
120
|
position: absolute;
|
|
121
121
|
color: var(--color);
|
|
122
122
|
box-sizing: border-box;
|
|
@@ -55,6 +55,12 @@ import { checkForPresentationFormatMismatch, warnIfTimeZoneProvided } from "./ut
|
|
|
55
55
|
export class Datetime {
|
|
56
56
|
constructor() {
|
|
57
57
|
this.inputId = `ion-dt-${datetimeIds++}`;
|
|
58
|
+
/**
|
|
59
|
+
* Set true only by `visibleCallback`. Lets `hiddenCallback` ignore the
|
|
60
|
+
* synthetic "not intersecting" entry IntersectionObserver fires on
|
|
61
|
+
* `observe()` when the host mounts offscreen.
|
|
62
|
+
*/
|
|
63
|
+
this.hasBeenIntersecting = false;
|
|
58
64
|
this.prevPresentation = null;
|
|
59
65
|
this.showMonthAndYear = false;
|
|
60
66
|
this.activeParts = [];
|
|
@@ -617,7 +623,21 @@ export class Datetime {
|
|
|
617
623
|
if (rect.width === 0 || rect.height === 0) {
|
|
618
624
|
return;
|
|
619
625
|
}
|
|
626
|
+
this.markReady();
|
|
627
|
+
};
|
|
628
|
+
this.markReady = () => {
|
|
629
|
+
if (this.el.classList.contains('datetime-ready')) {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
620
632
|
this.initializeListeners();
|
|
633
|
+
/**
|
|
634
|
+
* TODO FW-2793: Datetime needs a frame to ensure that it
|
|
635
|
+
* can properly scroll contents into view. As a result
|
|
636
|
+
* we hide the scrollable content until after that frame
|
|
637
|
+
* so users do not see the content quickly shifting. The downside
|
|
638
|
+
* is that the content will pop into view a frame after. Maybe there
|
|
639
|
+
* is a better way to handle this?
|
|
640
|
+
*/
|
|
621
641
|
writeTask(() => {
|
|
622
642
|
this.el.classList.add('datetime-ready');
|
|
623
643
|
});
|
|
@@ -910,6 +930,7 @@ export class Datetime {
|
|
|
910
930
|
this.clearFocusVisible = undefined;
|
|
911
931
|
}
|
|
912
932
|
this.loadTimeoutCleanup();
|
|
933
|
+
this.hasBeenIntersecting = false;
|
|
913
934
|
}
|
|
914
935
|
initializeListeners() {
|
|
915
936
|
this.initializeCalendarListener();
|
|
@@ -929,18 +950,8 @@ export class Datetime {
|
|
|
929
950
|
if (!ev.isIntersecting) {
|
|
930
951
|
return;
|
|
931
952
|
}
|
|
932
|
-
this.
|
|
933
|
-
|
|
934
|
-
* TODO FW-2793: Datetime needs a frame to ensure that it
|
|
935
|
-
* can properly scroll contents into view. As a result
|
|
936
|
-
* we hide the scrollable content until after that frame
|
|
937
|
-
* so users do not see the content quickly shifting. The downside
|
|
938
|
-
* is that the content will pop into view a frame after. Maybe there
|
|
939
|
-
* is a better way to handle this?
|
|
940
|
-
*/
|
|
941
|
-
writeTask(() => {
|
|
942
|
-
this.el.classList.add('datetime-ready');
|
|
943
|
-
});
|
|
953
|
+
this.hasBeenIntersecting = true;
|
|
954
|
+
this.markReady();
|
|
944
955
|
};
|
|
945
956
|
const visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01, root: el });
|
|
946
957
|
/**
|
|
@@ -976,6 +987,11 @@ export class Datetime {
|
|
|
976
987
|
if (ev.isIntersecting) {
|
|
977
988
|
return;
|
|
978
989
|
}
|
|
990
|
+
// Ignore the initial "not intersecting" entry IntersectionObserver fires on observe().
|
|
991
|
+
if (!this.hasBeenIntersecting) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
this.hasBeenIntersecting = false;
|
|
979
995
|
this.destroyInteractionListeners();
|
|
980
996
|
/**
|
|
981
997
|
* When datetime is hidden, we need to make sure that
|
|
@@ -1719,7 +1735,7 @@ export class Datetime {
|
|
|
1719
1735
|
const hasDatePresentation = presentation === 'date' || presentation === 'date-time' || presentation === 'time-date';
|
|
1720
1736
|
const hasWheelVariant = hasDatePresentation && preferWheel;
|
|
1721
1737
|
renderHiddenInput(true, el, name, formatValue(value), disabled);
|
|
1722
|
-
return (h(Host, { key: '
|
|
1738
|
+
return (h(Host, { key: '323c8c2327088f00934b8c93c3306538cb9b5677', "aria-disabled": disabled ? 'true' : null, onFocus: this.onFocus, onBlur: this.onBlur, class: Object.assign({}, createColorClasses(color, {
|
|
1723
1739
|
[mode]: true,
|
|
1724
1740
|
['datetime-readonly']: readonly,
|
|
1725
1741
|
['datetime-disabled']: disabled,
|
|
@@ -1729,7 +1745,7 @@ export class Datetime {
|
|
|
1729
1745
|
[`datetime-size-${size}`]: true,
|
|
1730
1746
|
[`datetime-prefer-wheel`]: hasWheelVariant,
|
|
1731
1747
|
[`datetime-grid`]: isGridStyle,
|
|
1732
|
-
})) }, h("div", { key: '
|
|
1748
|
+
})) }, h("div", { key: '1e0855c8909bc3f1e48a21ad68159fa782060691', class: "intersection-tracker", ref: (el) => (this.intersectionTrackerRef = el) }), this.renderDatetime(mode)));
|
|
1733
1749
|
}
|
|
1734
1750
|
static get is() { return "ion-datetime"; }
|
|
1735
1751
|
static get encapsulation() { return "shadow"; }
|