From 90a7e70a1c827835c18fd5f39d53ab17d286b4a7 Mon Sep 17 00:00:00 2001 From: Shawn Taylor Date: Tue, 2 Apr 2024 11:52:02 -0400 Subject: [PATCH] feat(content): add fixedSlotPlacement prop (#29233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue number: Internal --------- ## What is the current behavior? Content in the `fixed` slot is always placed after the main content in the DOM. ## What is the new behavior? - A new `fixedSlotPlacement` prop on Content allows developers to place fixed content either before or after the main content in the DOM ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Dev build: `8.0.0-dev.11712072527.1dd97c66` ⚠️This feature will not be part of the v8.0 release. As a result, do not merge this into `feature-8.0`. However, I am putting this PR up based off `feature-8.0` so it can get reviewed by the team. --------- Co-authored-by: Liam DeBeasi --- core/api.txt | 1 + core/src/components.d.ts | 8 +++++ core/src/components/content/content.tsx | 16 ++++++++-- .../components/content/test/content.spec.ts | 31 +++++++++++++++++++ packages/angular/src/directives/proxies.ts | 4 +-- .../standalone/src/directives/proxies.ts | 4 +-- packages/vue/src/proxies.ts | 1 + 7 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 core/src/components/content/test/content.spec.ts diff --git a/core/api.txt b/core/api.txt index e91c256322..9e98c453fa 100644 --- a/core/api.txt +++ b/core/api.txt @@ -361,6 +361,7 @@ ion-col,css-prop,--ion-grid-columns ion-content,shadow ion-content,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true +ion-content,prop,fixedSlotPlacement,"after" | "before",'after',false,false ion-content,prop,forceOverscroll,boolean | undefined,undefined,false,false ion-content,prop,fullscreen,boolean,false,false,false ion-content,prop,scrollEvents,boolean,false,false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 1f736e6a0e..73af9bcf99 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -762,6 +762,10 @@ export namespace Components { * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). */ "color"?: Color; + /** + * Controls where the fixed content is placed relative to the main content in the DOM. This can be used to control the order in which fixed elements receive keyboard focus. For example, if a FAB in the fixed slot should receive keyboard focus before the main page content, set this property to `'before'`. + */ + "fixedSlotPlacement": 'after' | 'before'; /** * If `true` and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. If the content exceeds the bounds of ionContent, nothing will change. Note, this does not disable the system bounce on iOS. That is an OS level setting. */ @@ -5482,6 +5486,10 @@ declare namespace LocalJSX { * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). */ "color"?: Color; + /** + * Controls where the fixed content is placed relative to the main content in the DOM. This can be used to control the order in which fixed elements receive keyboard focus. For example, if a FAB in the fixed slot should receive keyboard focus before the main page content, set this property to `'before'`. + */ + "fixedSlotPlacement"?: 'after' | 'before'; /** * If `true` and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. If the content exceeds the bounds of ionContent, nothing will change. Note, this does not disable the system bounce on iOS. That is an OS level setting. */ diff --git a/core/src/components/content/content.tsx b/core/src/components/content/content.tsx index d2a4193b6e..9c9e134114 100644 --- a/core/src/components/content/content.tsx +++ b/core/src/components/content/content.tsx @@ -75,6 +75,15 @@ export class Content implements ComponentInterface { */ @Prop() fullscreen = false; + /** + * Controls where the fixed content is placed relative to the main content + * in the DOM. This can be used to control the order in which fixed elements + * receive keyboard focus. + * For example, if a FAB in the fixed slot should receive keyboard focus before + * the main page content, set this property to `'before'`. + */ + @Prop() fixedSlotPlacement: 'after' | 'before' = 'after'; + /** * If `true` and the content does not cause an overflow scroll, the scroll interaction will cause a bounce. * If the content exceeds the bounds of ionContent, nothing will change. @@ -423,7 +432,7 @@ export class Content implements ComponentInterface { } render() { - const { isMainContent, scrollX, scrollY, el } = this; + const { fixedSlotPlacement, isMainContent, scrollX, scrollY, el } = this; const rtl = isRTL(el) ? 'rtl' : 'ltr'; const mode = getIonMode(this); const forceOverscroll = this.shouldForceOverscroll(); @@ -446,6 +455,9 @@ export class Content implements ComponentInterface { }} >
(this.backgroundContentEl = el)} id="background-content" part="background">
+ + {fixedSlotPlacement === 'before' ? : null} +
) : null} - + {fixedSlotPlacement === 'after' ? : null} ); } diff --git a/core/src/components/content/test/content.spec.ts b/core/src/components/content/test/content.spec.ts new file mode 100644 index 0000000000..4dcf7f3e0a --- /dev/null +++ b/core/src/components/content/test/content.spec.ts @@ -0,0 +1,31 @@ +import { newSpecPage } from '@stencil/core/testing'; + +import { Content } from '../content'; + +describe('content: fixed slot placement', () => { + it('should should fixed slot after content', async () => { + const page = await newSpecPage({ + components: [Content], + html: '', + }); + + const content = page.body.querySelector('ion-content')!; + const fixedSlot = content.shadowRoot!.querySelector('slot[name="fixed"]')!; + const scrollEl = content.shadowRoot!.querySelector('[part="scroll"]')!; + + expect(fixedSlot.nextElementSibling).not.toBe(scrollEl); + }); + + it('should should fixed slot before content', async () => { + const page = await newSpecPage({ + components: [Content], + html: ``, + }); + + const content = page.body.querySelector('ion-content')!; + const fixedSlot = content.shadowRoot!.querySelector('slot[name="fixed"]')!; + const scrollEl = content.shadowRoot!.querySelector('[part="scroll"]')!; + + expect(fixedSlot.nextElementSibling).toBe(scrollEl); + }); +}); diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 1fb566daac..1ec80848a6 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -592,7 +592,7 @@ export declare interface IonCol extends Components.IonCol {} @ProxyCmp({ - inputs: ['color', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], + inputs: ['color', 'fixedSlotPlacement', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], methods: ['getScrollElement', 'scrollToTop', 'scrollToBottom', 'scrollByPoint', 'scrollToPoint'] }) @Component({ @@ -600,7 +600,7 @@ export declare interface IonCol extends Components.IonCol {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], + inputs: ['color', 'fixedSlotPlacement', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], }) export class IonContent { protected el: HTMLElement; diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index c319aa5527..ce9e8ed625 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -659,7 +659,7 @@ export declare interface IonCol extends Components.IonCol {} @ProxyCmp({ defineCustomElementFn: defineIonContent, - inputs: ['color', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], + inputs: ['color', 'fixedSlotPlacement', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], methods: ['getScrollElement', 'scrollToTop', 'scrollToBottom', 'scrollByPoint', 'scrollToPoint'] }) @Component({ @@ -667,7 +667,7 @@ export declare interface IonCol extends Components.IonCol {} changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['color', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], + inputs: ['color', 'fixedSlotPlacement', 'forceOverscroll', 'fullscreen', 'scrollEvents', 'scrollX', 'scrollY'], standalone: true }) export class IonContent { diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 75e032378f..936b1e1278 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -262,6 +262,7 @@ export const IonCol = /*@__PURE__*/ defineContainer('ion-col', defin export const IonContent = /*@__PURE__*/ defineContainer('ion-content', defineIonContent, [ 'color', 'fullscreen', + 'fixedSlotPlacement', 'forceOverscroll', 'scrollX', 'scrollY',