Rundock.ui.<component>(options), gets an element back, and puts it on the page. Buttons, fields, tabs, tables, boards and the rest are Rundock’s; what you draw inside a canvas is yours.
Rundock puts the library into every extension view, with its stylesheet and the colours of the theme showing, before your script runs. You never bundle a copy, so your view always matches the Rundock it is running in. When Rundock restyles a button, your buttons change with it, and you publish nothing.
See every component
A running Rundock shows every component in every state, in both themes, at/rundock-ui/gallery. In browser mode that is:
Use it
The library is onwindow.Rundock.ui before your entry script starts, so you can call it straight away:
- Every factory takes one options object and returns an element. You compose them like any other DOM node.
- Labels are always text. A label containing
<b>shows<b>; nothing you pass is parsed as markup. - A wrong option is refused by name. An unknown
variantortone, or a callback that is not a function, throws aTypeErrornaming the component and the option, for exampleRundock.ui.button: variant must be one of primary, secondary, danger, danger-confirm. An uncaught error ends your view and names the reason, so you find out while building. - Your styles win. Components are styled by classes that start with
rui-, and your own stylesheets are inlined after Rundock UI’s, so you can override any of them. Use Rundock UI for controls and containers, and keep your own CSS to layout.
Components
Twenty-two components, from twenty-three factories: an empty state and a loading placeholder are one component with a factory each. Every one is shown, in every state, in the gallery.Rundock.ui.version says which version of the library is running.
The full options, defaults and keyboard behaviour of every component are in docs/RUNDOCK-UI.md in the Rundock repository.
Versions
Rundock.ui.version is a string, "MAJOR.MINOR". This release of Rundock provides "1.0".
- A minor version adds. A new component, a new option or a new state.
- A major version changes what an existing call does or accepts.
- A change of look is neither. Rundock puts the library into your view itself, so a restyle reaches every extension at once and asks nothing of you.
extension.rundockUi:
X.Y installs on a Rundock that provides X.Z, where Z is Y or greater, and on nothing else. An older minor might lack something you call, and another major might answer a call differently. Rundock refuses any other install and says why, for example: “the extension was built against Rundock UI 1.3, newer than the 1.0 this Rundock provides: update Rundock to install it.”
rundockUi is optional. An extension that declares nothing installs as before and still gets the library, with no check. A value that is not MAJOR.MINOR, such as "1" or " 1.0", is refused.
To use a newer component where it exists and fall back where it does not, test for it: typeof Rundock.ui.meter === 'function', or read Rundock.ui.version.
Tables
table takes columns, an array of { key, label, numeric, align, render, format, width, minWidth, widest, grow, resizable, edit }, and rows, an array of objects. A numeric column is right-aligned with tabular figures. caption is read by assistive technology and not shown. There is no sorting in this version.
Widths
A table measures each column once, the first time it is laid out: its natural width is its header and its widest value on one line. It then fixes the widths, so editing a cell never shifts a column.- Every column takes its natural width, except text wider than 280px, which is capped there. A value cut off by the cap ends in an ellipsis, with its whole text as the cell’s title. A numeric column, a checkbox or a row menu is never capped, and no column is narrower than its header.
- The main column takes the spare room. When the table is wider than its columns, the widest text column takes all the spare room and every other column keeps its natural width, so a short column stays tight and a row menu sits at the right edge. A numeric column, a control such as a checkbox or a row menu, and a column with a fixed
widthare never chosen. grow: truechooses the main column yourself, such as a notes column, instead of leaving it to the widest.- When no column can take it, for example when every column is fixed, numeric or a control, or the main column has been resized, the rest goes to an empty filler column after the last, so rows still run the full width. The filler has no header, is hidden from assistive technology and is never focusable.
- A table wider than its container keeps its widths and scrolls sideways inside its own wrapper, never the page.
widest: text or an element, or an array of them, measured with the cells. minWidth sets a floor in pixels. width fixes a column, as pixels or a CSS length such as "96px", "20%" or "12ch", and always wins. When the container’s width changes, the spare room is given out again, but never while a cell is being edited.
Resizing
resizable: true on the table gives every column a resize handle on its header’s right edge, and resizable on a column turns it on or off for that column alone. A handle is a focusable separator named for its column, such as “Resize the Quantity column”. Drag it, or focus it and press the arrow keys, 8px a step or 32px with Shift. Enter or a double-click puts the column back to the default rule. A column never goes below its header or its minWidth.
onResize({ key, width }) reports each resize, and width: null after a reset. A view that keeps widths passes each kept width back as that column’s width when it redraws the table. Never write widths into the person’s notes: a width is a view preference, not their data.
Kept widths. stateKey: '<name>' on a resizable table keeps its widths across a reload and a return to the note, with no code of yours: the table reads and writes them in the view’s own state, under the key rui.table.<name>. A kept width wins over the default rule until the person resets that column, and never falls below the column’s header or minWidth. stateKey is 1 to 54 letters, digits, ., _, : or -; give each table in a view its own. Where there is no view state, such as the gallery, a table with stateKey behaves exactly as one without it. Like all view state, kept widths are per machine, and a renamed or moved note starts without them.
Format
format(value, row) is what a cell shows, as text or an element. A column takes format or render, not both. An editable column must use format, because the table redraws the cell from its value after an edit.
Editing
A column withedit can be changed in place; one without is read-only.
edit.typeisnumber(the default),text,selectorcheckbox.minandmaxbound a number.options, required forselect, are strings or{ value, label }.label(row)names the row for assistive technology.when(row)returning false leaves that one cell read-only.- Keys. Click, tap, Enter or F2 opens the editor over the cell. Enter commits and Escape cancels. Tab commits and opens the next editable cell, Shift+Tab the one before. Leaving the editor commits. There is no arrow-key movement between cells.
- A number is digits with an optional
-and one., with,read as grouping. Anything else, or a number outsideminandmax, is refused by the table with the range in words, such as “Enter a number of 0 or more.”, and never reachesonEdit. An unchanged value closes without asking. onEdit({ row, key, value, previous })decides. Returntrueto accept. Return a message to refuse: the value reverts, and the message shows on a line of its own directly under the row, spanning the table and naming the column, such as “Quantity: More than this account holds.”, and focus stays in the editor. Return a promise to hold the cell while you save: the cell shows “Saving…” until the promise settles astrueor a message. Anything else, a throw or a promise that fails, refuses with “This change was not saved.”, so the table never shows a value you did not confirm.- A checkbox shows as a checkbox all the time and toggles in place with one click or Space, asking
onEditwithtrueorfalse. A refusal unticks it again. - The table never writes anything, neither the file nor your
rows. Updating your own data, and anything derived from it, is yours to do inonEdit. With noonEdit, nothing can be edited: every cell reads as text, and a checkbox is shown disabled.
View state
Rundock.viewState keeps your view’s own preferences for the note it is showing, such as a chosen tab or the widths someone dragged, so you never write them into the person’s note.
get(key)returns the value kept underkey, orundefined. It works from your entry’s first line: the state is in the frame before your script runs.set(key, value)changes the view’s copy at once and asks Rundock to keep the whole state.set(key, undefined)removes the entry.- A key is 1 to 64 letters, digits,
.,_,:or-. Any other key throws aTypeErrorand nothing is sent. Keys startingrui.belong to Rundock UI, so do not use that prefix for your own. - The limits are those of a view’s own state: plain JSON, 64 KB per note, kept per machine, and refused from an embedded view. A refusal arrives as
refusedwithof: 'setState', and your copy stays as you set it, so the view keeps working for the session. See A view’s own state.
Rundock.viewState.
Where a view sits: full bleed
A view opened on its own is the pane, not a box inside it. The frame fills the file pane edge to edge and top to bottom, and paints the pane’s own colour in both themes, with no border or rounded corners. Your page’sbody carries the same padding a note has, so your view’s first line starts exactly where a note’s first line of text does.
Do not add an outer padding or a background of your own. Rundock has already placed your view; extra padding makes it look inset.
There are two variations, each a class on your body:
-
An edge-to-edge canvas. Add
rundock-full-bleedto the body, and the padding goes to zero:Settingbody { padding: ... }in your own stylesheet works too, since it comes after Rundock’s. -
Embedded in another note. When your view is shown inside another note, Rundock adds
rundock-embeddedto the body. The panel around it already pads it, so the body has no padding and paints the panel’s colour. You need do nothing, but your view should read well at a third of the pane’s width. See Show up inside other notes.
What the library can and cannot do
Rundock UI runs inside your view’s frame, as your code does, with no more reach than your code has. It builds elements and listens to them. It sends no message to Rundock, reads nothing outside the frame and fetches nothing. The frame’s sandbox, its content security policy and the closed list of messages in the extension host contract are exactly the same with it as without it. In the light theme, yourbody also carries the class light, the same class Rundock’s own page carries, so a rule written body.light ... behaves in your view as it does in Rundock. The theme in init says the same thing.
When the theme changes while your view is open, a view whose ready says handles: ['theme'] is restyled in place: every component follows, and the view keeps its state, including edits not yet saved. A view that does not say so is rebuilt and starts again from the file. A view that edits should say it. See Theme changes.
Design rules the components follow
These are the decisions behind the look. If you draw your own content in acanvas, follow them too and it will sit comfortably beside the components.
- Two oranges, each for one kind of shape. Action orange,
--accent-action, fills a shape that carries white text or an icon: the primary button, the accent chip and the send button. Control orange,--accent-control, fills a bare shape: a checked checkbox, a toggle that is on, a slider’s fill and thumb, the selected option’s dot and a meter’s fill. Text on a fill needs more contrast than a shape against its background does, which is why there are two. - The brand orange,
--accent, is never a fill. It is for outlines, focus rings, hover and drag highlights, and links. - Danger differs by form as well as colour. Action orange and red are too close in brightness to tell apart by colour alone. A button that starts a destructive action is a red outline; only the final destructive action is solid red.
- Status is a solid fill with dark or white text, never coloured text on a pale tint of the same colour, which is hard to read in the light theme.
- A control’s edge is
--text-3, which stands out clearly against the surfaces controls sit on.--borderis for decorative dividers only. - Named, not only coloured. Every state a component shows by colour is also carried by a word, a role, a shape or a mark.
- Motion stops when reduced motion is on, and everything that waits breathes on one two-second rhythm.
Where to next
Build an extension
A complete extension built on Rundock UI.
What an extension cannot do
The sandbox your view runs in.