Skip to content

fix(runtime): hide slotted content with no destination in scoped components#5135

Merged
tanner-reits merged 11 commits intomainfrom
treits/fix/hide-slotted-content-with-no-destination
Dec 6, 2023
Merged

fix(runtime): hide slotted content with no destination in scoped components#5135
tanner-reits merged 11 commits intomainfrom
treits/fix/hide-slotted-content-with-no-destination

Conversation

@tanner-reits
Copy link
Copy Markdown
Contributor

@tanner-reits tanner-reits commented Dec 6, 2023

What is the current behavior?

Two current behaviors this solves:

  1. If a slot is dynamically rendered in a component using scoped or no encapsulation and is not rendered initially, the content that would go into this slot is marked as hidden. But, if the slot was rendered on a subsequent render, we would not remove the hidden attribute, so the content would still be invisible in the DOM.
  2. Content projected through to a component would not be hidden if it did not have a destination slot like how a component with a Shadow Root would hide any content in the light DOM not mapped to a slot.

Fixes: #2877

What is the new behavior?

Solutions in order of the issues listed above:

  1. We now track an "initially hidden" state for an element. This value is used to "reset" the hidden attribute when content is successfully relocated to a "slot" location. This accounts for if a user manually set the hidden attribute on an element so it would remain hidden after relocation. There may be issues with this value getting programmatically changed throughout a component's lifetime and reset to an incorrect value. We can address this in the future as needed.
  2. Leveraging the initial hidden state from Swiper #1, we now hide any content that is projected through to a scoped component that does not end up in a slot. If an appropriate "slot" is dynamically rendered later, the content will then be relocated and displayed accordingly.

Does this introduce a breaking change?

  • Yes
  • No

Testing

Manual testing

  1. Spin up a new Stencil component library
  2. Enable experimentalSlotFixes in the extras object of the Stencil config
  3. Update the project's my-component.tsx and index.html files as follows:
// my-component.tsx

@Component({
  tag: 'my-component',
  shadow: false,
  scoped: true,
})
export class MyComponent {
  @Prop() enabled = false;

  render() {
    return (
      <Host>
        <p>Test</p>
        {this.enabled && (
          <div data-txt="slot wrapper">
            <slot>
              <span>fallback default slot</span>
            </slot>
          </div>
        )}
      </Host>
    );
  }
}
// index.html

<body>
    <my-component>
      <div>
        <span>outside default slot</span>
      </div>
    </my-component>

    <button type="button">Click</button>
</body>

<script>
    document.querySelector('button').addEventListener('click', () => {
      document.querySelector('my-component').setAttribute('enabled', true);
    });
</script>
  1. Run the project and confirm that the div projected through from the index.html file does appear in the DOM despite the slot not being rendered.
  2. Install a build of this branch
  3. Re-run and observe the div is not visible until after clicking the button (which dynamically renders the slot).

Automated testing
Existing unit & e2e tests continue to pass. Added e2e tests for the reproduction case in the associated issue.

Other information

Only available with the experimentalSlotFixes extra flag enabled in a project's Stencil config.

Tanner Reits added 10 commits November 30, 2023 16:29
If a slot is located in an element and that element's tag is dynamically changed (e.g. from `p` to `span`), we need to re-relocate the slot on re-render

STENCIL-672: slot element loses its parent reference and disappears when its parent is rendered conditionally

Fixes: #4284, #3913
It is possible for slotted content to still be invisible in the DOM if the slot was not rendered on the first render. This commit resets the `hidden` attribute of a node on successful relocation.

STENCIL-1053
Hides any content that is projected through to a Stencil component that does not have a destination slot. Only for `scoped` components.

Fixes #2877

STENCIL-938
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Dec 6, 2023

--strictNullChecks error report

Typechecking with --strictNullChecks resulted in 1323 errors on this branch.

That's the same number of errors on main, so at least we're not creating new ones!

reports and statistics

Our most error-prone files
Path Error Count
src/dev-server/index.ts 37
src/mock-doc/serialize-node.ts 36
src/dev-server/server-process.ts 32
src/compiler/style/test/optimize-css.spec.ts 23
src/compiler/output-targets/dist-lazy/generate-lazy-module.ts 22
src/compiler/prerender/prerender-main.ts 22
src/testing/puppeteer/puppeteer-element.ts 22
src/runtime/client-hydrate.ts 20
src/screenshot/connector-base.ts 19
src/runtime/vdom/vdom-render.ts 18
src/compiler/config/test/validate-paths.spec.ts 16
src/dev-server/request-handler.ts 15
src/compiler/prerender/prerender-optimize.ts 14
src/compiler/sys/stencil-sys.ts 14
src/compiler/transpile/transpile-module.ts 14
src/runtime/vdom/vdom-annotations.ts 14
src/sys/node/node-sys.ts 14
src/compiler/prerender/prerender-queue.ts 13
src/compiler/sys/in-memory-fs.ts 13
src/runtime/connected-callback.ts 13
Our most common errors
Typescript Error Code Count
TS2345 399
TS2322 374
TS18048 286
TS18047 101
TS2722 37
TS2532 30
TS2531 23
TS2454 14
TS2352 13
TS2790 10
TS2769 8
TS2538 8
TS2416 6
TS2344 5
TS2493 3
TS2488 2
TS18046 2
TS2684 1
TS2430 1

Unused exports report

There are 14 unused exports on this PR. That's the same number of errors on main, so at least we're not creating new ones!

Unused exports
File Line Identifier
src/runtime/bootstrap-lazy.ts 21 setNonce
src/screenshot/screenshot-fs.ts 18 readScreenshotData
src/testing/testing-utils.ts 198 withSilentWarn
src/utils/index.ts 145 CUSTOM
src/utils/index.ts 269 normalize
src/utils/index.ts 7 escapeRegExpSpecialCharacters
src/compiler/app-core/app-data.ts 25 BUILD
src/compiler/app-core/app-data.ts 115 Env
src/compiler/app-core/app-data.ts 117 NAMESPACE
src/compiler/fs-watch/fs-watch-rebuild.ts 123 updateCacheFromRebuild
src/compiler/types/validate-primary-package-output-target.ts 61 satisfies
src/compiler/types/validate-primary-package-output-target.ts 61 Record
src/testing/puppeteer/puppeteer-declarations.ts 485 WaitForEventOptions
src/compiler/sys/fetch/write-fetch-success.ts 7 writeFetchSuccessSync

Copy link
Copy Markdown
Contributor Author

@tanner-reits tanner-reits left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Called out one thing

// Store the initial value of `hidden` so we can reset it later when
// moving nodes around.
if (isInitialLoad) {
nodeToRelocate['s-ih'] = nodeToRelocate.hidden ?? false;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be possible to run into a situation where an element is programmatically hidden and we end up storing the incorrect state since this only captures the state on initial render. I figured that can be something we deal with as needed since the functionality is currently broken anyway so this is a step in the right direction

@tanner-reits tanner-reits marked this pull request as ready for review December 6, 2023 16:16
@tanner-reits tanner-reits requested a review from a team as a code owner December 6, 2023 16:16
Copy link
Copy Markdown
Member

@christian-bromann christian-bromann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment, but overall LGTM 👍

@rwaskiewicz
Copy link
Copy Markdown
Member

@tanner-reits I have a quick clarifying questions regarding the manual testing steps:

  1. The component has the tag my-component, but the query selector in index.html references a "pui-example" - I'm guessing we can replace "pui-example" for "my-component" here?
  2. Does this fix the issue for all encapsulation models? In the manual testing example, we're using shadow: true, which differs from the karma tests in this PR

@tanner-reits
Copy link
Copy Markdown
Contributor Author

@rwaskiewicz Sorry bout that.

  1. The query in the steps should be 'my-component', that was a copy-pasta error when renaming things from the reproduction repo I was using locally.
  2. This change only modifies scoped. Which looks like that's the encapsulation mode I have in the testing steps so should be good there

@rwaskiewicz
Copy link
Copy Markdown
Member

@tanner-reits my bad on question 2 - IDK what I was looking at 😓

Co-authored-by: Christian Bromann <git@bromann.dev>
@tanner-reits tanner-reits added this pull request to the merge queue Dec 6, 2023
Merged via the queue into main with commit 77bce27 Dec 6, 2023
@tanner-reits tanner-reits deleted the treits/fix/hide-slotted-content-with-no-destination branch December 6, 2023 21:49
@christian-bromann
Copy link
Copy Markdown
Member

Released in v4.8.2

tanner-reits pushed a commit that referenced this pull request Dec 20, 2023
This commit adds a new extras flag, `experimentalScopedSlotChanges`, that is used to change logic in a few places to align Stencil's `scoped` encapsulation to work more like the native Shadow DOM when using slots.

Changes in #5146 and #5135 are changed from being gated by the `experimentalSlotFixes` flag to this new flag so they can be toggled independently.
tanner-reits pushed a commit that referenced this pull request Jan 2, 2024
This commit adds a new extras flag, `experimentalScopedSlotChanges`, that is used to change logic in a few places to align Stencil's `scoped` encapsulation to work more like the native Shadow DOM when using slots.

Changes in #5146 and #5135 are changed from being gated by the `experimentalSlotFixes` flag to this new flag so they can be toggled independently.
tanner-reits pushed a commit that referenced this pull request Jan 11, 2024
This commit adds a new extras flag, `experimentalScopedSlotChanges`, that is used to change logic in a few places to align Stencil's `scoped` encapsulation to work more like the native Shadow DOM when using slots.

Changes in #5146 and #5135 are changed from being gated by the `experimentalSlotFixes` flag to this new flag so they can be toggled independently.
github-merge-queue bot pushed a commit that referenced this pull request Jan 12, 2024
This commit adds a new extras flag, `experimentalScopedSlotChanges`, that is used to change logic in a few places to align Stencil's `scoped` encapsulation to work more like the native Shadow DOM when using slots.

Changes in #5146 and #5135 are changed from being gated by the `experimentalSlotFixes` flag to this new flag so they can be toggled independently.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slot CRUD does not work properly when shadow is false

3 participants