This file tracks the progress of the TableBerg architecture transition from block editor primitives (InnerBlocks) to a single block with faux blocks.
- Status: In Progress
- Notes:
- Core table/cell PHP rendering infrastructure now exists and is being expanded element-by-element
- Text and Button element renderers are integrated in
CellRenderer - Image, List, Icon, Star Rating, and Custom HTML element PHP renderers are now added and wired in
- Cell ribbon frontend rendering is now wired in from
cellStyles.ribbonand per-cell ribbon overrides - Element/ribbon renderer coverage is complete
- Frontend DataTable behavior is now complete (sorting, pagination, search)
- Status: Not started
- Notes:
- Status: Complete
- Notes: Freemius SDK now installed via
freemius/wordpress-sdk(v2.13.0). Bundled directories removed from both free and pro packages.
- Status: Complete
- Notes:
- Column sorting: Editor-side and frontend implementation complete
- Search: Editor-side and frontend implementation complete
- Pagination: Editor-side and frontend implementation complete
- All features treated as free during development — will separate pro features after all are done
- Status: Complete (editor side) — will revisit after PHP rendering is added
- Notes:
- Core infrastructure implemented (bindings stored at element level)
- Backend auto-detects source: known post field keys are resolved directly, everything else is treated as post meta
- REST API endpoints:
/dynamic-data/preview(takeskey+ optionalpostId) and/dynamic-data/meta-keys - Editor UI (DynamicDataPanel) integrated into text and button elements
- Collapsible binding cards showing attribute + field summary
- Remaining: Frontend PHP rendering (blocked on PHP render task), Pro sources (ACF, WooCommerce)
- Status: Complete
- Notes: All elements implemented: Image, Star Rating, List, Icon, and Custom HTML. Cell ribbon frontend rendering is also implemented.
- Status: Complete
- Notes:
- Added a PHP-side
TableBlockMigratorV0ToV1that reads the legacytableberg/cellinner block tree and converts it into transient v1 attrs before the existing v1 to v2 migration runs - Legacy top-level table attrs now map into v1
tableConfig,structure,data, andcellStyles, including row/column sizing, search, responsive settings, merged-cell spans, per-cell styles, and ribbons - Legacy child blocks are migrated into current cell elements: paragraph to text, button, image, core list, styled list, icon, star rating, custom HTML, and ribbon; dynamic-field wrappers are unwrapped to their child content during migration
- Follow-up fix:
TableBlockMigratorV1ToV2now strips legacycellStyles.cellsbefore merging cell defaults so per-cell overrides do not leak intocellDefaults.styles
- Added a PHP-side
- Status: Not started
- Notes: Search the codebase for all occurrences of dangerouslySetInnerHTML and replace with safer alternatives (React components, sanitized HTML via dedicated utilities, etc.)
In Progress:
- Elements currently need to receive
cellCoordsandelementIndexas props, which is verbose and error-prone - Create an ElementContext (React Context) that provides:
cellCoords:{ rowIndex, columnIndex }- position of the parent cellelementIndex: number - position within the cell's elements array
- Wrap element rendering in this context so child components can access coords via hook
- Benefits:
- Cleaner component signatures (no prop drilling)
- Easier to add new attributes that need cell position info
- Consistent access pattern across all elements
- Implementation approach:
- Create
ElementContextprovider in element renderer useElementContexthook for consuming components- Update TextElement, ButtonElement, and future elements to use the context
- Create
- Status: Complete
- Notes: Tableberg now works inside the non-iframed editor as well as the iframed editor.
- Status: In progress
- Notes: Non-iframed editor support is now in place, and the remaining work is to continue closing the gaps between faux table elements and native block editor behavior, including:
- Block selection and focus states
- Block mover controls
- Block toolbar (for selected elements)
- Block wrapper controls (alignment, dimensions, etc.)
- Block inserter integration (drag and drop)
- Block deletion and duplication
- Nested block support (if applicable)
- Block navigation/listing
- Responsive preview controls
- Any other iframe-specific functionality
Record progress, decisions, and blockers here after each session.
Completed:
- Updated the editor-side
Column Sortinginspector control inpackages/tableberg/src/controls.tsxso it no longer depends on a selected column - The panel now shows sorting toggles and sort-type selectors for every table column in one place
- Refined the panel UI to use the same collapsible card pattern as
DynamicDataPanel, with one summary row per column - Kept existing restrictions intact: sorting still requires a header row and remains unavailable for merged tables or for individual columns that contain cells with multiple elements
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Fixed faux element List View drag-and-drop reorder syncing in
packages/tableberg/src/hooks/block-editor-compat/useSyncInnerBlocksShim.ts - After detecting a Gutenberg-side inner block reorder, the shim now immediately updates faux block
elementIndexattributes to match the new block positions before the next sync pass - This prevents stale shim attributes from being interpreted as a second reorder request, which previously caused the actual cell element order to revert instead of persisting
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Refactored the persisted table attrs in
packages/tableberg/src/attributes.tsto the flatter top-level shape:tablerowscolumnscellsbindingscellDefaults
- Moved row/column counts into
table.rows/table.cols - Switched persisted cell storage to a record keyed by
"row,col"and added helpers to derive the existing editor/runtime compatibility slices (structure,data,cellStyles) from the new attrs shape - Added the inverse mapping so store mutations that still operate on the compatibility slices now write back to the new persisted attrs shape
- Changed element bindings to use per-element binding ID references plus a top-level
bindingsregistry for batched resolution - Reworked the editor dynamic data panel to create/update/remove binding definitions through the shared store while still editing the selected element's binding refs
- Updated the PHP table attr parser in
packages/tableberg/renderer/Table/TableAttrs.phpto accept the new persisted attrs shape and hydrate the existing renderer-facingstructure/data/tableConfig/cellStylesobjects from it - Added backward-compatible legacy defaults in
packages/tableberg/renderer/Table/Defaults.phpso existing renderer attr classes continue to parse against the derived compatibility structure
Follow-up:
- Updated the PHP renderer to read the new persisted attrs shape directly instead of reconstructing legacy
structure/data/tableConfig/cellStylesobjects first packages/tableberg/renderer/Table/TableAttrs.phpnow parsestable,rows,columns,cells,bindings, andcellDefaultsdirectlypackages/tableberg/renderer/Table/TableRenderer.phpnow reads row/column counts, column configs, cell defaults, and per-cell data directly from that new shape- Bumped the attr schema version to
2and added an editor-side migration path from v1 attrs (structure/data/tableConfig/cellStyles) to the new v2 attrs shape before the table store initializes - Replaced the editor-side migration path with a PHP REST-response migrator in
packages/tableberg/includes/Block_Attrs_Migrator.php, since Gutenberg strips unknown attrs before block edit JS receives them - Moved the PHP migration entry point into
packages/tableberg/renderer/Migrations/and split it into a versioned pipeline:BlockContentMigratorhandles REST-response rewriting for editor loadsTableBlockMigratordispatches per-version migrations fortableberg/tableTableBlockMigratorV0ToV1andTableBlockMigratorV1ToV2run sequentially when needed
Validation:
pnpm --filter tableberg check:typespassesphp -l packages/tableberg/renderer/Table/TableAttrs.phpphp -l packages/tableberg/renderer/Table/Defaults.php
Completed:
- Replaced the placeholder
packages/tableberg/renderer/Migrations/TableBlockMigratorV0ToV1.phpwith a real legacy migrator for pre-v1 Tableberg tables - Updated
packages/tableberg/renderer/Migrations/BlockContentMigrator.phpandpackages/tableberg/renderer/Migrations/TableBlockMigrator.phpso v0 migrations receive the parsed table block, not just the attr object, which lets the migrator read legacytableberg/cellinner blocks and their child blocks - Added legacy v0 to v1 mapping for:
- table config: header/footer flags, sticky header, width/alignment, search, responsive breakpoints, row/column counts
- structure: merged cell spans, row heights, column widths
- cell defaults: legacy padding, inner borders, border radius, zero/default element gaps
- per-cell overrides: legacy row/column/cell backgrounds, borders, border radii, horizontal layout, wrapping, ribbons
- cell content: paragraph to text, core list to list, styled list to styled list data, button, image, icon, star rating, custom HTML, ribbon, and dynamic-field wrapper unwrapping
- Hardened
packages/tableberg/renderer/Migrations/TableBlockMigratorV1ToV2.phpso the transient legacycellStyles.cellsarray is not copied into finalcellDefaults.styles
Decisions:
- Unknown legacy child blocks now fall back to
custom-htmlwhen they have saved HTML, instead of being dropped silently - Legacy row/column styling is expanded to per-cell overrides during migration because the current attr model no longer stores row/column presentation separately
- Legacy gradient-only cell/row/column backgrounds are not persisted when there is no flat color equivalent, since the current cell style schema only stores
backgroundColor
Validation:
php -l packages/tableberg/renderer/Migrations/TableBlockMigrator.phpphp -l packages/tableberg/renderer/Migrations/BlockContentMigrator.phpphp -l packages/tableberg/renderer/Migrations/TableBlockMigratorV0ToV1.phpphp -l packages/tableberg/renderer/Migrations/TableBlockMigratorV1ToV2.php- Targeted
php -rmigration smoke test covering legacy header attrs, paragraph/button conversion, ribbon extraction, and wrap/default handling
Follow-up:
- Fixed legacy
core/paragraphmigration to read saved block HTML from parsed inner blocks whenattrs.contentis absent, preserving rich text markup during paragraph to text-element conversion - Fixed legacy
core/listmigration to read parsedcore/list-iteminner blocks instead of depending only on serialized list HTML, preserving nested list items during list-element conversion - Changed legacy inner-border migration to materialize explicit per-cell borders, including legacy
innerBorderTypeandhideCellOutsideBordersbehavior, instead of storing inner borders only incellDefaults.styles.border - Fixed legacy border defaults so missing
enableInnerBorderstill behaves like the old block default (true), and missinginnerBorderconfig still falls back to1px solid #000000 - Added legacy paragraph-to-text migration support for
styles.backgroundColornow that the text element supports background color - Added a legacy v0 padding fallback so tables with no explicit
cellPaddingmigrate to10pxcell padding instead of inheriting the newer20preset default
Completed:
- Added
styles.backgroundColorto the text element editor schema and inspector controls - Applied text element background color in both editor rendering and frontend PHP rendering so it persists outside Gutenberg
- Exposed the new style as a bindable text element attribute for dynamic data usage
Validation:
pnpm --filter tableberg check:typespassesphp -l packages/tableberg/renderer/Text/TextAttrs.phpphp -l packages/tableberg/renderer/Text/TextRenderer.phpphp -l packages/tableberg/renderer/Text/Defaults.php
Completed:
- Restored legacy-style button width behavior for the current button element by applying custom widths on an outer wrapper instead of the button node itself
- Reused the old 25/50/75/100 percent width calculations so button sizing behaves correctly alongside block gaps and horizontal cell layouts
- Matched the same width handling in the PHP button renderer so frontend output stays consistent with the editor
Validation:
pnpm --filter tableberg check:typespassesphp -l packages/tableberg/renderer/Button/ButtonRenderer.php
Completed:
- Consolidated faux element block registration into a single shared module at
packages/tableberg/src/faux-blocks/index.tsx - Moved faux element selection bridging into the shared faux element edit component so all faux element blocks now:
- sync
useBlockProps()intouseFauxBlockPropsStore - resolve their parent
tableberg/tableblock - reselect the parent table block when the faux element is selected
- sync the active Tableberg element via
setSelectedElement([row, col], elementIndex)
- sync
- Updated
packages/tableberg/src/index.tsxto register faux element blocks through the new shared module instead of importing one registration file per element - Removed the duplicated per-element faux block registration files under
packages/tableberg/src/elements/*/block/index.tsx - Removed the now-unused legacy upsell shim registrations under
packages/tableberg/src/upsells-blocks/ - Follow-up refactor: moved faux element shim metadata and registration into a dedicated
packages/tableberg/src/faux-blocks/directory so faux block assets no longer sit inside the real element source directories or collide with the mainelementsmodule - Follow-up refactor: moved the faux cell shim registration and metadata into
packages/tableberg/src/faux-blocks/cell/for consistency with the faux element block layout
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added a dedicated editor-only faux block props store at
packages/tableberg/src/store/faux-block-props.tsxso hidden shim blocks can publish theiruseBlockProps()output without mixing ephemeral editor state into the main table data store - Wrapped block edit rendering with the new
FauxBlockPropsProviderinpackages/tableberg/src/index.tsx - Updated faux block edit components to call
useBlockProps()and sync those props into the new store viauseEffect:packages/tableberg/src/cell/block/index.tsxpackages/tableberg/src/elements/*/block/index.tsx
- Added helper hooks in
packages/tableberg/src/hooks/useFauxBlockProps.tsand exported them frompackages/tableberg/src/hooks/index.ts - Updated
packages/tableberg/src/cell/index.tsxso:- unselected actual cells use the stored faux cell block props
- unselected actual elements use the stored faux element block props
- selected actual elements continue using the Tableberg block's local
useBlockProps()behavior - wrapper className/style merging preserves both block-editor-provided props and Tableberg layout styles
- Follow-up refactor: replaced shim
cellIndexattrs withcellCoordsobjects ({ row, col }) across cell and faux element block metadata and sync logic, which removes editor bridge dependence onstructure.colsand makes shim identity match the real table coordinate model more directly
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added hidden faux block registrations for the remaining free cell elements so Gutenberg can represent them in the shim tree:
packages/tableberg/src/elements/image/block/*packages/tableberg/src/elements/list/block/*packages/tableberg/src/elements/icon/block/*packages/tableberg/src/elements/star-rating/block/*packages/tableberg/src/elements/custom-html/block/*
- Registered the new faux element blocks from
packages/tableberg/src/index.tsx - Expanded
packages/tableberg/src/cell/block/block.jsonallowedBlocksso faux cell blocks accept all current free element block types - Updated
packages/tableberg/src/hooks/block-editor-compat/useSyncInnerBlocksShim.tsso shim syncing maps each element type to its matching faux block name instead of falling back to text
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Updated
packages/tableberg/src/hooks/block-editor-compat/useSyncInnerBlocksShim.tsso faux inner blocks are synced incrementally instead of rebuilding the entire inner block tree on every change - Table cell shim blocks are now matched by
cellIndex, then inserted, moved, or removed as table structure changes - Element shim blocks inside each faux cell are now matched by block type and position, then inserted, moved, removed, and attribute-synced instead of replacing the whole cell subtree
- Zero-row or zero-column states now remove any existing faux inner blocks instead of leaving stale shim blocks behind
- Added hidden
InnerBlocksmounts for the table and faux cell blocks so Gutenberg List View can operate on the shim tree - List View reordering of faux element blocks now updates the corresponding cell element order in table data instead of snapping back on the next sync
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Cleaned up the block toolbar options menu for Tableberg faux cell/element blocks:
- disabled
lock,renaming,reusable, andvisibilitysupports in faux block metadata:packages/tableberg/src/cell/block/block.jsonpackages/tableberg/src/elements/text/block/block.jsonpackages/tableberg/src/elements/button/block/block.json
- added editor-side menu shim in
packages/tableberg/src/hooks/block-editor-compat/useBlockSettingsMenuShim.tsto hide unsupported or misleading options when a Tableberg cell/element is selected:- Group / Ungroup
- Lock / Unlock
- Rename
- Create pattern
- Hide / Show
- Add note
- intercepts the native
Copy,Cut,Duplicate,Delete,Add before, andAdd aftermenu items when an element is selected so they operate on Tableberg elements instead of faux inner blocks
- disabled
- Extracted shared clipboard helpers into
packages/tableberg/src/hooks/block-editor-compat/elementClipboard.tsand reused them in both keyboard shortcut handling and native menu interception - Added new store actions in
packages/tableberg/src/store/index.tsx:duplicateElementInCell(coord, elementIndex)to clone the selected element immediately after itselfinsertElementInCell(coord, elementIndex, element)to support nativeAdd before/Add aftermenu actions with default text elements
- Wired the new menu shim into
packages/tableberg/src/index.tsx - Follow-up fix:
- changed the shim to bind capture-phase handlers directly to the rendered menu items inside the popover instead of intercepting at
documentlevel, so native menu actions are reliably overridden before Gutenberg block actions run - hardened the shim to read the latest selected element/cell state from refs at interaction time so opening the options menu cannot tear down element-action interception during selection churn
- fixed shim activation so it remains enabled while faux cell/element blocks are selected (the table block itself is not always
isSelectedin that state) - fixed menu hide/show handling so options are restored when no faux selection is active and hidden again when a faux selection is present
- moved native menu action execution to
mousedown/keyboard activation instead of relying onclick, because popover mutations can happen between press and click and detach click-time interception - removed the native block settings menu shim entirely; Tableberg no longer modifies Gutenberg's native options menu for faux cell/element selections
- added a dedicated element toolbar options dropdown (
ElementOptionsButton) that usesToolbarDropdownMenuand provides Tableberg-ownedCopy,Cut,Duplicate,Add before,Add after, andDeleteactions without depending on Gutenberg's native block settings popover - wired the new toolbar options button into free element toolbars: text, button, list, image, icon, custom HTML, and star rating
- changed the shim to bind capture-phase handlers directly to the rendered menu items inside the popover instead of intercepting at
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added editor-side element clipboard shortcut handling in
packages/tableberg/src/hooks/useElementClipboard.ts- supports
Ctrl/Cmd+Cto copy the selected element - supports
Ctrl/Cmd+Xto copy then remove the selected element - supports
Ctrl/Cmd+Vto:- replace the selected element when an element is selected
- append the pasted element to the end of the first selected cell when only a cell is selected
- supports
- Clipboard payloads now use a cheap-to-detect text prefix (
TABLEBERG_ELEMENT:) before JSON-serialized element data - Added parsing/validation guards so only supported Tableberg cell elements are accepted from clipboard payloads
- Added in-memory clipboard fallback so the shortcuts still work when the browser clipboard API is unavailable or blocked
- Avoided intercepting native text clipboard behavior while editing RichText/contenteditable/input/textarea fields
- Wired the new hook into block edit rendering in
packages/tableberg/src/index.tsxand exported it frompackages/tableberg/src/hooks/index.ts
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added a new editor-side inserter extension that shows a
Table elementssection inside the global inserter Blocks browse panel when:- the global inserter is open
- the current block context is the active Tableberg block (or one of its faux inner blocks)
- a target exists via selected element cell or selected cell
- Implemented new component at
packages/tableberg/src/components/TableElementsInserterExtension/index.tsx:- detects active Tableberg context via
core/block-editorselected block + parent chain - tracks inserter open state via
core/editor - resolves insertion target as:
- selected element's containing cell, otherwise
- first selected cell
- uses a
MutationObserver+ portal mount into the inserter Blocks tab container (.block-editor-inserter__block-list) so the section appears in the native inserter UI - reuses existing Tableberg element item definitions (
cellInserterItems) and list UI - inserts elements via existing store action (
addElementToCell) and element factory (createElement)
- detects active Tableberg context via
- Wired extension rendering into block edit in
packages/tableberg/src/index.tsx. - Added editor styles for the new inserter section in
packages/tableberg/src/components/TableElementsInserterExtension/style.scssand imported intopackages/tableberg/src/editor.scss. - Follow-up polish:
- updated mount behavior to prepend the section at the top of the Blocks inserter panel via a dedicated mount node prepended to
.block-editor-inserter__block-list - expanded
useClickOutsidechrome exclusions inpackages/tableberg/src/hooks/useClickOutside.tsto treat inserter sidebar/menu/popover clicks as inside-editor interactions, preventing selection clear before element insert - expanded
useClickOutsideexclusions again to include top document-tools inserter toggle chrome (.editor-document-tools,.edit-post-header-toolbar, and inserter toggle class variants) so using the global +/x inserter button does not clear selected cell/element
- updated mount behavior to prepend the section at the top of the Blocks inserter panel via a dedicated mount node prepended to
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Updated element insertion flow so a newly inserted cell element is selected immediately after insertion
- Modified
addElementToCellinpackages/tableberg/src/store/index.tsxto:- compute the inserted element index for both existing and newly created cell data entries
- set
selectedElementto the inserted element's cell/index - clear
selectedRibbonCellon insert so element focus is unambiguous
- Kept cell selection behavior unchanged (
selectedCellsis preserved)
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added responsive table config types/defaults to editor attributes:
tableConfig.responsive.tablettableConfig.responsive.mobile- Breakpoint fields:
enabled,maxWidth,mode,direction,stackCount,headerAsCol - Synced defaults in:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.php
- Added new responsive controls panel in inspector:
- New component:
packages/tableberg/src/components/ResponsiveControl/index.tsx - New editor styles:
packages/tableberg/src/components/ResponsiveControl/style.scss - Imported via
packages/tableberg/src/editor.scss - Added controls:
- Device switcher (Desktop / Tablet / Mobile) using WordPress preview device APIs
- Enable responsive rule
- Max width (px)
- Responsive mode (
scroll/stack) - Stack-only options:
- Transform rows to columns (
directionrow/col) - Show first column (
headerAsCol) - Items per stack row (
stackCount)
- Transform rows to columns (
- Wired into main controls rendering in
packages/tableberg/src/controls.tsx
- New component:
- Added PHP parsing support for responsive config:
- New parser classes in
packages/tableberg/renderer/Table/TableAttrs.php:ResponsiveBreakpointConfigResponsiveConfig
- Wired into
TableConfig::from_array()as$tableConfig->responsive
- New parser classes in
- Added frontend data attributes output for responsive behavior:
packages/tableberg/renderer/Table/TableRenderer.phpnow outputs responsive metadata when any breakpoint is enabled:data-tableberg-responsivedata-tableberg-rows,data-tableberg-cols- Per-device attrs for tablet/mobile (
enabled,width,mode,direction,count,header)
- Implemented frontend responsive behavior in view script:
- New file:
packages/tableberg/src/frontend/responsive.ts - Supports responsive modes:
scroll(horizontal table scrolling)stackwithdirection: rowstackwithdirection: col
- Handles resize transitions with revive logic when leaving responsive mode
- Preserves base row/cell data attributes for rebuild/revive flow
- Added init in
packages/tableberg/src/view.ts
- New file:
- Added frontend responsive styles:
- New file:
packages/tableberg/src/frontend/responsive.scss - Imported in
packages/tableberg/src/style.scss
- New file:
- Follow-up: added editor-side responsive visual preview wiring in
packages/tableberg/src/table/index.tsx:- Reads Gutenberg preview device (
Desktop/Tablet/Mobile) - Applies responsive preview classes in editor table wrapper based on active breakpoint mode/direction
- Uses CSS preview styles for scroll + stack presentation during editor device preview
- Reads Gutenberg preview device (
- Follow-up: replaced stack editor preview approximation with actual stacked-row rendering in
packages/tableberg/src/table/index.tsx:Items Per Stack Rownow controls real sub-row chunking in editor previewTransform Rows to Columnsnow previews via transpose-before-stack behaviorShow First Column in Every Stack Rownow repeats first cell per generated sub-row in preview
- Follow-up: aligned frontend stack semantics in
packages/tableberg/src/frontend/responsive.ts:stackCountinterpreted as max cells per generated stacked row- when first-column repeat is enabled, first column is included within that max (minimum effective row size of 2)
- Follow-up: renamed responsive breakpoint fields for clarity:
- replaced
direction(row/col) with booleantranspose - replaced
headerAsColwithrepeatFirstCol - updated editor types/defaults, block defaults, PHP defaults/parser, renderer data attrs, and frontend parser/logic
- backward compatibility: legacy
direction/headerAsColare still read as fallbacks
- replaced
Validation:
pnpm --filter tableberg check:typespassesphp -lpasses for:packages/tableberg/renderer/Table/TableAttrs.phppackages/tableberg/renderer/Table/TableRenderer.phppackages/tableberg/renderer/Table/Defaults.php
Completed:
- Fixed selection clearing race in non-iframed editor mode that prevented cell merging from working when clicking toolbar controls
- Updated
packages/tableberg/src/hooks/useClickOutside.tsto ignore clicks inside editor chrome containers:.block-editor-block-toolbar.block-editor-block-popover.components-popover.interface-interface-skeleton__sidebar
- Kept outside-click behavior for true outside clicks while preserving cell selection during toolbar/sidebar interactions
- Hardened hook event handling by:
- early return when
ownerDocumentis unavailable - handling non-Element event targets safely
- using a stable
ownerDocumentreference for add/remove listener symmetry
- early return when
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Updated row insertion flow so every newly created cell in an inserted row starts with a blank Text element instead of an empty elements array
- Extended
insertRowAtinpackages/tableberg/src/table-structure.tswith an optional default element parameter - Added store wiring in
packages/tableberg/src/store/index.tsxsoinsertRowpassescreateElement("text")intoinsertRowAt - Kept merged-cell behavior intact: cells covered by row spans are still skipped and not overwritten
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added row-level config support in table structure:
structure.rowConfigs[*].height(optional string)- Added
RowConfigtype androwConfigsmap inpackages/tableberg/src/attributes.ts
- Added editor store actions in
packages/tableberg/src/store/index.tsx:setRowHeight(row, height)getRowConfig(row)- Mirrors column-width cleanup behavior by removing empty row config objects
- Added inspector UI in
packages/tableberg/src/controls.tsx:- New
Row Heightpanel - Requires a single selected cell to target its row
Height Modetoggle (Auto/Fixed)Row Heightsize control shown inFixedmode (default50px)- Follow-up: grouped row and column sizing controls into a shared
Column & Row Dimensionspanel
- New
- Updated editor cell rendering in
packages/tableberg/src/cell/index.tsx:- Applies row height as
height+min-heightwhen configured - Skips applying row height on cells with
rowSpan > 1
- Applies row height as
- Updated frontend PHP parsing/rendering:
packages/tableberg/renderer/Table/TableAttrs.phpparsesstructure.rowConfigsvia newRowConfigclasspackages/tableberg/renderer/Table/TableRenderer.phpresolves per-row height and passes it into cell render contextpackages/tableberg/renderer/Cell/CellRenderContext.phpnow carries rowheightpackages/tableberg/renderer/Cell/CellRenderer.phpappliesheight+min-heightwhenrowSpan === 1
Validation:
php -lpasses for:packages/tableberg/renderer/Table/TableAttrs.phppackages/tableberg/renderer/Table/TableRenderer.phppackages/tableberg/renderer/Cell/CellRenderContext.phppackages/tableberg/renderer/Cell/CellRenderer.php
pnpm --filter tableberg check:typespasses
Completed:
- Added new cell style field
elementGap(string) for spacing between elements within a cell- Synced defaults in:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.php
- Default value is now
var(--wp--preset--spacing--20)
- Synced defaults in:
- Added editor UI control in
packages/tableberg/src/controls.tsx:- New
Element Spacingcontrol in theCell Elementspanel viaSpacingControlSingle - Scope behavior matches other cell-style controls:
- applies to selected cells when a selection exists
- otherwise applies as the global cell style default
- New
- Updated editor cell rendering in
packages/tableberg/src/cell/index.tsx:- resolves effective
elementGapfrom per-cell override -> global cell style -> defaults - passes spacing into
CellElementRenderer - applies spacing via flex
gapon.tableberg-cell-elements
- resolves effective
- Updated PHP frontend rendering/parsing:
packages/tableberg/renderer/Table/TableAttrs.phpnow parsescellStyles.elementGappackages/tableberg/renderer/Table/TableRenderer.phpnow includeselementGapin global cell render contextpackages/tableberg/renderer/Cell/CellRenderContext.phpnow carries resolvedelementGappackages/tableberg/renderer/Cell/CellRenderer.phpnow:- applies wrapper
gapstyle to rendered cell elements - supports per-cell
elementGapoverride viaoverrideStyles
- applies wrapper
Validation:
pnpm --filter tableberg check:typespassesphp -lpasses for updated renderer files
Completed:
- Added new editor-side table edit toolbar component at
packages/tableberg/src/components/TableEditToolbar/index.tsx- Toolbar is vertical and floats to the left of the table, top-aligned
- Shown when the table block is selected, a cell is selected, and sort preview mode is off
- Added block-toolbar toggles to independently show/hide:
- Row/column edit controls (insert/delete)
- Duplicate/move controls
- Includes controls:
- Insert row before / after
- Delete row
- Insert column before / after
- Delete column
- Duplicate row / duplicate column
- Move row up / down
- Move column left / right
- Duplicate/move controls are currently disabled when the table has merged cells
- Added toolbar styles in
packages/tableberg/src/components/TableEditToolbar/style.scssand imported inpackages/tableberg/src/editor.scss - Added hover preview behavior for toolbar row/column operations:
- Hovering insert/duplicate row/column actions now shows insertion guides between rows/columns (Block Editor-style, single-entry animation)
- Duplicate hover also highlights the source row/column being copied
- Hovering delete row/column actions now tints the affected row/column red
- Edge insertion (after last row/column) shows the same animated guide on the outer boundary
- Implemented via new
tableEditPreviewstore state and cell preview classes/styles inpackages/tableberg/src/cell/toolbar-preview.scss
- Updated table-edit toolbar button tooltips for better readability/interaction:
- Replaced default per-button tooltip behavior with a custom anchored tooltip layer
- Added short hide grace period to reduce flicker when moving across toolbar buttons
- Applied stronger tooltip text weight via
tableberg-edit-toolbar-tooltip - Slightly increased toolbar button hit area to reduce accidental hover loss
- Integrated toolbar into block edit rendering in
packages/tableberg/src/index.tsx- Added
tableberg-editor-shellwrapper class for positioning context - Renders toolbar within the table figure when conditions match
- Added
- Added new structure mutation helpers in
packages/tableberg/src/table-structure.ts:insertRowAt,deleteRowAt,insertColumnAt,deleteColumnAt- Handles merged cell span adjustments (
rowSpan/colSpan) and coordinate shifting - Updates row/column config indices when inserting/deleting
- Added new store actions in
packages/tableberg/src/store/index.tsx:insertRow,deleteRow,insertColumn,deleteColumn- Applies structure/data/style updates through the new helpers
- Resets element/ribbon selection and moves cell selection to a valid resulting cell
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added new inspector panel
Element Font Optionsinpackages/tableberg/src/controls.tsxdirectly after theCell Elementspanel - Added three stateless action controls that open popovers (no persisted control state/value display):
Set elements' text colorSet elements' link colorSet elements' font size
- Updated dropdown actions to use a custom
Resetbutton label:- disabled built-in picker clear/reset actions
- added always-enabled
Resetaction that applies element default values (#000000,"",1.38rem) for the relevant option across scoped cells
- Updated UI styling for the new controls:
- trigger rows now visually match color control rows (without color indicators)
- removed spacing between the three font option rows (stacked contiguous controls)
- custom
Resetaction now uses the same clear-action visual style as WordPress picker clear buttons
- Implemented bulk apply behavior for typography-related elements only:
- Text element (
styles.textColor,styles.linkColor,styles.fontSize) - List element (
styles.textColor,styles.linkColor,styles.fontSize) - Star rating element (
reviewTextColor,reviewTextLinkColor,reviewTextFontSize)
- Text element (
- Scope behavior matches existing helper controls:
- applies to selected cells when a selection exists
- otherwise applies to all cells in the table
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Migrated toggle block source from pro package into free package at
packages/tableberg/src/blocks/toggle- Added
block.json,index.tsx,style.scss,editor.scss, andfrontend.ts
- Added
- Added block-local asset declarations in toggle metadata (
packages/tableberg/src/blocks/toggle/block.json):editorScript: file:./index.tsxeditorStyle: file:./index.cssstyle: file:./index.cssviewScript: file:./frontend.ts
- Added server-side registration + rendering for free toggle block:
- Added
packages/tableberg/renderer/ToggleBlockRenderer.phpwith toggle render callback - Registered
build/blocks/toggle/block.jsoninpackages/tableberg/tableberg.phpwithToggleBlockRenderer::render_toggle_block
- Added
- Removed global toggle imports from free table block entrypoints (
index.tsx,style.scss,editor.scss,view.ts) so toggle assets are handled by toggle block metadata - Updated migrated toggle code to align with free package APIs:
- switched spacing helper import to shared package (
@tableberg/shared/utils/styling-helpers) - adapted
ColorControlprops to usevalue/onChange - removed stray
o;statement from tab removal handler
- switched spacing helper import to shared package (
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Reworked cell elements alignment architecture to be implicit (no persisted
cellStyles.justification):- Removed
justificationfrom table attribute defaults/schema:packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.php
- Updated editor cell rendering (
packages/tableberg/src/cell/index.tsx) to:- stop reading/storing cell-level justification
- apply per-element flex alignment wrappers (
display:flex+justify-content) - derive horizontal group alignment from child element alignment when all elements match
- Updated frontend cell renderer (
packages/tableberg/renderer/Cell/CellRenderer.php) with matching per-element flex wrappers and implicit horizontal alignment behavior
- Removed
- Added new alignment utilities in
packages/tableberg/src/alignment.ts:- canonical element alignment model (
left/center/right) - element alignment read/write helpers across all element types
- canonical element alignment model (
- Applied destructive schema updates (no backward compatibility layer):
- removed all legacy alignment fallbacks and migration transforms
- store now consumes/syncs canonical attrs directly
- Converted cell alignment control in inspector (
packages/tableberg/src/controls.tsx) to implicit behavior:- value is derived from selected/all child elements
- mixed child alignments show indeterminate state
- changing control updates child element alignments (no cell style writes)
- removed
space-betweenfrom alignment options
- Added/standardized element-level alignment support and controls:
- Text: added
alignattr/default + toolbar alignment control (packages/tableberg/src/elements/text/index.tsx) - List: added
alignattr/default + toolbar alignment control (packages/tableberg/src/elements/list/index.tsx) - Custom HTML: added
alignattr/default + toolbar alignment control (packages/tableberg/src/elements/custom-html/index.tsx,packages/tableberg/src/elements/custom-html/controls.tsx) - Button: moved alignment from
styles.alignto top-levelalign; rendering now uses flex justify-content (packages/tableberg/src/elements/button/index.tsx,packages/tableberg/src/elements/button/controls.tsx) - Icon: renamed
styles.justifytostyles.align(packages/tableberg/src/elements/icon/index.tsx,packages/tableberg/src/elements/icon/controls.tsx) - Star Rating: renamed
starAlignto top-levelalign(packages/tableberg/src/elements/star-rating/index.tsx,packages/tableberg/src/elements/star-rating/controls.tsx) - Image: removed class-based internal alignment in favor of cell/element flex alignment wrappers (
packages/tableberg/src/elements/image/index.tsx,packages/tableberg/src/elements/image/style.scss)
- Text: added
- Updated PHP element attrs/defaults for alignment schema parity:
renderer/Text/*,renderer/List/*,renderer/CustomHtml/*renderer/Button/*(alignmoved top-level)renderer/Icon/*(styles.aligncanonical)renderer/StarRating/*(aligncanonical)
Validation:
pnpm --filter tableberg check:typespassesphp -lpasses for all updated renderer PHP files
Completed:
- Added element deletion action in editor store:
removeElementFromCell(coord, elementIndex)inpackages/tableberg/src/store/index.tsx- Removes the element from the target cell and drops empty cell element arrays from
data.cells - Clears
selectedElementwhen the deleted element was selected (and reindexes selection if needed)
- Added shared toolbar delete control:
- New
packages/tableberg/src/components/ElementDeleteButton.tsx - Uses WordPress toolbar button +
trashicon and localized labelDelete element
- New
- Wired delete button into all element toolbars shown on selection:
packages/tableberg/src/elements/text/index.tsxpackages/tableberg/src/elements/button/controls.tsxpackages/tableberg/src/elements/image/controls.tsxpackages/tableberg/src/elements/list/index.tsxpackages/tableberg/src/elements/star-rating/controls.tsxpackages/tableberg/src/elements/icon/controls.tsxpackages/tableberg/src/elements/custom-html/controls.tsx- Updated corresponding element control invocations to pass
cellCoords+elementIndex
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added new cell-level style defaults and schema fields:
cellStyles.orientation(vertical|horizontal, defaultvertical)cellStyles.justification(left|center|right|space-between, defaultleft)cellStyles.wrap(wrap|nowrap, defaultwrap)cellStyles.verticalAlign(top|middle|bottom, defaultmiddle)- Synced defaults in:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.php
- Added inspector UI in
packages/tableberg/src/controls.tsx:- New panel:
Cell Elements Elements Orientationicon toggle (Vertical / Horizontal)Elements Alignmenticon toggleElements Allow WraptoggleElements Vertical Alignmenticon toggle (Top / Middle / Bottom)
- New panel:
- Updated editor cell rendering in
packages/tableberg/src/cell/index.tsx:- Applies
vertical-alignto table cells - Adds flex-based cell element layout support for both orientations
- In vertical orientation, alignment controls remain active and map correctly to vertical/horizontal axes
- In vertical orientation,
space-betweenis excluded from Elements Alignment (unsupported on cross-axis)
- Applies
- Updated frontend PHP rendering/parsing:
packages/tableberg/renderer/Table/TableAttrs.phpparses the new cell style fieldspackages/tableberg/renderer/Table/TableRenderer.phppasses new global cell style fields into render contextpackages/tableberg/renderer/Cell/CellRenderContext.phpnow carries orientation/justification/wrap/verticalAlignpackages/tableberg/renderer/Cell/CellRenderer.phpapplies:- cell
vertical-align - horizontal element wrapper layout styles
- per-cell override handling for new fields
- cell
- Hardened control value fallback in
packages/tableberg/src/hooks/useCellStyleControl.tsso missing legacy style keys fall back to control defaults
Validation:
php -lpasses for:packages/tableberg/renderer/Table/Defaults.phppackages/tableberg/renderer/Table/TableAttrs.phppackages/tableberg/renderer/Table/TableRenderer.phppackages/tableberg/renderer/Cell/CellRenderContext.phppackages/tableberg/renderer/Cell/CellRenderer.php
pnpm --filter tableberg check:typespasses
Completed:
- Added new table-level border config defaults/parsing:
tableConfig.tableBorder(top/right/bottom/left, default empty strings)- Synced defaults in:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.php
- Added editor controls in
packages/tableberg/src/controls.tsx:Table Bordervia sharedBorderControl- Controls update/reset
tableConfig.tableBorder
- Updated editor table rendering in
packages/tableberg/src/table/index.tsx:- Applies outer border styles (
border-top/right/bottom/left) to the<table>element
- Applies outer border styles (
- Updated PHP frontend rendering/parsing:
packages/tableberg/renderer/Table/TableAttrs.phpnow parses table border configpackages/tableberg/renderer/Table/TableRenderer.phpnow outputs matching table border inline styles
- Fixed border conflict when table border is set and only one cell spacing axis is
0:- Added table border-side marker classes on rendered table markup (editor + PHP frontend)
- Extended
packages/tableberg/src/table-spacing.scssto suppress conflicting outer cell borders against table outer border on zero-spacing axis
- Follow-up simplification:
- Removed table border radius setting from editor, attributes, and frontend rendering
Validation:
php -lpasses for:packages/tableberg/renderer/Table/TableAttrs.phppackages/tableberg/renderer/Table/TableRenderer.phppackages/tableberg/renderer/Table/Defaults.php
pnpm --filter tableberg check:typespasses
Completed:
- Added new table-level sticky config defaults/parsing:
tableConfig.stickyHeader(boolean, defaultfalse)- Synced defaults in:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.phppackages/tableberg/renderer/Table/TableAttrs.php
- Added editor controls in
packages/tableberg/src/controls.tsx:- Moved sticky option into
Header & Footer Settings - Renamed sticky control to
Sticky Header
- Moved sticky option into
- Updated editor rendering:
packages/tableberg/src/cell/index.tsxnow applies sticky styles (position: sticky,top,z-index) to top-row header cells when sticky header is enabled- Sticky behavior now applies to header cells in the top row
- Sticky cells now fall back to white background (
#fff) when no background color is configured
- Updated frontend PHP rendering:
packages/tableberg/renderer/Table/TableRenderer.phpnow passes sticky config into cell render contextpackages/tableberg/renderer/Cell/CellRenderContext.phpnow carries sticky header configpackages/tableberg/renderer/Cell/CellRenderer.phpnow applies sticky inline styles and white background fallback for sticky cells
Validation:
php -lpasses for:packages/tableberg/renderer/Table/TableRenderer.phppackages/tableberg/renderer/Table/TableAttrs.phppackages/tableberg/renderer/Cell/CellRenderContext.phppackages/tableberg/renderer/Cell/CellRenderer.phppackages/tableberg/renderer/Table/Defaults.php
pnpm --filter tableberg check:typespasses
Completed:
- Added table-level cell spacing support in table config defaults/parsing:
tableConfig.cellSpacing.horizontal(string, default"0")tableConfig.cellSpacing.vertical(string, default"0")- Synced defaults in:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.phppackages/tableberg/renderer/Table/TableAttrs.php
- Added editor controls in the Dimensions panel (
packages/tableberg/src/controls.tsx):Cell Spacingnow uses sharedSpacingControlfrom@tableberg/components- Control maps left/right to horizontal spacing and top/bottom to vertical spacing
- Reset behavior returns both values to
0
- Updated editor table rendering in
packages/tableberg/src/table/index.tsx:- Applies
border-collapse: separatewhen either spacing value is non-zero - Applies
border-spacing: <horizontal> <vertical>when spacing is enabled - Keeps
border-collapse: collapsewhen both spacing values are zero
- Applies
- Updated PHP frontend rendering in
packages/tableberg/renderer/Table/TableRenderer.php:- Mirrors editor behavior for
border-collapseandborder-spacing - Uses direct
"0"checks for spacing axis values - Added spacing-axis classes on table markup to support border conflict fixes
- Added CSS rules in
packages/tableberg/src/table-spacing.scssto prevent doubled borders when only one spacing axis is0
- Mirrors editor behavior for
Validation:
php -lpasses for:packages/tableberg/renderer/Table/TableRenderer.phppackages/tableberg/renderer/Table/TableAttrs.phppackages/tableberg/renderer/Table/Defaults.php
pnpm --filter tableberg check:typespasses
Completed:
- Added table-level caption support to table config defaults and parsing:
tableConfig.caption(string, default"")- Synced defaults in:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.phppackages/tableberg/renderer/Table/TableAttrs.php
- Added editor caption UI:
- New
TableCaptioncomponent inpackages/tableberg/src/components/TableCaption/index.tsx - Uses
RichTextwithfigcaptionfor formatted caption content - Caption visibility behavior:
- auto-shows when caption has content
- auto-hides when empty and block is deselected
- Added toolbar caption toggle in
packages/tableberg/src/controls.tsx(Add caption/Remove caption)
- New
- Updated editor markup to use semantic figure/caption structure:
packages/tableberg/src/index.tsxnow renders the table block inside<figure>and renders caption below table content- Added
showCaptiontransient state +setShowCaptionaction inpackages/tableberg/src/store/index.tsxto control editor caption visibility
- Updated frontend PHP rendering to output semantic figure/caption structure:
packages/tableberg/renderer/Table/TableRenderer.phpnow wraps output in<figure class="wp-block-tableberg">- Renders
<figcaption class="tableberg-table-caption wp-element-caption">when caption is not empty - Caption content is sanitized via
StringAttr::asHtml()
- Added caption styles in
packages/tableberg/src/components/TableCaption/style.scssand imported into botheditor.scssandstyle.scss
Validation:
php -lpasses for:packages/tableberg/renderer/Table/TableRenderer.phppackages/tableberg/renderer/Table/TableAttrs.phppackages/tableberg/renderer/Table/Defaults.php
pnpm --filter tableberg check:typespasses
Completed:
- Added new color sidebar helper controls in
packages/tableberg/src/controls.tsx:Header Background ColorEven Row Background ColorOdd Row Background ColorFooter Background ColorRow Background ColorColumn Background Color
- Implemented helper control behavior without adding new attributes:
- Controls infer their value from existing per-cell/background-effective values
- Mixed target values show as unset in the control
- Setting a helper control updates background color on the appropriate cells via existing per-cell style overrides
- Target cell resolution now uses
data.cells(notstructure.cells) so helper controls work for normal (unmerged) tables wherestructure.cellsis sparse/default-span-only
- Refactored helper control logic out of
controls.tsxinto new hookpackages/tableberg/src/hooks/useBackgroundColorHelpers.tscontrols.tsxnow consumes generated control props from the hook- helper targeting/inference behavior remains unchanged; refactor is organizational only
- Row/column helper controls are selection-driven:
- shown when cells are selected
- row control targets all cells in selected row(s)
- column control targets all cells in selected column(s)
- Header/footer helper controls are shown only when their respective table sections are enabled
- Even/odd helper controls now target visible data rows (respecting search and pagination), with the first visible data row treated as odd
Validation:
pnpm --filter tableberg check:typespasses
Completed:
- Added table-level width and alignment support to table config defaults and parsing:
tableConfig.tableWidth(auto/wide/full/ custom string, defaultauto)tableConfig.tableAlignment(left/center/right, defaultleft)- Removed width/alignment normalization and legacy value remapping; renderer/editor now use the configured values directly
- Added editor controls in
packages/tableberg/src/controls.tsx:Table Widthcontrol viaSizeControl(default350px)- Toolbar alignment control now includes only
left/center/right - Added sidebar width mode control (
auto/fixed/wide/full) above table width - Table width control is shown only when width mode is
fixed - Toolbar alignment control is disabled when width mode is
auto,wide, orfull - Fixed mode selection now works for legacy blocks by normalizing width first (custom width no longer gets forced back to
auto)
- Updated editor table rendering in
packages/tableberg/src/table/index.tsx:- Applies table width as inline
width+max-widthwhen set - Applies left/center/right alignment class to wrapper (
justify-table-*)
- Applies table width as inline
- Added alignment styles in
packages/tableberg/src/table-width.scss:- left/center/right alignment behavior
- wide/full now follows Gutenberg alignment model via
alignwide/alignfullclasses
- Updated PHP renderer parsing and output:
packages/tableberg/renderer/Table/TableAttrs.phpnow parsestableWidthandtableAlignmentpackages/tableberg/renderer/Table/TableRenderer.phpnow maps wide/full toalignwide/alignfulland applies table width style inline only for left/center/right
- Updated editor block wrapper in
packages/tableberg/src/index.tsx:- applies Gutenberg-style
alignwide/alignfullclass +data-alignwhen table width is wide/full
- applies Gutenberg-style
- Updated editor table rendering in
packages/tableberg/src/table/index.tsx:- applies custom
tableWidthonly when table width is a fixed custom value
- applies custom
- Synced defaults:
packages/tableberg/src/attributes.tspackages/tableberg/src/block.jsonpackages/tableberg/renderer/Table/Defaults.php
Validation:
php -lpasses for updated table renderer filespnpm --filter tableberg check:typespasses
Completed:
- Added fixed-width column support to table attributes:
tableConfig.fixedColumnWidths(boolean)structure.columns[*].width(string)
- Added editor controls in
packages/tableberg/src/controls.tsx:Equal width columnstoggle (table-level)- Per-column
Width Modecontrol (Auto/Fixed) when equal-width mode is disabled Column Widthinput shown only forFixedmode, with default150px(never indeterminate)
- Added new store action in
packages/tableberg/src/store/index.tsx:setColumnWidth(column, width)with cleanup logic to remove empty per-column config objects
- Updated editor cell rendering in
packages/tableberg/src/cell/index.tsx:- Applies computed column width (
width+min-width) to all non-colspan cells - In fixed mode, computes equal width as
100 / colspercent
- Applies computed column width (
- Updated PHP renderer parsing and output:
packages/tableberg/renderer/Table/TableAttrs.phpnow parsesfixedColumnWidthsand columnwidthColumnConfig::from_arraynow supports nullablesortableduring general column parsing (prevents width-only columns from becoming sortable by default)packages/tableberg/renderer/Table/TableRenderer.phpcomputes per-cell column width and passes it to render contextpackages/tableberg/renderer/Cell/CellRenderContext.phpandCellRenderer.phpnow carry and apply cell width (width+min-width) for non-colspan cells
- Synced defaults:
packages/tableberg/src/block.jsonpackages/tableberg/src/attributes.tspackages/tableberg/renderer/Table/Defaults.phpfixedColumnWidthsnow defaults totrue
Validation:
php -lpasses for updated renderer filespnpm --filter tableberg check:typesrun completed without reporting new type errors
Completed:
- Added frontend search metadata output from
packages/tableberg/renderer/Table/TableRenderer.php:data-tableberg-search-enableddata-tableberg-search-placeholderdata-tableberg-search-positiondata-tableberg-search-highlight-color
- Implemented frontend search behavior in
packages/tableberg/src/frontend/search.ts:- renders search UI above each enabled table
- filters data rows by text element content only (for now) while preserving header/footer
- tracks per-row match state (
data-tableberg-search-match) - emits
tableberg:search-changedevent after each filter update - highlights matching search terms inside matched rows using
mark.tableberg-search-highlight
- Tagged rendered text elements with
.tableberg-text-elementinpackages/tableberg/renderer/Text/TextRenderer.phpso frontend search/highlight can target text elements explicitly - Integrated search initialization in
packages/tableberg/src/view.ts - Updated frontend pagination in
packages/tableberg/src/frontend/pagination.tsto:- paginate only search-matched rows
- reset to page 1 when search changes
- listen to both sorting and search events for re-rendering
- Enabled frontend search styles in
packages/tableberg/src/style.scssand added row hide/highlight styles inpackages/tableberg/src/components/SearchInput/style.scss
Validation:
php -lpasses forpackages/tableberg/renderer/Table/TableRenderer.phppnpm --filter tableberg check:typesstill fails due pre-existing unrelated type errors:packages/components/editor/ColorDropdown.tsx(__experimentalColorGradientControlexport)packages/tableberg/src/components/RibbonControl/index.tsx(CellCoordsexport)
Next:
- DataTable frontend behavior is complete; next work can focus on post-transition tasks and pro/free separation
Completed:
- Added frontend pagination config output in
packages/tableberg/renderer/Table/TableRenderer.phpviadata-tableberg-pagination - Implemented frontend pagination behavior in
packages/tableberg/src/frontend/pagination.ts:- reads pagination config from rendered table attrs
- renders pagination controls below each table
- supports page number buttons, prev/next arrows, and ellipsis behavior
- paginates only data rows while preserving header/footer rows
- skips pagination when row-spanning cells are present
- Integrated sorting + pagination initialization in
packages/tableberg/src/view.ts - Added sorting-to-pagination synchronization using a shared table event (
tableberg:rows-changed) inpackages/tableberg/src/frontend/sorting.ts - Enabled frontend pagination styles by importing
packages/tableberg/src/components/PaginationNavigation/style.scssinpackages/tableberg/src/style.scss
Validation:
php -lpasses forpackages/tableberg/renderer/Table/TableRenderer.phppnpm --filter tableberg check:typesstill fails due pre-existing unrelated type errors:packages/components/editor/ColorDropdown.tsx(__experimentalColorGradientControlexport)packages/tableberg/src/components/RibbonControl/index.tsx(CellCoordsexport)
Next:
- Implement frontend search behavior
Completed:
- Added block frontend script entry via
viewScriptinpackages/tableberg/src/block.json - Implemented frontend table sorting in
packages/tableberg/src/view.ts:- initialization for rendered tables with sortable column metadata
- click and keyboard (
Enter/Space) sorting on sortable header cells - sort cycle: ascending -> descending -> original row order
- sort-type-aware parsing for
text,number, anddate - active sort indicator +
aria-sortupdates
- Extended PHP table rendering to expose sorting metadata and hooks:
- table-level data attrs (
data-tableberg-sortable,data-tableberg-header,data-tableberg-footer,data-tableberg-columns) - per-row
data-tableberg-row - per-sortable-header attrs (
data-sortable, keyboard/focus attrs) and indicator markup - added
sortableTypesupport toCellRenderContextandCellRenderer
- table-level data attrs (
- Added frontend sortable-header/indicator styles in
packages/tableberg/src/cell/style.scss
Validation:
php -lpasses for updated renderer filespnpm --filter tableberg check:typesstill fails due pre-existing unrelated type errors:packages/components/editor/ColorDropdown.tsx(__experimentalColorGradientControlexport)packages/tableberg/src/components/RibbonControl/index.tsx(CellCoordsexport)
Next:
- Implement frontend search behavior
- Implement frontend pagination behavior
Completed:
- Added
getBoolOrNullhelper inpackages/tableberg/renderer/utils.phpfor typed boolean extraction - Fixed ribbon parser autoload issue by moving
RibbonConfigAttrsto its own PSR-4 file:Ribbon/RibbonConfigAttrs.php
- Kept ribbon attribute classes in the same module file:
Ribbon/RibbonConfigAttrs.php(RibbonAttrs,RibbonIconAttrs,RibbonStyleAttrs)
- Simplified ribbon defaults parsing to use
get*OrNullhelpers directly infrom_arraymethods (removed redundant fallback literals) - Confirmed renderer syntax validity after refactor (
php -lon updated ribbon files)
Completed:
- Continued server-side frontend rendering work in
packages/tableberg/renderer - Integrated
ImageRendererinto cell element rendering flow (CellRenderernow handlesimage) - Finalized initial image renderer module structure:
Image/Defaults.phpImage/ImageAttrs.phpImage/ImageRenderer.php
- Aligned image rendering with editor attributes:
- alignment wrapper classes (
left/center/right) - size slug URL fallback to original media URL
- aspect ratio / scale / width / height / border / border radius style output
- optional linked image with
_blankrel handling - optional caption rendering
- alignment wrapper classes (
- Fixed namespaced WordPress escaping calls in image renderer (
\esc_url,\esc_attr)
Next:
- Continue implementing the remaining element renderers in PHP and wire them into
CellRenderer - Add frontend scripts for search/sort/pagination after element renderer coverage is sufficient
Completed:
- Added
ListRenderermodule structure inpackages/tableberg/renderer/ListList/Defaults.phpList/ListAttrs.phpList/ListRenderer.php
- Implemented list attribute parsing with defaults for:
- nested list items (
content,indentLevel) - list config (
listType,listStyle) - icon data (
iconName,svg.viewBox,svg.path) - style values (spacing, typography, colors)
- nested list items (
- Implemented frontend list HTML rendering for both modes:
- basic list rendering (
ul/ol) with nested tree generation based on indent levels - styled list rendering with icon/decimal marker output and inline style parity
- basic list rendering (
- Wired list element rendering into
CellRenderer(name === "list") so frontend output now renders list content instead of placeholder element markup
Next:
- Continue implementing the remaining element renderers in PHP and wire them into
CellRenderer - Add frontend scripts for search/sort/pagination after element renderer coverage is sufficient
Completed:
- Added
IconRenderermodule structure inpackages/tableberg/renderer/IconIcon/Defaults.phpIcon/IconAttrs.phpIcon/IconRenderer.php
- Implemented icon attribute parsing with defaults for:
- icon source (
iconName, optional SVG data, optional URL) - size and behavior (
paragraph/char) - link config (
url,target) - style values (justify, rotation, colors/hover colors, spacing, border, border radius)
- icon source (
- Implemented frontend icon HTML rendering with editor parity:
- SVG icon rendering and URL image rendering
- icon link wrapping with
_blankrel handling - hover CSS custom property output (
--tableberg-icon-color-hover,--tableberg-icon-bg-hover) - behavior class support (
tableberg-icon-as-char)
- Wired icon element rendering into
CellRenderer(name === "icon") so frontend output now renders icon content instead of placeholder element markup - Refactored duplicated SVG icon attribute objects into shared
SvgIconAttrsinrenderer/Attrs, reused by both List and Icon renderers
Next:
- Continue implementing the remaining element renderers in PHP and wire them into
CellRenderer - Add frontend scripts for search/sort/pagination after element renderer coverage is sufficient
Completed:
- Added
StarRatingRenderermodule structure inpackages/tableberg/renderer/StarRatingStarRating/Defaults.phpStarRating/StarRatingAttrs.phpStarRating/StarRatingRenderer.php
- Implemented star-rating attribute parsing with defaults for:
- star config (
starCount,starSize,starColor,selectedStars) - review text config (
enableText,reviewText,reviewTextAlign,reviewTextColor) - star alignment (
starAlign)
- star config (
- Implemented frontend star-rating HTML rendering with editor parity:
- fractional star fill rendering via SVG masks
- alignment classes (
tableberg-stars-center,tableberg-stars-right) - optional review text rendering with sanitized HTML + inline text alignment/color styles
- Wired star-rating element rendering into
CellRenderer(name === "star-rating") so frontend output now renders star-rating content instead of placeholder element markup
Next:
- Continue implementing the remaining element renderers in PHP and wire them into
CellRenderer - Add frontend scripts for search/sort/pagination after element renderer coverage is sufficient
Completed:
-
Added
CustomHtmlRenderermodule structure inpackages/tableberg/renderer/CustomHtmlCustomHtml/Defaults.phpCustomHtml/CustomHtmlAttrs.phpCustomHtml/CustomHtmlRenderer.php
-
Implemented custom HTML attribute parsing and frontend rendering using sanitized HTML output (
StringAttr::asHtml()) -
Wired custom HTML element rendering into
CellRenderer(name === "custom-html") -
Added ribbon frontend renderer module in
packages/tableberg/renderer/RibbonRibbon/Defaults.phpRibbon/RibbonConfigAttrs.phpRibbon/RibbonRenderer.php
-
Implemented ribbon parsing and rendering for all ribbon types:
badgebookmarkcornersideicon
-
Added shared cell ribbon resolution in rendering context:
- global ribbon config from
cellStyles.ribbon - per-cell ribbon override from
cellStyles.cells[*][1].ribbon - merged override behavior before render
- global ribbon config from
-
Wired ribbon HTML output into cell frontend markup so rendered table cells include ribbon overlays
Next:
- Add frontend scripts for search/sort/pagination now that element/ribbon renderer coverage is complete
Completed:
-
Added CustomHtmlElement with features from old pro version:
- HTML code editing via PlainText input
- Live preview rendering in sandboxed iframe
- HTML/Preview toggle buttons in toolbar
- Dynamic data binding support for HTML content
- Auto-switch to preview mode when content is bound
-
Updated infrastructure:
- Added
CustomHtmlElementTypetoCellElementunion inattributes.ts - Updated
elements/index.tsexports andcreateElementfunction - Updated
CellChildAutoCompleter.tsxto enable custom-html - Updated
cell-inserter/items.tsto enable custom-html - Updated
Cellcomponent to render CustomHtmlElement - Updated
bindable-attributes.tsto support custom-html element - Added SCSS styles for custom-html element
- Added
Architecture decisions:
- CustomHtmlElement follows established element pattern (ElementRendererProps, controls separation, store integration)
- Preview uses sandboxed iframe with
pointer-events: noneto allow selection clicks to pass through - Editor styles are injected into iframe for consistent rendering
- HTML editor shown by default; preview only when user clicks Preview button or content is bound
- Integrates with sortPreviewMode (shows preview during sort preview)
- Single bindable attribute:
content(HTML string)
Completed:
-
Added IconElement with full feature parity from old pro version:
- Icon selection from built-in library (WordPress icons, Font Awesome, social icons)
- URL import for custom SVG/image icons
- Icon size control (HeightControl)
- Rotation (-180 to 180 degrees)
- Justify/alignment (left, center, right)
- Behavior mode: block-level ("paragraph") or inline ("char")
- Link support with URL and target (_blank/_self)
- Color controls with normal/hover states
- Background color/gradient with normal/hover states
- Padding and margin spacing controls
- Border and border-radius controls
- Dynamic data bindings for: linkUrl, color, background
-
Updated infrastructure:
- Added
IconElementTypetoCellElementunion inattributes.ts - Updated
elements/index.tsexports andcreateElementfunction - Updated
CellChildAutoCompleter.tsxto enable icon - Updated
cell-inserter/items.tsto enable icon - Updated
Cellcomponent to render IconElement - Updated
bindable-attributes.tsto support icon element - Added SCSS styles for icon element
- Added
Architecture decisions:
- IconElement follows the established element pattern (ElementRendererProps, controls separation, store integration)
- Reuses existing
@tableberg/components/icon-librarycomponents (Icon, IconPickerMini) - Icon data structure supports both library icons (with SVG path data) and URL-based custom icons
- Hover states use CSS custom properties (
--tableberg-icon-color-hover,--tableberg-icon-bg-hover) - Integrates with sortPreviewMode (selection disabled during sort preview)
- Supports dynamic data bindings following established pattern
Completed:
- Added search feature with full editor functionality (frontend deferred until PHP rendering is done)
- New types in
attributes.ts:SearchConfig:{ enabled, placeholder, highlightColor }- Added
search?: SearchConfigtoTableConfig
- Created
search.tswith utilities:filterRowsBySearch()- filters rows by search term across all cell contentgetMatchingRowCount()- returns count of matching data rowshighlightSearchMatches()- wraps matching text in<mark>tags with custom background color
- Updated store with:
- Search state:
searchTerm(transient, not persisted) - Actions:
setSearchTerm,setSearchConfig,getFilteredRowIndices
- Search state:
- New UI components:
SearchInput- search input with icon, clear button, and result count (rendered above table)SearchControl- inspector panel with:- Enable search toggle
- Placeholder text customization
- Highlight color picker (uses @tableberg/components ColorControl with theme colors)
- Updated
PrimaryTableto integrate search → sort → paginate pipeline:- Filter rows by search term
- Sort the filtered rows (if sorting enabled)
- Paginate the sorted/filtered rows (if pagination enabled)
- Updated
PaginationNavigationto acceptfilteredRowCountprop for correct page calculation when search is active - Added search highlighting to TextElement and ButtonElement:
- Highlights matching text with configurable background color
- Highlighting only active when highlightColor is set (non-empty)
- Shows highlighted (read-only) view when search is active with highlight color
- RichText editing disabled during active search with highlighting to preserve highlights
Architecture decisions:
- Search config stored in
tableConfig.search(table-level setting) searchTermis transient state (not persisted to block attributes) - resets on page reload- Search is case-insensitive and matches any part of cell content
- All cell elements'
contentattribute is searched (text, button labels, etc.) - Header and footer rows are always preserved in results
- Search resets pagination to first page when term changes
- No restrictions on search with merged cells or row-spanning cells (unlike sorting/pagination)
- Highlight uses
<mark>elements with inline background-color style - Highlight is disabled when highlightColor is empty (no separate toggle)
- Uses theme color palette via @tableberg/components ColorControl
Deferred:
- Frontend search script - will be implemented alongside PHP rendering task
- PHP needs to output
data-tableberg-searchattribute with config - Frontend JS will render search input and handle filtering/highlighting
- PHP needs to output
Completed:
- Removed localStorage caching from
useDynamicData— react-query handles all caching - Removed
bindingsFingerprint— pass bindings object directly to query keys - Removed PHP Resolver class and Sources (SourceInterface, PostFieldSource, PostMetaSource, ElementRenderer)
- Consolidated all dynamic data logic into
RestControllerwith procedural code - Removed
sourceparameter entirely — backend auto-detects post field vs post meta by checking key against known post field list - Renamed
/dynamic-data/keysendpoint to/dynamic-data/meta-keys(post field keys are static and known client-side) - Removed
/dynamic-data/sourcesendpoint - Simplified
BindingSourcetype: removedsourcefield, now just{ key, postId?, fallback? } - Removed
typefield fromBindableAttribute(was never used) — now just{ path, label } - Standardized dynamic data values to always be
string | null(cast to string on PHP side) - Reworked DynamicDataPanel UI:
- Only shows currently bound attributes (not all bindable ones)
- "Add Binding" button opens a blank form with attribute selector
- Bound attributes show as collapsible cards with attribute label + field summary
- Attribute dropdown shown in both new and existing binding forms
- Forms submit on Enter
- Switched
fetchAvailableMetaKeysto useuseQueryfor caching
Architecture decisions:
- No need for source abstraction — post field keys are a fixed known set, anything else is post meta
- Dynamic data always returns strings (PHP casts non-null values)
BindableAttribute.typeremoved — the consumer already knows what it expects for each path
Completed:
- Created bindings architecture with parallel
bindingsobject on elements - TypeScript types:
BindingSource,ElementBindings,BindableAttribute - PHP infrastructure:
DynamicData\RestController- REST API endpoints for editor preview
- Editor components:
DynamicDataPanel- Inspector sidebar panel for configuring bindingsuseDynamicDatahook for fetching preview values- Integrated into TextElement and ButtonElement controls
- Store updated with
updateSelectedElementBindingsandgetSelectedElement
Architecture decisions:
- Bindings stored at element level (self-contained, easy to copy/paste)
- Parallel to attributes (static value preserved as fallback)
- Nested path support (e.g.,
link.url,styles.backgroundColor)
Pending:
- Frontend PHP rendering needs to be implemented first (separate task)
- Pro sources (ACF, WooCommerce) to be added later
- Deprecate old Pro DynamicField block and migrate
Completed:
- Added pagination feature with full editor functionality (frontend deferred until PHP rendering is done)
- New types in
attributes.ts:PaginationConfig:{ enabled, pageSize, showPageNumbers, showPrevNext }- Added
pagination?: PaginationConfigtoTableStyles
- Created
pagination.tswith utilities:tableHasRowSpanningCells()- detects rows with rowSpan > 1isPaginationAllowed()- checks if pagination can be enabled (no row-spanning cells)getTotalPages()- calculates total pages excluding header/footergetPagedRowIndices()- returns row indices for current page (header/footer always included)defaultPaginationConfig- default config values
- Updated store with:
- Pagination state:
currentPage - Actions:
setCurrentPage,setPaginationConfig,getPaginationConfig,isPaginationAllowed
- Pagination state:
- New UI components:
PaginationNavigation- navigation controls below table (prev/next buttons, page numbers with ellipsis)PaginationControl- inspector panel with enable toggle, page size input with suggestions (5, 10, 20, 30), navigation style toggles
- Updated
PrimaryTableto render paginated rows when pagination is enabled - Added sort indicator chevrons in header cells when sorting is enabled (visible even outside preview mode)
Architecture decisions:
- Pagination config stored in
tableStyles.pagination(table-level setting) - Pagination allowed only if table has no row-spanning cells (colSpan OK, rowSpan not OK)
- Header row (if enabled) always shown on all pages
- Footer row (if enabled) always shown on all pages
- Pagination and sorting work together: sort first, then paginate
- No preview mode for pagination - controls are always visible and functional when enabled
- At least one of showPageNumbers or showPrevNext must be enabled (toggles are disabled when they're the only option)
Deferred:
- Frontend pagination script - will be implemented alongside PHP rendering task
- PHP needs to output
data-tableberg-paginationattribute with config - Frontend JS will render page navigation controls and handle page switching
- PHP needs to output
Completed:
- Added column sorting feature (editor preview only - frontend deferred until PHP rendering is done)
- New types in
attributes.ts:SortableType:'text' | 'number' | 'date'ColumnConfig:{ sortable?: SortableType }- Added
columns?: Record<number, ColumnConfig>toStructure
- Created
sorting.tswith utilities:getColumnsWithMergedCells()- detects columns with merged cellsgetColumnsWithMultipleElements()- detects columns with multi-element cellsisColumnSortable()- checks if column can be sortedsortRowsByColumn()- performs the actual sort
- Updated store with:
- Sort preview state:
sortPreviewMode,previewSortColumn,previewSortOrder - Actions:
enterSortPreviewMode,exitSortPreviewMode,togglePreviewSort,getSortedRowIndices - Column config actions:
setColumnSortable,getColumnConfig,isColumnSortableAllowed
- Sort preview state:
- New UI components:
SortPreviewBanner- banner shown during preview modeColumnSortingControl- inspector panel for configuring column sorting
- Updated
PrimaryTableto render sorted rows in preview mode with clickable sort indicators
Architecture decisions:
- Sorting config stored in
structure.columns(nottableStyles) - Column is sortable only if it has no merged cells AND no cells with multiple elements
- Editor preview mode is temporary - exiting reverts to original order
- No global "enable sorting" toggle - sorting is enabled per-column via the "Sort as" dropdown
Deferred:
- Frontend sorting script - will be implemented alongside PHP rendering task
- PHP needs to output data attributes (
data-tableberg-columns,data-tableberg-header,data-tableberg-footer) - Frontend JS will read config and add click-to-sort on header cells
- PHP needs to output data attributes (
Completed:
- Created
mergeAttrsWithDefaultsAndApplyBindingshelper inpackages/shared/utils/merge-attrs-with-bindings.ts- Combined function that merges partial attrs with defaults, then applies dynamic binding values
- Supports dot-notation paths for bindings (e.g.,
link.url,styles.backgroundColor) - Uses shallow-cloning along the path for immutability
- Accepts optional
defaultFallbackfor when preview is null and no per-binding fallback is set - Removed separate
merge-attrs-with-defaults.tsfile
- Updated ButtonElement to consume all bindable attributes:
content- button textlink.url- link URLid- HTML anchor (newly added)styles.backgroundColor- background colorstyles.textColor- text color
- Added
sortPreviewModeguard to ButtonElement (matching TextElement behavior)- Prevents element selection during sort preview
- Renders as static
<div>when in sort preview mode
- Added UI feedback for dynamically-bound controls:
- Link toolbar button disabled with tooltip when
link.urlis bound - Color control labels show "(dynamic)" suffix when bound
- HTML anchor help text changes when
idis bound
- Link toolbar button disabled with tooltip when
- Refactored TextElement to use
mergeAttrsWithDefaultsAndApplyBindingsfor consistency
Architecture decisions:
- Single combined function
mergeAttrsWithDefaultsAndApplyBindingshandles both defaults and bindings in one call - Default fallback
"(No data)"is applied to all bound attributes when preview is null and no fallback configured (even for non-text paths like URL/color — browser handles invalid values gracefully) - Binding detection variables (
linkUrlIsBound,textColorIsBound, etc.) added to controls for UI feedback contentIsBoundvshasDynamicBindingsdistinction: former controls RichText/div rendering, latter controls CSS class
Completed:
-
Added ImageElement with full feature parity from old version:
- Media upload/selection from WordPress media library
- Image resize with ResizableBox (drag handles)
- Aspect ratio control (Original, 1:1, 4:3, 3:4, 3:2, 2:3, 16:9, 9:16)
- Scale options (cover, contain) when aspect ratio is set
- Width and height controls (px units)
- Resolution/size slug selection (thumbnail, medium, large, full)
- Alt text support
- Alignment (left, center, right)
- Caption support with toggle
- Link support with target options
- Border and border radius controls
- Dynamic data bindings for: media.url, alt, href, caption
-
Added StarRatingElement with full feature parity from old version:
- Star count (5-10 range)
- Star size (10-50px)
- Star color with theme color picker
- Selected stars value (0.1 step increments, supports half stars)
- Interactive star selection (click to toggle between full/half)
- Star alignment (left, center, right)
- Optional review text with RichText editing
- Review text alignment (left, center, right, justify)
- Review text color
- Dynamic data bindings for: selectedStars, reviewText
-
Updated infrastructure:
- Added new element types to
CellElementunion inattributes.ts - Updated
elements/index.tsexports - Updated
CellChildAutoCompleter.tsxto enable image and star-rating - Updated
cell-inserter/items.tsto enable image and star-rating - Updated
Cellcomponent to render new elements - Updated
bindable-attributes.tsto support new elements - Added SCSS styles for both elements
- Added new element types to
Architecture decisions:
- Both elements follow the established pattern (ElementRendererProps, controls separation, store integration)
- Image element uses WordPress MediaUpload and ResizableBox for native WP experience
- Star rating uses SVG-based Star component from @tableberg/shared/icons for fractional star rendering
- Both elements integrate with sortPreviewMode (selection disabled during sort preview)
- Both support dynamic data bindings following established pattern