Change record status: 
Project: 
Introduced in branch: 
1.0.x
Introduced in version: 
1.0.0-beta9
Description: 

Before 1.0.0, a beta renames or removes an API outright, with no deprecation layer. This record lists every such change of 1.0.0-beta9.
Run the database updates: they convert the profiles and presets of a site. Configuration shipped by a module, a theme, a recipe or an install profile has to be converted by hand, as each section says.

The Builder renders in an iframe

Two themes, two documents

The builder page renders with the admin theme. The Builder renders in a same-origin iframe, a page of its own on the front theme. The library previews load the same way, each in an iframe on the front theme.
The builder page CSS does not reach the Builder iframe, and the builder's own scripts, htmx and Shoelace do not run in it. The front theme's scripts run as on any page.

JavaScript working on Builder nodes

Its nodes are in the iframe's document, not in the builder page: builder.querySelectorAll('[data-node-id]') no longer finds them. JavaScript working on Builder nodes from the builder page searches both documents with Drupal.displayBuilder.queryAll():

// Before.
const nodes = builder.querySelectorAll('.db-island-builder [data-node-id]');

// After: the builder page, then its Builder iframes.
const nodes = Drupal.displayBuilder.queryAll(builder, '[data-node-id]');

A request changing the Builder resolves before the iframe reloads. Wait for it before reading the nodes:

Drupal.displayBuilder.afterBuilderRefresh(builder).then(() => {
  const nodes = Drupal.displayBuilder.queryAll(builder, '[data-node-id]');
});

The builder page's once() does not work on a Builder node: it returns an empty array for an element of another document. Flag the node with a property instead.
instanceof checks fail across documents too: test nodeType or tagName.

More in the Builder iframe documentation.

Preview is a mode of the Builder

The Preview is a toggle on the Builder

The Preview was a panel, shown beside the Builder or the Scaffold in a split view. It is a toggle in the Builder header, shortcut p, which shows the display over the Builder at the same width and zoom.
A button opens the same preview in a new tab, which reloads on each change. The split view is removed.

The island keeps its ID, preview: the profiles of a site need no change for it.

IslandType::Preview is removed

The preview island is a Secondary island attached to the Builder, attach_to: ['builder'], rendered in its header with pane_header: TRUE. IslandType has six cases. An island declaring type: IslandType::Preview must pick another type.

The chrome indicator island is removed

The preview shows whether the site's header and footer wrap the display. display_builder_post_update_drop_chrome_indicator() removes chrome_indicator from the profiles of a site. A profile shipped as configuration drops the key.

IslandType::Floating is renamed IslandType::Secondary

The island type of the controls attached to a main area panel, such as the viewport switcher, the Highlight toggle or the Scaffold options, was IslandType::Floating. These controls no longer float: they sit in the header row of their panel.
The case is renamed IslandType::Secondary, its value secondary, and the profile form lists these islands as Secondary controls.

An island declaring the type has to use the new case. Its attach_to and pane_header settings are unchanged.

Before:

#[Island(
  id: 'my_control',
  label: new TranslatableMarkup('My control'),
  type: IslandType::Floating,
  attach_to: ['builder'],
  pane_header: TRUE,
)]

After:

#[Island(
  id: 'my_control',
  label: new TranslatableMarkup('My control'),
  type: IslandType::Secondary,
  attach_to: ['builder'],
  pane_header: TRUE,
)]

CSS classes, test IDs and the pinned box

The wrapper of such an island carries db-island-secondary, not db-island-floating. Its data-testid starts with secondary_, not floating_: secondary_highlight, secondary_viewport.

The box pinned over a panel, for a control without pane_header, is renamed the same way:

  • its class db-island-floating-controls becomes db-island-secondary-controls;
  • its z-index token --db-z-floating-controls becomes --db-z-secondary-controls;
  • the view_floating_controls slot of the display_builder:display_builder component becomes view_secondary_controls.

A theme styling the box, or overriding the component's template, has to use the new names. Profiles store no island type: they need no change.

The component Library lists the default theme, other themes opt in

Other themes are listed once included

The Builder renders in the default theme and loads its CSS only. The component Library listed the components of every installed theme, the admin theme included. It lists those of the default theme, its base themes and modules now.
The new Include other themes setting of the Components island adds the components of another installed theme. Exclude providers still hides a module, the default theme or one of its base themes.

A profile listing the components of another theme stops listing them: edit the profile and include that theme. Components already placed in a display stay.

A profile shipped as configuration includes a theme with the new key:

islands:
  component_library:
    status: true
    include_themes:
      my_theme: my_theme

Styles and Design tokens follow the default theme

The Styles and Design tokens panels offered the styles and CSS variables of every installed theme. They offer those of the default theme, its base themes and modules now.

Profile island settings: exclude_id is a list, the Scaffold has no components

exclude_id is a list

The exclude_id setting of the component_library and block_library islands was a string, one ID per line. It is a list of IDs. The profile form still edits it as one ID per line.

display_builder_post_update_exclude_id_as_list() converts the profiles of a site. Run the database updates.
A profile shipped as configuration by a module, a theme, a recipe or an install profile must convert the key: a string fails the configuration schema check of tests.

Before:

component_library:
  status: true
  exclude_id: "navigation\nui_suite_bootstrap:pagination"

After:

component_library:
  status: true
  exclude_id:
    - navigation
    - 'ui_suite_bootstrap:pagination'

The Scaffold has no components setting

The Scaffold rendered the components of its components setting for real, and every other node as a card. It renders every node as a card now, and the setting is removed, with the Components option of the Scaffold options.

display_builder_post_update_scaffold_drop_components() removes it from the profiles of a site. A profile shipped as configuration must drop the key: display_builder.islands.scaffold has no schema any more.

Pattern presets no longer have a weight

A pattern preset had a weight, set by dragging the rows of the presets list. It is removed.

The library lists presets by group: groups in alphabetical order, presets without a group last under Others, then by label within a group. To list a preset higher, give it a group or a label that sorts first.

The presets list at Structure » Display builder » Presets is no longer draggable. It is a table you filter by group, context and label, and sort by label, publication or last update.

Stored presets

display_builder_post_update_remove_pattern_preset_weight() removes the key from the presets of a site. Run the database updates.

Presets shipped as configuration

A preset shipped by a module, a theme, a recipe or an install profile must drop the key: display_builder.pattern_preset.* has no weight in its schema any more.
Kept, it fails the configuration schema check of tests, and loading the preset sets a dynamic property on the entity, a PHP deprecation.

Before:

id: hero_dark
label: 'Hero, dark'
group: Heroes
weight: 3

After:

id: hero_dark
label: 'Hero, dark'
group: Heroes

Buildables answer access(), checkAccess() is removed

DisplayBuildableInterface::checkAccess() is replaced by access()

DisplayBuildableInterface extends Drupal\Core\Access\AccessibleInterface. The static checkAccess(string $instance_id, AccountInterface $account) is removed.
A buildable answers access() for the display it was built for, instead of reading an instance ID. A class implementing DisplayBuildableInterface has to implement access(): DisplayBuildablePluginBase does not provide it.

Before:

$access = $buildable::checkAccess($instance_id, $account);

After:

$access = $instance->getBuildablePlugin()->access('update', $account, TRUE);

collectDisplays() lists only what the current user may build

Each collectDisplays() checks access() itself. The Instances panel no longer filters the list: a buildable listing displays has to do it.

An instance with no buildable is no longer allowed

InstanceAccessControlHandler asks the instance's buildable plugin. An instance whose ID matched no buildable prefix used to be allowed, for example in a test. It now needs a buildable.

Instance tree methods throw instead of failing silently

One failure style

InstanceInterface now reports every failure the same way:

  • An unknown node or parent throws InvalidNodeException. So does a move into the node itself or into its own descendant, checked before the slot cardinality.
  • A full root or slot throws \OutOfRangeException, as before.
  • moveToRoot(), moveToSlot(), setSource(), setThirdPartySettings() and remove() return the instance. The moves no longer return FALSE.
  • getNode() returns NULL for a missing node, not [].
  • getParentId() throws for a missing node. NULL keeps meaning "at the root".

Before:

if (!$instance->moveToSlot($node_id, $parent_id, $slot_id, 0)) {
  // Unknown node, unknown parent, or a move into a descendant.
}
$instance->remove($node_id);

After:

try {
  $instance->moveToSlot($node_id, $parent_id, $slot_id, 0)->remove($other_id);
}
catch (InvalidNodeException $e) {
  // Unknown node, unknown parent, or a move into a descendant.
}
catch (\OutOfRangeException $e) {
  // The slot is full.
}

getSlotNodeIds() lists a slot

InstanceInterface::getSlotNodeIds(string $parent_id, string $slot_id): ?array returns the IDs of the nodes in a slot, in order: empty for an empty or unknown slot, NULL when the parent node does not exist. Read a slot through it rather than through the data of the source plugin.

To check before calling: getNode() returns NULL for a missing node, and getSlotNodeIds() returns NULL for a missing parent.
There is no query for a full root or slot: catch \OutOfRangeException. On the builder's own API routes, both exceptions become an error message in the builder.

Override buildables split revert(), and setters return the object

DisplayBuildableOverrideInterface::revert() is split in two

revert() did two jobs: it emptied the override field and saved the entity, and it returned the sources of the overridden display. It is removed, replaced by:

  • getDefaultSources(): array, which only reads the sources of the overridden display.
  • deleteOverride(): void, which empties the override field and saves the entity.

A class implementing DisplayBuildableOverrideInterface has to implement both.

Before:

$sources = $buildable->revert();

After:

$buildable->deleteOverride();
$sources = $buildable->getDefaultSources();

Setters return the object

ProfileInterface::setIslandConfiguration() and PageLayoutInterface::setSources() return static, so calls chain. A class implementing either interface has to declare the new return type.

isDisplayBuilderEnabled() declares bool

DisplayBuilderEntityDisplayInterface::isDisplayBuilderEnabled() declares bool. A class implementing it has to declare it too.

Impacts: 
Site builders, administrators, editors
Module developers
Themers
Site templates, recipes and distribution developers