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).
| Comment | File | Size | Author |
|---|---|---|---|
| #18 | client-slot-insertion.gif | 260.04 KB | jessebaker |
| #9 | empty_region.png | 129.62 KB | jessebaker |
| screen-gif.gif | 4.09 MB | shyam_bhatt |
Issue fork experience_builder-3473761
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
Comment #3
wim leers@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 👍
Comment #4
wim leersRelated: #3472299-17: Update default config to make a fresh install result in an XB UI with an empty canvas.
Comment #5
jessebaker commentedComment #6
jessebaker commentedComment #7
jessebaker commentedComment #8
jessebaker commentedComment #9
jessebaker commentedI 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]Placeholdersand doing it on the server side instead.Comment #10
jessebaker commentedComment #11
wim leersWhat is the concrete before vs after that you're looking for? What markup needs to change? What API response needs to change?
Comment #12
jessebaker commentedWhen 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/1I'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-placeholdershould not be rendered.For empty slots
In the markup an empty slot currently looks like
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-placeholdershould 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.
On the front end
Once the backend is inserting those DIVs the front end can then remove
ui/src/hooks/useRenderPreviewEmptySlotPlaceholders.tsandui/src/hooks/useRenderPreviewEmptyRegionPlaceholder.tsComment #13
jessebaker commentedComment #14
jessebaker commentedComment #15
wim leersPart one: needs more clarity 🙏
👎 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! 👍
Hah! Should be an easy fix, and easily added test coverage for: in
\Drupal\Tests\experience_builder\Kernel\Plugin\ExperienceBuilder\ComponentSource\SingleDirectoryComponentTest::testGetClientSideInfo()tests withisPreview: FALSE. 👍Part three: needs more clarity 🙏
<!-- 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! 😨\Drupal\experience_builder\ComponentSource\ComponentSourceWithSlotsInterfaceimplementations will need to consistently perform the same.Comment #16
jessebaker commentedThis 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:
and I'd like to see this instead:
Comment #17
wim leersAh, if this is a concern for the
contentregion only, then this seems solvable.Do you think this should be a beta blocker rather than a stable blocker?
Comment #18
jessebaker commentedObviously 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.
Comment #20
wim leers#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 :)
Part two: empty slots
This actually should be fairly doable too:
\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 previewGeneratedFieldExplicitInputUxComponentSourceBase::hydrateComponent()to also receive a$isPreviewparameter, and letting it omit slot examples — it currently looks like this: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 :)
Comment #22
jessebaker commentedI 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.phpis 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).Comment #23
wim leers🥳
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! 🙈Comment #24
tedbowThe 3473761-empty-slot MR has phpunit test failures I will look into.
I re-running the failed e2e tests on the other MR
Comment #25
thoward216 commented@tedbow are you still actively working on the failing phpunit tests for 3473761-empty-slot MR?
Comment #26
thoward216 commentedComment #27
thoward216 commentedBoth MRs are now passing tests and ready for review.
Comment #28
tedbowBack-end changes look good
Comment #29
wim leersGiven FE lead @jessebaker made the FE changes, going ahead here 🚢
Comment #30
wim leersBoth MRs need to get upstream changes merged in 🙏
Comment #31
thoward216 commentedBoth MRs have been rebased.
Comment #32
thoward216 commentedComment #34
wim leersThanks! First one is in. Will merge the second after a new CI pipeline passes.
Comment #35
thoward216 commentedAfter merging the first one there was a conflict in the remaining MR, I have rebased that one again.
Comment #37
wim leersThanks!