Skip to content

Merge development for v5.1.0 - #650

Merged
mkocansey merged 84 commits into
mainfrom
development
Sep 10, 2026
Merged

mkocansey merged 84 commits into
mainfrom
development

Conversation

@mkocansey

Copy link
Copy Markdown
Collaborator

Summary

Merges the v5.1.0 development cycle into main.

New components: Divider, Confirm Dialog (later removed, see below), Context Menu, Currency Input, Password Strength Meter, Document/File Preview, Transfer List, Inline Edit, Keyboard Key, Copy to Clipboard Button, Meter/Gauge, Description List, Banner (later removed, see below), Code Block, Chat, Carousel, Kanban, Scheduler, Credit Card.

Removed during this cycle: Confirm Dialog (duplicated existing Modal patterns), Banner (duplicated Alert).

Notable fixes: Calendar event details drawer and highlight-today, Command Palette focus ring and restyle, Data Grid checkbox/search/layout, select dropdown positioning, Copy Button icon state, Context Menu/Dropmenu stacking, Scheduler appointment styling and slot sizing, Credit Card controls and gradients, modal default close button, secondary button style, code block padding/line-highlight.

Root composer.json's inter-package version constraints and replace block were already manually bumped to 5.1.0 on this branch (46962e0, 5ac8b0e) ahead of merging.

Test plan

  • vendor/bin/monorepo-builder validate passes on main after merge
  • CI green on this PR
  • Tag and release v5.1.0 from main once merged

mkocansey and others added 30 commits August 29, 2026 22:29
Checkboxes and radios now match the real Checkbox component's colour
and shape (the shape fix needed an explicit override since Tailwind's
Preflight reset beats the Forms plugin's zero-specificity :where()
rule, same conflict the real Radio component already works around
with rounded-full). The search field now reuses the actual .bw-input
class and icon-prefix pattern instead of a hand-tuned lookalike, so it
can't drift out of sync with Input again. Also fixes: header text
alignment losing to a more specific thead th rule, a doubled border
between the last visible row and the pagination footer on paginated
grids (tr:last-child was matching the true last row in the DOM, not
the last visible one), and a missing cursor:pointer on Clear
selection.
The docs site's hand-rolled search dialog (blurred backdrop, rounded-
3xl panel, softly-rounded result cards, and a proper icon+heading+
description empty state) looked noticeably more polished than the
actual Command Palette component it was meant to showcase. Brought
the component's own CSS in line: backdrop-filter blur, bigger panel
and item radii, a permanently-filled close button instead of hover-
only, and a richer empty state with an icon badge and supporting
description (new emptyDescription prop, backward compatible). The
loading state now shows a spinning icon rather than plain text, done
with a lightweight CSS animation instead of adding the Spinner
package as a new dependency.
positionItems() computed the dropdown's fixed left/top from the trigger's
viewport-relative getBoundingClientRect(), but an ancestor with transform,
filter, perspective, contain, or backdrop-filter (e.g. modal's
drop-shadow-2xl) becomes the containing block for position:fixed instead of
the viewport — so a select opened inside a modal landed away from its
trigger. Added a shared fixedPositioningOffset() helper that finds such an
ancestor and compensates.

The select trigger also carried bg-white, forcing a white box in light mode
regardless of what it sat on, unlike the input field which is transparent
by default. Switched to bg-transparent.

Separately, #eab011c added border-2 to .bw-input to fix faint borders but
never adjusted .form-label's top offsets for the extra 2px, so labels sat
visibly closer to the top border than centered. Added the 2px back across
all sizes and rebuilt the CSS bundle.
Today's date badge in month view and today's whole column in week/day
view were always tinted with no way to turn it off. Gate both behind a
new highlight-today prop (default false) in the PHP render and its JS
client-navigation mirror alike. aria-current="date" stays unconditional
either way, since that's a screen-reader signal, not a visual one.
Two bugs in month view's overflow toggle, reported against the docs site:

1. Clicking "Show less" hid every event in the cell, not just the
   overflow ones. The click handler's selector,
   [data-bw-calendar-overflow-event], matched the attribute's mere
   presence rather than its value, so it grabbed both
   data-bw-calendar-overflow-event="true" and ="false" markers alike.

2. Once expanded, every event row rendered at height: 0 with its text
   overlapping instead of stacking. .bw-calendar-event defaults to
   flex-shrink: 1 as a flex item of .bw-calendar-cell-events, so when the
   column ran out of room the browser crushed every row toward zero
   height instead of leaving them their natural size and letting the
   column's own overflow-y: auto scroll.

Verified the added Playwright assertions actually catch both regressions
by reverting each fix in turn and confirming the test fails, then
restoring and confirming it passes.
The previous fix (fa04f52) gave event rows flex-shrink: 0 so they keep
their real height instead of being crushed to zero -- correct, but it
left the "+N more" button (a different class, .bw-calendar-event-more,
not .bw-calendar-event) still shrinkable, and it turned out real,
unshrunk heights for max-events-per-day's default of 3 rows plus the
button don't quite fit the cell's existing budget. The button ended up
needing a scroll to see even before expanding anything -- not what a
first-time visitor expects from a plain "+N more" link, and not what the
README already promised ("its own small internal scrollbar once +N more
is expanded", i.e. only once expanded).

Reclaimed the ~12px shortfall from the row layout itself rather than
touching the calendar's overall fixed height, which already exactly
fills its 40rem budget for a 6-week month: dropped events'/the more
button's vertical padding in favour of line-height alone, tightened the
row gap, and gave the button the same flex-shrink: 0. Confirmed the
default collapsed state now fits with zero scroll needed, and that
expanding still needs to scroll exactly when it should (verified the new
assertion fails without this fix and passes with it).
Tailwind's own Preflight puts a default focus ring on every text-like
input via box-shadow -- a property outline:none and border:0 don't touch,
since it's a separate CSS declaration entirely. The ring only shows once
:focus-visible actually matches (real keyboard-context focus, not a
plain programmatic .focus() call), which is why it slipped through: any
test or manual check that opens the palette and calls .focus() directly
misses it, only a genuine click/keyboard-triggered focus reproduces it.

Fixed by explicitly setting box-shadow: none on .bw-command-palette-input.
Verified against a real click-triggered focus (checked :focus-visible
actually matched), and confirmed the new Playwright assertion fails
without the fix and passes with it.
# Conflicts:
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
Was dot+date/time eyebrow, then title, then description -- title now
comes first, followed by the dot+date line, then a separate time line
(only present for timed events; empty and hidden via CSS :empty for
all-day events), then description and link, unchanged.

Split calendarFormatEventDateTime into calendarFormatEventDate and
calendarFormatEventTime so the two lines can be populated independently.
* Reorder the event details drawer: title, then date, then time

Was dot+date/time eyebrow, then title, then description -- title now
comes first, followed by the dot+date line, then a separate time line
(only present for timed events; empty and hidden via CSS :empty for
all-day events), then description and link, unchanged.

Split calendarFormatEventDateTime into calendarFormatEventDate and
calendarFormatEventTime so the two lines can be populated independently.

* Add spinner overlay to data-grid loading state
hide-input replaces mask (kept as a deprecated alias) and now actually
works, since numeric mode was forcing every box back to type=number
regardless of mask. Also fixes boxes accepting more than one digit,
including the last box never releasing focus.
showDrawer() schedules its initial focus via requestAnimationFrame, which
can resolve after something else (a script, assistive tech, or a fast
Tab) already moved focus into the drawer, stealing it back to the first
focusable element. Skip the auto-focus when focus is already inside the
drawer by the time the frame runs.

Fixes the flaky "modal focus wraps and stacked drawers close from the
top" E2E test.
… !important

PackageIdentityTest was failing because "bladewindui/bladewindui" had been
dropped from composer.json's replace block. monorepo-builder's
UpdateReplaceReleaseWorker regenerates that block from packages/* directories
only, so it has no idea about the two manually-maintained former-name shim
entries — it silently dropped this one during the v4.5.0 release run and it
has been missing ever since. Re-added, at the same self.version marker as
everything else.

ConfigSurfaceTest was failing because code's new has_separator/hide_input
props (added for the OTP separator/masking feature) were never added to the
published packages/meta/config/bladewind.php, so a consumer had no way to
discover them without reading the Blade source.

CompiledCssTest was failing because .bw-data-grid-search-input used
!important to win its padding-left override over .bw-input's own px-3 —
exactly the pattern removed everywhere else in 4.4.0. Switched to a compound
selector (.bw-data-grid-search-input.bw-input) for real specificity instead.

Rebuilt the CSS bundle so the committed one matches, which the "Build CSS
bundle" check was also failing on.

Full local verification: vendor/bin/phpunit (555 passed), npm run test:js,
vendor/bin/monorepo-builder validate.
All three shipped as complete components in v5.0.0 but were never added
to the component tables here, so this README was already stale for the
components it claims to document. Also fixes the license badge, which
still pointed at the old bladewindui/bladewindui repo name.
…list item

Rejects Tree view, Fieldset, Masked input, Rich text editor, Segmented
control, Cascader, Mention input, Signature pad, Image cropper, Lightbox,
Guided tour, Split button, Speed dial, Gantt chart, and Org chart as
low-value for typical Laravel admin/SaaS apps. Adds Divider, Confirm
dialog, Password strength meter, Currency input, Document preview, and
Copy-to-clipboard button as candidates (tracked in GitHub issues #614-631).

Also adds a completion-checklist item requiring new components to document
Livewire wire:ignore guidance and fire native input/change events for
wire:model compatibility, following up from #632.
Dropmenu, Popover, Select, Sortable, Datepicker, and Filepicker
instantiated their JS widget in a script tag that wasn't guarded against
re-execution, so a Livewire re-render created a duplicate instance and
duplicate document-level listeners on every render. Datepicker was the
worst case, appending a brand-new calendar popup to document.body each
time. All six now guard on a data-bwInitialised flag, matching the
convention already used by Data Grid, Sidebar, and Stepper.

Sortable's guard was wrapped in a closure that broke its documented
"read the order yourself" API (bare `list_name.toArray()` from another
script tag), so its Sortable instance is now exposed via window instead
of a block-scoped const.

Added a setFieldValue() helper (core/helpers.js) that sets a field's
value and dispatches a native change/input event, and wired it into
every component that previously set .value directly with no dispatch:
Rating, Colorpicker, Timepicker, Slider, Sortable, Datepicker, and
Filepicker's base64 hidden inputs. wire:model now observes these without
extra wiring.

Updated the README for all 16 audited components with a Livewire section
covering the above where it applies, or wire:ignore guidance for the
components (Data Grid, Command Palette, Sidebar, Drawer, Tab, Stepper)
whose interaction state lives in the DOM rather than Livewire state.
Wraps the long intro paragraph and realigns the component tables to a
consistent column width; no content changes.
- modal: add dialog ARIA (role, aria-modal, aria-labelledby) and an
  accessible label on the icon-only close button
- card, statistic, avatar (+N more): clickable divs are now keyboard
  operable, via role/tabindex plus the existing bwActivateOnKey()
  delegation pattern rather than inline handlers (a repo-enforced CSP
  test forbids those)
- table, table-columns: onclick rows are keyboard operable via a new
  delegated data-bw-table-row-clickable listener
- avatar: the alt prop is now actually applied to the <img> tag
- popover: wire up aria-expanded/aria-haspopup/aria-controls and
  role="dialog", toggled by popover.js on show/hide
- tooltip: set aria-describedby on the trigger while the bubble is shown
- select: give the inline <style> block a CSP nonce; drop an untracked
  !important the compiled-CSS test couldn't see
- number, textarea (Quill toolbar): route value changes through the
  existing setFieldValue() helper so wire:model observes them, and add
  a bwInitialised re-render guard matching the #632 pattern; the same
  fix applied to checkMinMax()'s clamp path in helpers.js since input
  shares it
- tooltip, calendar, data-grid, command-palette, sidebar, drawer:
  replace hardcoded dark-mode hex colors with the shared
  --color-dark-* tokens
- add missing test coverage for colorpicker, horizontal-line-graph,
  listview, popover, spinner, theme-switcher, timeline, timepicker,
  and tooltip (59 new tests)
New leaf package bladewind-divider (Content group): horizontal or
vertical rule with an optional centered label, none/small/medium/large
spacing, a colour override, and decorative vs semantic (role=separator)
modes. Closes #626.
New leaf package bladewind-confirm-dialog (Content group), composing
Modal rather than duplicating it: a danger/warning/info/primary tone
picks the icon and confirm button colour, the backdrop cannot dismiss
it by default, and an onConfirm JS expression can return a promise —
while it is pending both buttons disable and the confirm button shows
a spinner (runBwConfirmDialogAction in helpers.js), and a rejection
re-enables them and leaves the dialog open. Closes #627.
New leaf package bladewind-context-menu (Navigation group): gives any
region a right-click (or keyboard context-menu key) action menu, with
icons, disabled items, separators, a danger tone, and arbitrarily
nested submenus. Placement is viewport-aware for both the top-level
menu and every submenu, flipping away from whichever edge it would
otherwise overflow.

A dedicated context-menu.js drives positioning, roving-tabindex
keyboard navigation (arrows, Home/End, Enter/Space, Escape), and
submenu open/close on hover, click, or the arrow keys — separate from
Dropmenu's JS since it anchors to a pointer position rather than a
trigger element and has no trigger of its own. Closes #614.
New leaf package bladewind-currency-input (Forms group), wrapping
Input's existing money mask rather than duplicating it: derives the
currency symbol, its position, and the decimal/thousands separators
from a currency code and locale via PHP's intl extension when it's
installed, falling back to a small built-in symbol table and
locale-independent defaults ('.', ',', 2 decimal places, 0 for
zero-decimal currencies like JPY) when it isn't. Every derived value
can be overridden individually. Closes #629.
New leaf package bladewind-password-meter (Forms group): a standalone
companion that watches an existing password field by name/id (for
prop) rather than wrapping it, so it works with any password field,
BladewindUI's own or otherwise. Scores 0-4 on keystroke — up to two
points for length (min_length, strong_length thresholds) and up to
two for character variety (lower/upper/digit/symbol) — and reflects
that as a 4-segment colour bar plus an optional translated label.

Also fixes a pre-existing drift between the maintained lang/en source
and the packages/core/lang/en copy Laravel actually loads at runtime
(loadTranslationsFrom points at packages/core/lang) — "close" had
been added to the former but never synced to the latter. Closes #628.
New leaf package bladewind-file-preview (Forms group): the read-only
display counterpart to Filepicker. Shows a thumbnail or an
extension-derived file-type icon, the filename, a human-formatted
size (bytes in, B/KB/MB/GB out), and download/remove actions. The
remove control defaults to removing itself from the DOM (delegated
via a new [data-bw-file-preview-remove] listener in helpers.js,
mirroring Tag's dismiss-button pattern) and is fully overridable with
onRemove for server-backed removal. Closes #630.
New leaf package bladewind-transfer-list (Forms group): two panels,
"Available" and "Selected", with per-panel search, select-all, arrow
buttons to move checked or all items, and a double-click shortcut to
move a single row. Submits as a normal name[] array field — one
hidden input per item, enabled only while it sits in the "Selected"
panel, toggled in place rather than added/removed from the DOM.

items accepts an array or JSON string of {value, label} objects, with
valueKey/labelKey overrides matching Select's own convention.
Closes #615.
# Conflicts:
#	README.md
#	packages/content/composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
# Conflicts:
#	COMPONENT_BACKLOG.md
#	README.md
#	packages/content/composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
# Conflicts:
#	COMPONENT_BACKLOG.md
#	packages/content/composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
# Conflicts:
#	README.md
#	composer.json
#	packages/content/composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
#	packages/meta/config/bladewind.php
# Conflicts:
#	README.md
#	composer.json
#	packages/content/composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
#	packages/meta/config/bladewind.php
# Conflicts:
#	COMPONENT_BACKLOG.md
#	README.md
#	composer.json
#	packages/content/composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
#	packages/meta/config/bladewind.php
… section

Left behind by the feature/scheduler merge conflict resolution.
# Conflicts:
#	composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
#	packages/forms/composer.json
#	packages/meta/config/bladewind.php
Context Menu, Divider, Confirm Dialog, Transfer List, Inline Edit,
Password Meter, Currency Input, File Preview, Carousel, Banner, Code
Block, Kbd, Description List, Chat, Meter, Copy Button, Kanban, and
Scheduler are all merged into development.
Their items-list wrapper carries permanent animate.css classes
(animation-fill-mode: both never gets cleaned up), which browsers
treat as an ongoing opacity animation and promote to its own stacking
context with an implicit z-index of auto. That trapped the intended
z-[9999] on the fixed/absolute items list inside a context that could
lose a same-tier stacking tie to whatever comes later in the DOM, e.g.
a code block below the demo on a docs page.

Giving the wrapper itself an explicit position and z-index keeps it
(and any nested submenu) at the intended stacking priority regardless
of what surrounds it.
Combining 'hidden' with 'inline-flex' on the same element is unreliable:
whichever utility's rule comes later in the compiled CSS wins the cascade,
not the more specific-looking class. inline-flex was emitted after hidden,
so the JS toggle never actually hid either icon.
Rounded corners (0.25rem, was 0.375rem) and no border, matching the
solid-tinted look of calendar's event pills. Keeps scheduler's own open
color palette (any Tailwind color name) rather than calendar's fixed
info/success/warning/danger set.
The topmost hour label was centered exactly on the header/body boundary
line like every other hour label, but unlike interior labels it has no
row above it to share that space with, so it read as sitting right on
the line. It now sits just inside its own row instead.

Also raise the minimum event height from 18px to 32px: a 30-minute
appointment at the default 30-minute grid was rendering at exactly one
slot (24px), too short to fit both the label and time-range lines
without clipping.
Every hour label centers on its own gridline via a -8px offset, which
looks fine at interior boundaries where the previous row's whitespace
absorbs it, but the first hour had nothing above it to absorb into: it
sat flush against the header border while the last hour's label kept
the full row's worth of natural trailing space below it. Added a
one-row spacer above the grid, sized to match that trailing space, so
the gap before the first label now matches the gap after the last one.
The earlier 32px minimum height fix for text clipping grew a 30-minute
appointment's box beyond its real end time, eating into the following
slot's space and leaving no visible room for a legitimately adjacent
appointment to render. Now the box always matches its actual duration
(floored only to 16px so it stays clickable); when that's too short
for both the label and time-range lines, the time line is dropped and
moved into a title tooltip instead of being clipped or shown at all.
Prism's own stylesheet sets padding: 1em on <pre>, which read as
excessive vertical whitespace around short snippets. Override just the
vertical component (!py-2, matching how !m-0 and !rounded-none already
beat Prism's rules here) and leave the 1em horizontal padding as-is.
Prism's .line-highlight hardcodes margin-top: 1em to exactly cancel out
its own default pre[data-line] padding: 1em. Our earlier !py-1 override
changed that padding to 4px without updating the other half of that
pairing, so every highlighted line rendered 10px below the line it was
meant to mark. Its background is also a warm, low-opacity tint tuned
for Prism's light theme and was nearly invisible against our dark
background. Both are now overridden together, scoped to this component.
…let listview items forward attributes

Context menu:
- Add a target prop (CSS selector or bare element id) so the menu can
  bind to an element elsewhere on the page instead of requiring the
  trigger to be wrapped in the region slot, useful for menus scoped to
  a whole page or an element you don't want to restructure.
- Drop the outer wrapper's hardcoded inline-block, which capped a
  block/w-full region's width to its own shrink-to-fit size instead of
  the actual container.
- Swap focus: for focus-visible: on items so the first item doesn't
  look pre-highlighted the instant the menu opens (a mouse action),
  while keyboard navigation still highlights the focused item.
- Fix danger tone: the icon's color was hardcoded regardless of tone,
  so only the label text turned red, not the icon.

Listview:
- Forward extra attributes (id, data-*, wire:key, ...) onto the <li>
  via $attributes->merge, needed to target individual list rows with
  the context menu's new target attribute, and generally useful on its
  own.
The document-level data-bw-modal-close delegation (added for strict CSP
support, #608) never fired because clicks inside the modal box were
swallowed with stopImmediatePropagation before they could bubble to the
document listener. Every modal using the default close action for its
Cancel or OK button was unclosable by that button.

Replace the propagation-swallowing with a target check on the backdrop
listener so it only closes on clicks that actually land on the backdrop,
letting clicks elsewhere bubble normally.
# Conflicts:
#	README.md
#	composer.json
#	packages/calendar/resources/assets/css/calendar.css
#	packages/command-palette/resources/assets/css/command-palette.css
#	packages/content/composer.json
#	packages/core/public/css/bladewind-ui-no-preflight.min.css
#	packages/core/public/css/bladewind-ui.min.css
#	packages/data-grid/resources/assets/css/data-grid.css
#	packages/forms/composer.json
#	packages/meta/config/bladewind.php
#	packages/navigation/composer.json
…k, stale line-highlight test assertion

CurrencyInputTest expected GH₵ for GHS, but with ext-intl installed
(as CI has, unlike this sandbox) ICU's NumberFormatter has no real
glyph for GHS in en-US and just echoes the currency code back as the
"symbol". The component accepted that as a real resolved symbol,
skipping past its own curated fallback map that already had the
correct GH₵ entry. Only accept ICU's symbol when it actually differs
from the currency code.

CodeBlockTest still asserted the old margin-top: 0.25rem value for
Prism's line-highlight override; the source moved to 1px in 074449b/
868953c but the test wasn't updated then.
@mkocansey
mkocansey merged commit f435e27 into main Sep 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant