Overview

Starting from an empty canvas (which will be the case after #3472299: Update default config to make a fresh install result in an XB UI with an empty canvas) should be an inviting experience that encourages a user to start adding components. Currently, it's very far from that. We are on our way to that!

Adding a component with slots should make it clear, through the UI design, that more child components can be added.

Stronger illustration of the current problem, by @jessebaker in #18:

Proposed resolution

Currently empty slots in SDC templates can have "example" content. We should ignore this and instead render an placeholder div for consistency. Because the placeholder is currently inserted into the preview iFrame after it has loaded it causes a noticeable layout shift every time the preview re-renders so this needs to be refactored and inserted/rendered on the back end.

See comment #12 for more details on what still needs to happen.


Everything below here has already been addressed and is just here for posterity

Design: What to show when a region is empty in the preview
Empty region design

Design: What to show when a slot is empty in the preview
Empty slot design

Design: What to show in the preview when a component is in the layout tree but doesn't render any markup
If a component takes up 0px of space in the preview, we should not render the component overlay in the preview. Users will have to use the layers panel to move/sort around these "invisible" components.

FE:The designs above are rendered into the overlay, but that means we must introduce a corresponding amount of space into the preview iFrame document.

As of #3512455: Implement new drag-and-drop solution that removes the need to drop items into the preview iFrame an initial implementation has been introduced purely on the front end for empty slots in components and for the empty content region when you have a 'blank' page.

For each empty component slot we insert a div with a min-height of 40px in useRenderPreviewEmptySlotPlaceholders.ts and then in the SlotOverlay.tsx we render the designed dropZone. useRenderPreviewEmptyRegionPlaceholder.ts does the same for the Content region.

Out of scope

There are plans afoot to refactor the layers panel (to use DnDKit) so, for this ticket, no changes need to be made there (e.g. for showing empty slots in the layers panel etc).

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

shyam_bhatt created an issue. See original summary.

wim leers’s picture

Title: After deleting all the components from the canvas, "Add section" button should be visible on the canvas » Empty layout/canvas has poor UX — needs "Add section" button, probably more
Category: Bug report » Feature request
Priority: Normal » Major
Issue summary: View changes
Issue tags: +Usability
Related issues: +#3473772: Improve UI for empty states - Empty layout, +#3472299: Update default config to make a fresh install result in an XB UI with an empty canvas

@jessebaker reported the same problem ~2 hours later: #3473772: Improve UI for empty states - Empty layout. Closed that as a duplicate.

Transfered what he wrote and merged it with @shyam_bhatt's write-up 👍

wim leers’s picture

jessebaker’s picture

Title: Empty layout/canvas has poor UX — needs "Add section" button, probably more » [Needs design] Empty regions/Slots have poor UI and thus UX
Version: » 0.x-dev
Issue summary: View changes
Issue tags: +Needs design
jessebaker’s picture

Title: [Needs design] Empty regions/Slots have poor UI and thus UX » [Needs design] Empty regions/Slots have no UI and thus poor UX
jessebaker’s picture

Issue summary: View changes
jessebaker’s picture

Assigned: Unassigned » callumharrod
jessebaker’s picture

Title: [Needs design] Empty regions/Slots have no UI and thus poor UX » Show empty Regions/Slots in the preview
Issue summary: View changes
Issue tags: -Needs design +stable blocker
StatusFileSize
new129.62 KB

I believe at this point all that is left is the backend work to render the empty space inside the iFrame - basically taking the work done in useRenderPreviewEmpty[Slot|Region]Placeholders and doing it on the server side instead.

jessebaker’s picture

Assigned: callumharrod » Unassigned
Issue summary: View changes
wim leers’s picture

Assigned: Unassigned » jessebaker
Status: Active » Postponed (maintainer needs more info)

BE:Because the placeholder is inserted into the preview iFrame after it has loaded it causes a noticeable layout shift every time the preview re-renders so this needs to be refactored and inserted/rendered on the back end.

What is the concrete before vs after that you're looking for? What markup needs to change? What API response needs to change?

jessebaker’s picture

Title: Show empty Regions/Slots in the preview » Render a placeholder DIV inside empty Regions/Slots in the preview
Issue summary: View changes
Status: Postponed (maintainer needs more info) » Active

When a request to the back end for the preview HTML is made currently the HTML returned contains HTML comments to denote the location of regions and slots. When a slot or the main content "region" is empty, the design calls for an empty space into which users can drop their first component to start making the page. The space is styled to have either a green or purple border. This border is rendered in the overlay. Inside the iFrame we simply need some blank space.

The blank space is created by inserting a div with a height and width. In head, we find the HTML comments and insert the div on the client side. This leads to a "jump" after the page is rendered as the new divs are inserted. If the placeholder div were inserted server side the jump would not happen.

So, when a request returns the preview HTML (e.g. a POST or PATCH to /xb/api/v0/layout/xb_page/1 I'd like the HTML returned to already have placeholder divs in between the HTML comments.

For empty regions:

In the markup an empty region currently looks like

<!-- xb-region-start-content --><!-- xb-region-end-content -->

I would like for it to look like this instead:

<!-- xb-region-start-content --><div class="xb--region-empty-placeholder"></div><!-- xb-region-end-content -->

As soon as there is anything rendered in the region, the xb--region-empty-placeholder should not be rendered.

For empty slots

In the markup an empty slot currently looks like

<!-- xb-slot-start-d42bf579-4554-48e2-9438-b859e00d29d8/content -->
            <!-- xb-slot-end-d42bf579-4554-48e2-9438-b859e00d29d8/content -->

I would like for it to look like this instead:

<!-- xb-slot-start-d42bf579-4554-48e2-9438-b859e00d29d8/content --><div class="xb--slot-empty-placeholder"></div><!-- xb-slot-end-d42bf579-4554-48e2-9438-b859e00d29d8/content -->

As soon as there is anything rendered in the slot, the xb--slot-empty-placeholder should not be rendered.

In addition - the server currently renders the first of a slot's examples from the SDC's yml into the slot. This shouldn't be happening. Currently the front end has to remove this markup.

slots:
  column_one:
    title: Column One
    description: The contents of the first column.
    examples:
      - <p>This is column 1 content</p> <--- this should not be rendered in XB

On the front end

Once the backend is inserting those DIVs the front end can then remove ui/src/hooks/useRenderPreviewEmptySlotPlaceholders.ts and ui/src/hooks/useRenderPreviewEmptyRegionPlaceholder.ts

jessebaker’s picture

Issue summary: View changes
jessebaker’s picture

Assigned: jessebaker » wim leers
wim leers’s picture

Title: Render a placeholder DIV inside empty Regions/Slots in the preview » [PP-1] Render a placeholder DIV inside empty Regions/Slots in the preview
Assigned: wim leers » jessebaker
Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs tests
Parent issue: » #3499352: SDCs should only have get HTML comments injected when `renderComponent(isPreview: TRUE)`
Related issues: +#953034: [meta] Themes improperly check renderable arrays when determining visibility, +#3499352: SDCs should only have get HTML comments injected when `renderComponent(isPreview: TRUE)`, +#3518606: When a global region is empty, don't render HTML comment annotations into it

Part one: needs more clarity 🙏

For empty regions: …

👎 This will inevitably trigger #953034: [meta] Themes improperly check renderable arrays when determining visibility.

🏓 When does XB render an empty region? Only when it's just had its last component instance removed? Because since #3518606: When a global region is empty, don't render HTML comment annotations into it we specifically don't render these!

Part two: clear! 👍

In addition - the server currently renders the first of a slot's examples from the SDC's yml into the slot. This shouldn't be happening. Currently the front end has to remove this markup.

Hah! Should be an easy fix, and easily added test coverage for: in \Drupal\Tests\experience_builder\Kernel\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponentTest::testGetClientSideInfo() tests with isPreview: FALSE. 👍

Part three: needs more clarity 🙏

For empty slots […]

  1. We can do this for SDCs, but then this is effectively blocked on #3499352: SDCs should only have get HTML comments injected when `renderComponent(isPreview: TRUE)` — otherwise we'll inevitably render <!-- xb-slot-start-d42bf579-4554-48e2-9438-b859e00d29d8/content --><div class="xb--slot-empty-placeholder"></div><!-- xb-slot-end-d42bf579-4554-48e2-9438-b859e00d29d8/content --> on the live site, too! 😨
  2. 🏓 We can't do that for code components … so how do you propose to tackle this for those? 😅 If they don't need this: why not?
  3. And that last point is generalizable: all future \Drupal\experience_builder\ComponentSource\ComponentSourceWithSlotsInterface implementations will need to consistently perform the same.
jessebaker’s picture

When does XB render an empty region? Only when it's just had its last component instance removed?

This is a good point - I am specifically talking about the Content "region" when a new page is loaded. Other global regions don't need this.

It currently looks like this:

<!-- xb-region-start-content -->
<!-- xb-region-end-content -->

and I'd like to see this instead:

<!-- xb-region-start-content -->
<div class="xb--region-empty-placeholder"></div>
<!-- xb-region-end-content -->
For empty slots [...]
  1. Ah, yes. I agree this would be blocked on that. I didn't realise the HTML comments were not already limited to only the XB preview
  2. Code components: We are already inserting the HTML comments around slots for code components. I think determining if they are empty is tricky. Perhaps inserting the placeholder can be done at a similar time in the process to the suggested solution here #3520581: [later phase] Unwrap `<astro-island>`s and `<astro-slot>`s
  3. It seems that we need bespoke solutions for both SDC and JS components. I don't know if there is a way to generalise this implementation so that the process can happen no matter the ComponentSource.
wim leers’s picture

Status: Postponed (maintainer needs more info) » Postponed

Ah, if this is a concern for the content region only, then this seems solvable.

  1. Totally reasonable to assume that #3499352: SDCs should only have get HTML comments injected when `renderComponent(isPreview: TRUE)` would not have been necessary, I was surprised too 😅
  2. Just checking: you do confirm that there's no server-side changes needed/expected, right?
  3. I don't think it's generalizable, unfortunately.

Do you think this should be a beta blocker rather than a stable blocker?

jessebaker’s picture

Issue summary: View changes
Issue tags: -stable blocker
StatusFileSize
new260.04 KB

Do you think this should be a beta blocker rather than a stable blocker?

Obviously the ramifications are not so serious as data integrity or even severe in terms of user impact - however, we have put a lot of time and energy into creating a 'native feeling' to our editing experience and reducing flicker and FOUC has been a large part of that. The impact is on first impressions and the confidence brought from a feeling of robustness.

Here is a gif showing the current state. For clarity it's only an issue when there is an empty slot on the page. This is an SDC. The effect is the same with a code component. Each time a prop is updated and the preview updates the content below the slot appears to jump up and then pop back down again as the page renders without the space and then the space is inserted.

[edit] The embedded gif is playing at a low frames per second and doesn't seem to show the flicker unless you open it in a new tab.

For now, I'm going to remove stable and beta blocker labels and treat this as a "nice to have" because in light of conversations around other priorities this not critical and can be fixed after beta.

I also think that perhaps this CAN be addressed client side if we tackle it in a different way given that it's non-trivial to handle on the server. It will need some thought but there are options to explore.

wim leers’s picture

Title: [PP-1] Render a placeholder DIV inside empty Regions/Slots in the preview » Render a placeholder DIV inside empty Regions/Slots in the preview
Issue summary: View changes
Status: Postponed » Needs review

#3499352: SDCs should only have get HTML comments injected when `renderComponent(isPreview: TRUE)` went in overnight! Time to re-assess.

Thanks for #18, that's super helpful additional context 🙏

Part one: empty "content" region

This part is trivial to implement, so created an MR for it. If you confirm this does what you want/need, we can merge this right away :)

It currently looks like this:

<!-- xb-region-start-content -->
<!-- xb-region-end-content -->

and I'd like to see this instead:

<!-- xb-region-start-content -->
<div class="xb--region-empty-placeholder"></div>
<!-- xb-region-end-content -->

Part two: empty slots

This actually should be fairly doable too:

  1. \Drupal\experience_builder\Plugin\DataType\ComponentTreeHydrated::renderify() knows when it's rendering a preview ($isPreview === TRUE), and so it can do something different (like inserting that div) when rendering a preview
  2. which gives us two choices:
    1. either comparing the value of a slot with its example value/default, and if that exists, replace it
    2. or updating GeneratedFieldExplicitInputUxComponentSourceBase::hydrateComponent() to also receive a $isPreview parameter, and letting it omit slot examples — it currently looks like this:
        public function hydrateComponent(array $explicit_input): array {
          $hydrated[self::EXPLICIT_INPUT_NAME] = $explicit_input['resolved'];
      
          if ($slots = $this->getSlotDefinitions()) {
            // Use the first example defined in SDC metadata, if it exists. Otherwise,
            // fall back to `"#plain_text => ''`, which is accepted by SDC's rendering
            // logic but still results in an empty slot.
            // @see https://www.drupal.org/node/3391702
            // @see \Drupal\Core\Render\Element\ComponentElement::generateComponentTemplate()
            $hydrated['slots'] = array_map(fn($slot) => $slot['examples'][0] ?? '', $slots);
          }
      
          return $hydrated;
        }
      

    I think the first choice is preferable because it keeps the component sources simpler, and hence more reliable, which is important for long-term DX/ecosystem of XB. I'll get a sample MR for that up too.

    That still won't solve it for code components (as described in #15.three.2) though (the server side can't change that), but this should leave it in a much better spot at least :)

jessebaker’s picture

Assigned: jessebaker » balintbrews

I don't really understand how but, @wim leers, your change in !1055 to add the slot placeholder on the server side is working for both SDC and Code Components. I've tested and double checked and the div rendered in ComponentTreeHydrated.php is showing for both types of component.

Those two MR's combined have 100% resolved this issue as far as I am concerned. I've removed the now redundant JS (and 1 other function I found that was already redundant from function-utils.ts (!1055 diff).

wim leers’s picture

Issue tags: -Needs tests

🥳

Yes, I realized actually while writing that code that due to slot population even for code components happening server-side (see \Drupal\experience_builder\Plugin\ExperienceBuilder\ComponentSource\JsComponent::setSlots()), my statements about that have been wrong all along! 🙈

tedbow’s picture

Assigned: balintbrews » tedbow
Status: Needs review » Needs work

The 3473761-empty-slot MR has phpunit test failures I will look into.

I re-running the failed e2e tests on the other MR

thoward216’s picture

@tedbow are you still actively working on the failing phpunit tests for 3473761-empty-slot MR?

thoward216’s picture

Assigned: tedbow » thoward216
thoward216’s picture

Status: Needs work » Needs review

Both MRs are now passing tests and ready for review.

tedbow’s picture

Back-end changes look good

wim leers’s picture

Assigned: thoward216 » Unassigned
Status: Needs review » Reviewed & tested by the community

Given FE lead @jessebaker made the FE changes, going ahead here 🚢

wim leers’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs reroll

Both MRs need to get upstream changes merged in 🙏

thoward216’s picture

Both MRs have been rebased.

thoward216’s picture

Status: Needs work » Needs review

  • wim leers committed 1edc451e on 0.x
    Issue #3473761 by wim leers, thoward216, jessebaker, tedbow, shyam_bhatt...
wim leers’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs reroll

Thanks! First one is in. Will merge the second after a new CI pipeline passes.

thoward216’s picture

After merging the first one there was a conflict in the remaining MR, I have rebased that one again.

  • wim leers committed f7adb38d on 0.x
    Issue #3473761 by wim leers, thoward216, jessebaker, tedbow, shyam_bhatt...
wim leers’s picture

Status: Reviewed & tested by the community » Fixed

Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.