/**
* Centralized data attributes used across the Blok editor.
* This is the single source of truth for all data-blok-* attributes.
*
* Exported as Blok.DATA_ATTR for users.
*
* Naming convention:
* - Remove 'data-blok-' prefix and convert to camelCase
* - Global attributes: hidden, disabled, focused
* - Component prefixed: popover, popoverItem, toolbar, etc.
*/
export const DATA_ATTR = {
// ============================================
// Core Element Identifiers
// ============================================
/** Interface type identifier (blok, inline-toolbar, tooltip) */
interface: 'data-blok-interface',
/** Block element wrapper */
element: 'data-blok-element',
/** Block element content wrapper */
elementContent: 'data-blok-element-content',
/** Editor wrapper container */
editor: 'data-blok-editor',
/** Per-instance discriminator on the editor wrapper (a monotonic counter, as a
* string). Two editors on one page share every other scope attribute, so this
* is what lets a page-level stylesheet — Blok's own injected `style.fontSize`
* sheet, or a host rule — address ONE editor. Public styling hook. */
instance: 'data-blok-instance',
/** Redactor zone */
redactor: 'data-blok-redactor',
/** Present on the editor wrapper once a `blocks.render()` batch has finished
* inserting blocks into the DOM; removed while a re-render is in flight.
* Acts as a stable render-readiness gate for consumers (e.g. E2E waits). */
rendered: 'data-blok-rendered',
/** Blok version number stamped on the editor wrapper (e.g. '1.10.0', 'dev').
* Consumed by browser extensions to identify the running version. */
version: 'data-blok-version',
// ============================================
// Block Identifiers
// ============================================
/** Block unique identifier */
id: 'data-blok-id',
/** Block component/tool type */
component: 'data-blok-component',
/** Tool type attribute */
tool: 'data-blok-tool',
/** Block nesting depth (derived from the parentId chain) */
depth: 'data-blok-depth',
/** Flat list-nesting indentation level (0 = root); tool-agnostic, mirrors list depth */
indent: 'data-blok-indent',
/** Header tool's heading level (1-6). Public styling hook — keyed by level rather
* than by tag name, so a level remapped to a custom tag via `levelOverrides[n].tag`
* (types/tools/header.d.ts) still matches its level's typography rules. */
headingLevel: 'data-blok-heading-level',
// ============================================
// Global States
// ============================================
/** Element is hidden from view */
hidden: 'data-blok-hidden',
/** Element is disabled and non-interactive */
disabled: 'data-blok-disabled',
/** Element is focused via keyboard navigation */
focused: 'data-blok-focused',
/** Block is selected */
selected: 'data-blok-selected',
/** Block is stretched */
stretched: 'data-blok-stretched',
/** Editor or element is empty */
empty: 'data-blok-empty',
/** Present on the editor wrapper while read-only mode is active.
* Public styling hook — lets hosts key rules off the editing state
* without JS. Deliberately does NOT collapse the gutter: plain
* read-only still shows the block-hover copy-link control there, and
* in-place readOnly.set() flips must not shift the layout. */
readonly: 'data-blok-readonly',
/** Present on the editor wrapper while read-only mode hides ALL editor
* controls (readOnly: { hideControls: true }). Public styling hook —
* drives the gutter auto-collapse for genuinely chromeless read-only. */
controlsHidden: 'data-blok-controls-hidden',
/** Present on the editor wrapper when config.hideToolbar is true.
* Public styling hook — drives the gutter auto-collapse (the gutter
* exists solely to house the toolbar's +/⠿ controls). */
toolbarHidden: 'data-blok-toolbar-hidden',
/** Which gutter the floating block controls occupy: 'left' (default,
* inline-start) or 'right' (inline-end). Written on the editor wrapper from
* config.toolbarPosition and kept in sync by `toolbar.setPosition()`.
* Public styling hook — drives the gutter swap and the actions-bar side. */
toolbarPosition: 'data-blok-toolbar-position',
// ============================================
// Editor Modes
// ============================================
/** Content alignment mode (left, center, right) */
contentAlign: 'data-blok-content-align',
/** Right-to-left mode */
rtl: 'data-blok-rtl',
/** Editor content width mode (present with value "full" for wide mode; absent = narrow) */
width: 'data-blok-width',
/** Present on the editor wrapper when config.style.nativeSelection is true.
* Public styling hook — disables Blok's ::selection repaint (preflight.css)
* and re-points the fake-background highlight at the UA Highlight color
* (colors.css), so selection falls back to native/host-defined colors. */
nativeSelection: 'data-blok-native-selection',
/** Present on the editor wrapper while a cross-block TEXT selection is painted.
* Suppresses the engine's own ::selection paint (main.css) so the
* ::highlight() sub-ranges are the only thing drawn — Chromium and Firefox
* paint such a range natively too and would otherwise double it up. */
crossSelection: 'data-blok-cross-selection',
// ============================================
// Drag and Drop
// ============================================
/** Block is being dragged */
dragging: 'data-blok-dragging',
/** Multiple blocks being dragged */
draggingMulti: 'data-blok-dragging-multi',
/** Block is being duplicated (Alt+drag) */
duplicating: 'data-blok-duplicating',
/** Drag handle element */
dragHandle: 'data-blok-drag-handle',
// ============================================
// Toolbar
// ============================================
/** Toolbar element */
toolbar: 'data-blok-toolbar',
/** The floating block-controls bar (plus button + drag/settings handle)
* inside the toolbar. Public styling hook — the side it docks to is driven
* from the wrapper's `data-blok-toolbar-position`. */
toolbarActions: 'data-blok-toolbar-actions',
/** Settings toggler button */
settingsToggler: 'data-blok-settings-toggler',
/** Toolbox is open */
toolboxOpened: 'data-blok-toolbox-opened',
/** Block settings is open */
blockSettingsOpened: 'data-blok-block-settings-opened',
/** Element is opened (generic) */
opened: 'data-blok-opened',
// ============================================
// Popover Container
// ============================================
/** Root popover element */
popover: 'data-blok-popover',
/** Popover container wrapper */
popoverContainer: 'data-blok-popover-container',
/** Popover items list */
popoverItems: 'data-blok-popover-items',
/** Custom, engine-independent scrollbar thumb overlaid on the popover items */
popoverScrollbar: 'data-blok-popover-scrollbar',
/** Stamped on the custom scrollbar thumb while it is being dragged (keeps it revealed) */
popoverScrollbarDragging: 'data-blok-dragging',
/** Stamped on a scroll container while it is actively scrolling (reveals the auto-hidden scrollbar thumb) */
scrolling: 'data-blok-scrolling',
/** Popover overlay element */
popoverOverlay: 'data-blok-popover-overlay',
/** Popover custom content area */
popoverCustomContent: 'data-blok-popover-custom-content',
/** Popover custom class */
popoverCustomClass: 'data-blok-popover-custom-class',
/** Inline popover variant */
popoverInline: 'data-blok-popover-inline',
/** Popover is open */
popoverOpened: 'data-blok-popover-opened',
/** Popover opens upward */
popoverOpenTop: 'data-blok-popover-open-top',
/** Popover opens leftward */
popoverOpenLeft: 'data-blok-popover-open-left',
// ============================================
// Popover Nesting
// ============================================
/** Nested popover indicator */
nested: 'data-blok-nested',
/** Nesting level value */
nestedLevel: 'data-blok-nested-level',
/** Group label for promoted search results from nested children */
promotedGroupLabel: 'data-blok-promoted-group-label',
/** Group label for top-level matches in search results */
topLevelGroupLabel: 'data-blok-top-level-group-label',
// ============================================
// Popover Header
// ============================================
/** Header container */
popoverHeader: 'data-blok-popover-header',
/** Header text element */
popoverHeaderText: 'data-blok-popover-header-text',
/** Back button in nested popover */
popoverHeaderBackButton: 'data-blok-popover-header-back-button',
// ============================================
// Popover Items
// ============================================
/** Item container */
popoverItem: 'data-blok-popover-item',
/** Item icon wrapper */
popoverItemIcon: 'data-blok-popover-item-icon',
/** Chevron icon for nested items */
popoverItemIconChevronRight: 'data-blok-popover-item-icon-chevron-right',
/** Item title text */
popoverItemTitle: 'data-blok-popover-item-title',
/** Item secondary title */
popoverItemSecondaryTitle: 'data-blok-popover-item-secondary-title',
/** Item is active/selected */
popoverItemActive: 'data-blok-popover-item-active',
/** Item's child menu is currently open — keeps the trigger looking selected */
popoverItemChildrenOpen: 'data-blok-popover-item-children-open',
/** Confirmation state */
popoverItemConfirmation: 'data-blok-popover-item-confirmation',
/** Disable hover styling */
popoverItemNoHover: 'data-blok-popover-item-no-hover',
/** Disable focus handling */
popoverItemNoFocus: 'data-blok-popover-item-no-focus',
/** Destructive action item (e.g. delete) */
popoverItemDestructive: 'data-blok-popover-item-destructive',
/** Separator item */
popoverItemSeparator: 'data-blok-popover-item-separator',
/** Separator line element */
popoverItemSeparatorLine: 'data-blok-popover-item-separator-line',
/** HTML-based item */
popoverItemHtml: 'data-blok-popover-item-html',
/** Item has child menu */
hasChildren: 'data-blok-has-children',
/** Item name identifier */
itemName: 'data-blok-item-name',
/** No search results shown */
nothingFoundDisplayed: 'data-blok-nothing-found-displayed',
// ============================================
// Overlay / Selection
// ============================================
/** Selection overlay */
overlay: 'data-blok-overlay',
/** Overlay container */
overlayContainer: 'data-blok-overlay-container',
/** Selection rectangle */
overlayRectangle: 'data-blok-overlay-rectangle',
/** Overlay is hidden */
overlayHidden: 'data-blok-overlay-hidden',
/** Fake cursor indicator */
fakeCursor: 'data-blok-fake-cursor',
/** Fake background for selection */
fakeBackground: 'data-blok-fake-background',
// ============================================
// Scroll
// ============================================
/** Auto-scroll zone (top/bottom) */
scrollZone: 'data-blok-scroll-zone',
/** Scroll is locked */
scrollLocked: 'data-blok-scroll-locked',
/** Hard scroll lock */
scrollLockedHard: 'data-blok-scroll-locked-hard',
// ============================================
// Caret
// ============================================
/** Shadow caret element */
shadowCaret: 'data-blok-shadow-caret',
// ============================================
// Placeholders
// ============================================
/** Placeholder text */
placeholder: 'data-blok-placeholder',
/** Active placeholder text */
placeholderActive: 'data-blok-placeholder-active',
// ============================================
// Columns Layout
// ============================================
/** The columns row rendered by the column_list tool (the flex container).
* Public styling hook — its direct `[data-blok-element]` children are the
* column holders, whose shrink floor reads `--blok-column-min-width` and
* whose gutter reads `--blok-column-gutter`. */
columns: 'data-blok-columns',
/** A single column inside a columns row. */
column: 'data-blok-column',
/** Drag-to-resize separator between two adjacent columns. Present only in
* edit mode — the separators ARE the gutter there. */
columnResizer: 'data-blok-column-resizer',
/** Present on a columns row whose gutter comes from the container's own
* column-gap instead of from resizer elements. Set in read-only mode, where
* no resizers are built — the discriminator between an editable row and a
* published one. */
columnsStaticGutter: 'data-blok-columns-static-gutter',
// ============================================
// Nested Blocks
// ============================================
/** Container that hosts nested block holders (table cells, toggle/callout/header children).
* Used as a universal guard: before moving a block holder via appendChild,
* check `holder.closest([nestedBlocks])` — if truthy, the holder is already
* claimed by another container and must not be stolen. */
nestedBlocks: 'data-blok-nested-blocks',
// ============================================
// Mutation Tracking
// ============================================
/** Element excluded from mutation tracking */
mutationFree: 'data-blok-mutation-free',
// ============================================
// Keyboard Ownership
// ============================================
/** Marks a subtree whose keyboard belongs to the Tool that rendered it, not to
* the editor. Blok's block-level keydown/keyup handling stands down entirely
* for events originating inside it — Escape, Tab, the arrows, "/" and the
* Enter/Backspace/Delete structural keys all reach the element untouched.
*
* Blok already exempts native ``/`