Changelog
Source:NEWS.md
blockr.ui (development version)
A
Blockr.menu()can carry a tool,config.tool({ icon, label, onSelect }), at the end of its caption line in the top right corner: an icon button named by its tooltip that opens the menu’s list somewhere fuller (#94). Tab moves to it from the filter box, and a click closes the menu and handsonSelectwhat was typed in the filter box. The menu for the dock’s block actions uses it to open the block browser in the sidebar, with the newopenicon, a box with an arrow out of its corner. The spec’s “Picking a block” now keeps the browser and its form for a block’s and a link’s IDs, which cannot be changed after adding.The new token
--blockr-logois the blockr logo’s green,#089e76, which the design spec now names as the third colour allowed outside the palette (#95). Like the block category colours, it is the same under every theme and in the dark scheme, and it clears 3:1 on the surface in both.The new
Blockr.menu.delegate(selector, config)opens and closes the menu of every trigger that matchesselector, including triggers added to the page later, such as the “…” buttons on the dock’s block cards (#79). A click and the down arrow act as on a trigger wired withBlockr.menu.bind(), so a menu opened from the keyboard starts on its first row. A functionconfigis read on each open and is given the trigger. Nothing marks a trigger before its first open, so its markup carriesaria-haspopup="menu"andaria-expanded="false".The new
base_dep()is the stylesheet for a page that does not load Bootstrap (#85): a reset of the browser’s defaults, Open Sans (bundled: latin, latin-ext, greek, and math for operators such as ≤, ≥ and ≠), and the look of the markup Shiny’s own inputs emit (form-group,form-control,checkbox,btn). It bringstheme_dep()along, whose--bs-btn-*values its buttons read, so they look the same with or without Bootstrap.The new
dropdown()builds a panel that opens under its toggle and stays open while it is worked in (a search field, a list edited in place), as the navbar’s menus need, which are Bootstrap dropdowns today. Its script,Blockr.dropdownincontrols_dep(), moves the open panel to the page body and places it as a menu is placed, so no panel’s overflow clips it.The theme layer draws
.btn-outline-primaryas the main kind and.btn-outline-dangeras the destructive one, so they look the same with or without Bootstrap. With Bootstrap, the Download and Delete buttons of blockr.session’s workflow manager lose its blue and red outlines, and blockr.dock’s “Add panel” turns from secondary to main.The words beside a radio button are no longer drawn as a field’s label, 12px and muted, but as text, as a checkbox’s are.
The theme layer,
theme_dep(), draws Shiny’scheckboxInput()andcheckboxGroupInput(), inline or not, and bslib’sinput_switch()as the design system’s checkbox, so R code keeps calling them and on/off looks the same everywhere.Rows in a menu of actions (
Blockr.menu,action_menu()) take radius 4, like the rows of a Select list, instead of 6.A new layer,
--blockr-z-overlay(1000, Bootstrap’s$zindex-dropdown), for an overlay that belongs to the content but is fixed on<body>, such as a drag ghost or a drop line: above the content, under the navbar and everything that floats.The small icons gain
"eye","dots","minus","info","warning","maximize"and"restore", the icons blockr.dock and blockr.extra still took from Font Awesome, throughshiny::icon()or dockViewR’s defaults (#85).The new
shiny_input_batch_dep()dependency skips the empty input messages Shiny has sent after every deferred input since 1.7.5: its batcher never records that a send is queued, so each deferredsetInputqueues one, and all but the first go out empty. The server runs a full input cycle for each, and on a dock board the dock’s own messages queue behind them. Attach it once at the page level. Reported upstream as rstudio/shiny#4436.The small icons ship as files, one SVG per icon in
assets/icons, whichsmall_icon()andBlockr.iconsare built from (#80). JavaScript that runs without R, such as a package’s test harness, reads them there: an icon is its file without its comment, which carries the icon’s note, and without the whitespace between tags.The block’s mark is drawn here, in the design system’s four sizes (#65). In R,
block_mark()builds it from a block’s glyph and category as the.blockr-block-markclass: 24px, or 32, 20 and 16px through its--32,--20and--16modifiers, with the glyph half the mark plus 2px and the corners a quarter of it. For a place that needs an image, such as the DAG’s canvas,block_mark_svg()draws the same mark as an SVG or itsdata:URI. The category colours are tokens,--blockr-category-<category>, with the spec’s fixed Okabe-Ito values, and blockr.ui owns them in place of blockr.dock’sblk_color(). In R,category_color()reads them. ABlockr.menurow’smarktakes the block’scategoryand draws the same mark at 24px, in place of the menu’s own.blockr-menu__markclass and its--blockr-menu-markproperty.The new
small_icon()draws the design system’s small icons in markup built in R (#64), from the listBlockr.iconsis now built from:controls_dep()writes the list into the page ahead ofblockr-ui.js, so R and JavaScript draw one copy of each. The icons are no longer part ofblockr-ui.js, so a page that loads that file on its own, as a test harness might, has none. Every icon is hidden from screen readers, and the plus is a 1.25px stroke now, as blockr.dock’s views menu draws it, in place of Bootstrap’s filled path. The set gainsgrip, the drag handle of that menu’s page rows.The warning border,
--blockr-color-border-warning, is amber-600 (#d97706) in the light scheme, a new step of the amber ramp, up from amber-500: a warning border, and a status dot drawn in one, now clears 3:1 on white (WCAG 1.4.11). A test holds icons and status borders to 3:1 on the surface in both schemes.Escape and a click outside go through one dismiss stack,
Blockr.layer()(#49). Every control that opens something registers it as a layer: the Select’s list and its expanded tags, the code field’s completions, both menus of actions, the tooltip, a dirtyBlockr.textCommit()field and the gear tray. Escape closes the top layer only, and a click, read on pointerdown, closes the layers above the one it lands in, so a Select in the gear tray or a menu over a modal closes alone. While a tooltip shows, the next Escape hides it and leaves what is under it open. TheBlockr.onDocClick()registry is gone; a package’s own floating UI registers withBlockr.layer()instead.The design system’s menu of actions,
Blockr.menu(), joinscontrols_dep(): rows with an icon, label and meta text, dividers, group titles, a head (a name with a badge and a line of text), current, disabled and destructive rows, placed withBlockr.place()and driven from the keyboard. A trigger is wired withBlockr.menu.bind(). Its classes also style menus built elsewhere.A
Blockr.tooltipline can carry abadge.The new
action_menu()builds a menu of actions opened by a button: downloads, Rename, Remove. Its rows aremenu_item()s wrapping adownloadLink(), anactionLink()or any link or button, withmenu_section()titles andmenu_divider()rules between them. A row does one thing and the menu closes; unlikeBlockr.Select.menu()it sets no value. Open, the list sits on the page body, so no panel’s overflow clips it.The new
tool_button()is the design system’s 26px icon button, named by a tooltip.The new
gear_tray()withtray_section()draws the gear and its tray for a block whose UI is written in R. The tray lays out Shiny’s own inputs at the design system’s sizes.The dropdown of a selectize input, as
selectInput()draws it, is a layer on the dismiss stack while it is open, so Escape closes it before the gear tray, modal or panel around it.An element built in R can carry its tooltip as a
data-blockr-tooltipattribute, and a badge after the name asdata-blockr-tooltip-badge(#63);Blockr.tooltipshows it as the light card, in place of the nativetitlebox.Text marked
data-blockr-editableshows the text cursor and a tooltip naming the gesture, “Double-click to edit” unless the attribute names another.The new
shortcut()writes a keyboard hint once for every platform:shortcut("Mod+Shift+S")reads ⌘⇧S on a Mac and Ctrl+Shift+S elsewhere. It fits the meta slot of amenu_item().The table preview draws its header in the table style: the sort cue sits on the name’s line, a numeric column’s header sits over its numbers, and a sorted header states its order in a tooltip and in
aria-sort. A label or value cut off by its column shows whole in the light card rather than a nativetitle.With
theme_dep()in the page, Bootstrap’s buttons take the design system’s kinds:.btn-primaryis the main button,.btn-default(Shiny’sactionButton()anddownloadButton()),.btn-secondaryand.btn-lightthe secondary one,.btn-linkthe quiet one and.btn-dangerthe destructive one. Markup built for blockr uses the.blockr-btnclasses, in three sizes.The design tokens are rewritten around the vocabulary of the design spec: a colour palette, meaning tokens for text, backgrounds, borders and status, type, radii, control heights and shadows. Every name defined before keeps its old value.
The tokens include a stacking scale,
--blockr-z-stickyup to--blockr-z-toast, at Bootstrap’s own z-index values. Anything that floats over the page takes one of these layers; inside a component, a z-index only orders siblings, from -1 to 3.The
theme_dep()dependency also attachesblockr-tokens-dark.css, which restates the tokens underdata-bs-theme="dark", the attribute that blockr.core’s dark-mode board option sets. In dark, Bootstrap’s body background and text colour follow the tokens too.The
theme_dep()function returns two dependencies in atagList(), the tokens and the theme layer, so a component can carry the tokens without restyling the rest of the page.The
controls_dep()dependency carries the controls blockr blocks are built from, moved here from blockr.dplyr:Blockr.Select, the Enter button, the required-empty cue, the checkbox, the segmented control, the gear tray andBlockr.place, with the stylesheets they draw with. It brings the tokens but not the theme layer, which an app attaches withtheme_dep(). The dependency names are the ones blockr.dplyr used, so a page carries one copy of each; while blockr.dplyr still ships its own copies, that copy is blockr.dplyr’s, as htmltools keeps the higher version of a name.controls_dep()also carriesBlockr.Input, the code field with column and function completions, moved here from blockr.dplyr under its old dependency names (blockr-input-js,blockr-input-css).
blockr.ui 0.0.1
The
shiny_has_perf_dep()dependency strips a redundant:has(> *)guard out of the rule Shiny uses to fade a pass-throughuiOutput()while it recalculates. That guard sits in non-subject position with a universal subject, which makes Chrome restyle the whole document on every DOM insertion: 107ms per insertion on a 40-block dock board against 3ms without it, growing linearly with element count, and paid by every block re-render and every keystroke in a picker. Removing it is provably behaviour-neutral, since reaching the subject already proves the guarded container has an element child, so both thedisplay: contentspass-through and the fade survive. Attach it once at the page level.One
var()fallback had drifted from the token it backs: the.blockr-type-labelrule wrote#b0b7c3where--blockr-color-text-subtleresolves to#9ca3af, which the two other readers of that token in the same file already spelled correctly. It rendered the wrong grey in any host that does not attachtheme_dep().The block browser, sidebar, link menu and stack menu are gone, along with
is_hex_color()– 19 of the 28 exports. Package blockr.dock vendored a copy of all four in July 2025 and has developed on them since (a link edit form, a native colour input, reworked panel ownership, and a fifthsidebar-inputsmember that never existed here), while these copies had not been touched since June. Nothing outside this package used them, and both copies styled the same class names, so whichever stylesheet loaded last decided the result. Coverage of the surviving copy’s client behaviour was extended first, in BristolMyersSquibb/blockr.dock#440.The
Suggestson blockr.dock goes with them, removing the last non-CRAN entry fromRemotes:and simplifying a CRAN release.The
theme_dep()dependency carries the shared blockr stylesheet: the--blockr-*design tokens in a:rootblock, and the unscoped Bootstrap theme layer - typography, labels, form controls, selectize, buttons, tooltips, popovers and the DataTables chrome - that blockr apps have so far picked up fromblockr.dock. A host attaches it once from its own UI. Nothing in this package attaches it for you, and the token block on its own styles nothing.html_table_displaywires the HTML table preview into blockr.core’stabular_displayseam (blockr.core >= 0.1.4). Apps opt in withoptions(blockr.tabular_display = blockr.ui::html_table_display)to preview data, parser and transform block results through the paginated, sortable HTML table rather than the default minimal preview. This supersedes the never-readblockr.html_table_previewoption.link_menu_server()and the block browser now resolve a variadic target’s new-link input to an empty (positional) slot rather than a generated integer name ("1","2", …). This aligns with blockr.core’s name-or-position variadic input model, where an integer input is a named argument - so the old convention quietly named what should be positional.sidebar_ui()panels now re-bind their body inputs/outputs on open (Shiny.bindAll).hidePanelunbinds on close, so a pre-rendered panel opened viashow_sidebar(id)with noui(no body swap) stayed unbound after its first close and silently stopped emitting. This broke the add / append block browser, which could only commit once.block_browser_server(id, board, target)now returns a ready-to-apply value instead of a raw spec: ablockr.coreblocksobject for the add flow, orlist(blocks, links)for append / prepend with the link’s input port resolved menu-side. It builds the block and validates the committed ids when given aboardreactive, matchinglink_menu_server()/stack_menu_server().block_browser_ui()no longer bakes board-seeded default ids into the markup, so the add-flow panel is independent of board state and can be pre-rendered once and opened without re-rendering.append_to()/prepend_to()now accept aNULLblock id (a source-less descriptor), so an append / prepend panel can likewise be pre-rendered once with the source / target supplied server-side at commit. (Breaking change for the committed-value shape.)New
link_menu_ui()/link_menu_server()/link_menu_dep()module: a bidirectional card-list link picker. Cards represent both OUTGOING (“CONNECT TO”) and INCOMING (“CONNECT FROM”) candidates for a fixedanchorblock, gated by the anchor’s free-input capacity. Single-shot click-to-add, per-card chevron-revealed advanced form (link_id+block_inputwhen the target end has arity > 1). The binding’sreceiveMessageaccepts apool-updatepayload so consumers can keep the menu open across multiple link commits in a session; just-wired cards drop client-side without re-rendering.link_eligible_pools(board, anchor)is exported so consumers recompute the post-commit pool against the same eligibility logic the menu uses for its initial render.link_menu_server()gainsboard(reactive) andanchorarguments: it owns link-id validation (viablockr.core::notify()) and, when passed a board reactive, keeps an open menu in sync with the board via amenu:syncdiff that supersedespool-update- it can now also add a card that became eligible (e.g. after a link / block was removed elsewhere), not just hide ones already rendered, all without re-rendering.New
stack_menu_ui()/stack_menu_server()/stack_menu_dep()module: a multi-select card-list block picker for stacks, with an inline hue / lightness slider + hex colour picker and a panel-level form for the stack name / color / id.target = NULLis the create flow;target = "<stack_id>"selects the edit flow. Mirrorsblock_browser_ui()’stargetargument shape.stack_menu_server()gainsboard(reactive) andtargetarguments: it now owns validation of the committed spec (id / name / colour, viablockr.core::notify()) and, when passed a board reactive, keeps an open menu in sync with the board - cards are added / removed live via amenu:syncdiff with no re-render, so scroll, selection, and in-progress inputs are preserved. The committed reactive now returns ablockr.corestacksobject (one id-keyed stack built vianew_stack(), colour carried as an attribute) rather than a raw list, so a consumer applies it without reshaping.New exported
is_hex_color()helper (#rgb/#rrggbb) so consumers validate colours against the same rule the stack menu uses.Initial package scaffold.