Skip to content

v3.3.0

Choose a tag to compare

@lehni lehni released this 09 Oct 21:04
· 596 commits to main since this release

Changes since v3.2.7. No public API was removed: these are fixes, security hardening and accessibility work, with a few behaviour changes that apps may need to act on, listed under Upgrade notes.

Known issue: with Objection.js 3.2.0, queries that mix withGraphJoined() and withGraphFetched() fail. This is fixed in v3.3.1, which requires Objection.js 3.3.0. Please upgrade to v3.3.1.

Upgrade notes

Dependencies

  • @floating-ui/dom is a new peer dependency of @ditojs/admin (a
    required peer of @tiptap/vue-3). Add it to the app:
    pnpm add @floating-ui/dom.
  • type-fest is now a regular dependency of @ditojs/admin, as only its
    type declarations use it. Apps that only installed it to satisfy the peer
    can remove it.
  • @tiptap/extension-text-style is no longer a peer of the admin; remove it
    unless the app uses it itself.
  • vue is now a peer dependency of @ditojs/ui, so its dist no longer
    bundles a second Vue runtime. Apps already provide vue; nothing to do
    unless @ditojs/ui is used without it.
  • The admin's UMD / require() build now exposes { DitoAdmin, default }
    instead of the class itself; read .DitoAdmin or .default from it. ESM
    imports are unchanged, and import { DitoAdmin } now works too.
  • Library builds of @ditojs/build keep subpaths of dependencies external
    (e.g. @tiptap/pm/*, @vueuse/integrations/*); apps must provide these
    packages instead of relying on inlined copies. CSS subpaths are still
    bundled.

Admin: notifications, labels and dialogs

  • notify() escapes text and title. Markup in text now shows as
    literal tags (a dev warning points to it). Pass formatted text through the
    new html option instead, and escape any data in it with escapeHtml():
    notify({ type: 'info', html: '<b>Saved</b> ' + escapeHtml(name) }).
    text and html are mutually exclusive in the types.
  • Item labels escape item data. getItemLabel() escapes the item's
    string values; HTML returned by developer render() / itemLabel()
    callbacks is still rendered as HTML. Apps that stored markup in item fields
    to style labels must render it through itemLabel() instead.
    itemLabel() callbacks return HTML, so they must escape the values they
    insert with escapeHtml(). DitoContext gains itemLabelText, the
    plain-text form of the HTML itemLabel; form aria-labels use this text.
  • Confirmations are admin dialogs instead of native window.confirm()
    (delete, remove, leave with unsaved changes, remove file). E2E suites that
    answer native dialogs (page.on('dialog')) must click the dialog's buttons
    instead: Cancel, or the submit button named after the verb, e.g.
    Delete. The clipboard reports errors through notifications instead of
    alert().
  • A beforeunload prompt shows when a form or view has unsaved changes.
    E2E suites that reload or navigate away from dirty forms may need to accept
    the native beforeunload dialog, or save / discard first.
  • Plain select values whose option disappeared are kept and shown as
    "(unavailable)", instead of being cleared silently. Apps relying on the
    value being dropped must clear it themselves, e.g. in onChange of the
    field the options depend on.
  • Color fields emit change only once the user's edit is done, and no longer
    for values changed by code. onChange handlers that relied on programmatic
    changes must react to the source of the change instead.
  • Deleting items from a list with its own resource no longer marks the form
    dirty (the server already applied the change); change is still emitted.
  • Validation errors are rendered in flow below the field (an alert list
    linked via aria-describedby) instead of in a tippy tooltip, so forms with
    errors are taller. Inputs receive the focus instead of their wrappers.
  • Schema handlers in the onX form are now wired to native DOM events like
    events: { x } already was. Unknown validator names, schema members that
    override component members and unsupported emitEvent() options now log
    warnings.

Admin: deprecations

  • setLoading() on resources and views and DitoRoot.registerLoading()
    are deprecated; they forward to the new loading trackers
    (loadingTracker.begin() / .track()). isLoading is now read-only and
    only declared on resource components, views and the root.
  • $pulldown-* SCSS variables ($pulldown-radius, $pulldown-padding*)
    are deprecated until 4.0; the pulldown no longer uses them.
  • options.groupBy (the unused type in the declarations) is deprecated; use
    the schema's groupBy, now also supported by radio and checkboxes.

Admin / UI: removed and renamed CSS classes

Apps that style or select these classes (including e2e locators) must update:

  • .dito-pulldown, .dito-pulldown--open, .dito-pulldown__item,
    .dito-pulldown__item--disabled: the create button and account menu use
    ui's DitoMenuButton (.dito-menu-button__menu, __item,
    __item--disabled).
  • The tippy error theme ([data-theme~='error']): error tips are gone,
    see above.
  • .dito-label--active: replaced by :focus-visible.
  • .dito-page--width-60 / --width-80, .dito-sidebar--width-60 /
    --width-99: breakpoints use container queries now.
  • .dito-drop-target: now .dito-upload--drop-target on the upload.
  • .dito-date-time-picker__inner (and its --focus modifier).
  • Dead selectors that never matched were fixed: .dito-switch-label →
    .dito-switch__label, .dito-upload-footer → .dito-upload__footer.
  • Markup changes: calendar controls and the login link are <button>s
    instead of <a>s; drag handles are focusable DitoDragHandles; the
    calendar grid has rows (.dito-calendar__week, .dito-calendar__item);
    tree items render
    DitoEditButtons instead of inline buttons; date and time pickers render
    through DitoPickerInput.

Server

  • Related models are validated fully also under models with
    $initialize().
    Relations are now parsed and initialized with the parent's
    options before its $initialize(), so { id } references in graphs are
    validated as full models and fail on required fields. Link by foreign key
    (authorId: 1) or use #ref / relate: true instead of { id } stubs.
  • new Model(json) no longer validates, as its comment always promised;
    call Model.fromJson() to validate.
  • Not-found errors include the model id:
    'Item' model with id 12 not found. Adjust tests matching the message.
  • DatabaseError.cause points at the driver's native error
    (error.nativeError) instead of the wrapped Objection error. Formatted
    errors keep their stack.
  • NotImplementedError responds with 501 instead of 404.
    AuthenticationError carries passport's failure status.
  • type: 'color' validates through the new color format, as a hex color
    (#rgb, #rgba, #rrggbb, #rrggbbaa) or a color keyword (it accepted any
    string). Stored values such as
    'rgb(…)', 'ff0000' (no #) or '' now fail validation on updates;
    migrate the data or use another type / format for these fields.
  • Collection PATCH / PUT with a single object instead of an array returns
    400 (BodyValidation).
  • Scoped deletes only delete the models of the requested scope; users excluded
    by the session scope are logged out.
  • Invalid date strings in filters are left to the format check (400) instead
    of being parsed.
  • from: 'member' parameters and $owner checks go through
    member.get again, so app overrides of member.get apply. Overrides receive
    (ctx, modify, { query, forUpdate }) and should pass these on, e.g. to the
    new fetchMember().
  • Asset steps (handleAddedAndRemovedAssets() etc.) run through the
    Application methods again on model writes, so app overrides apply. All asset
    methods return arrays, also without an Asset model.
  • Actions are routed by the HTTP methods Node.js supports
    (http.METHODS); other method names throw at setup.
  • Sessions serialize users by $id(); $is() compares by $id().
  • AdminController.getPath(name) is deprecated; use resolveConfigPath(name).

Type declarations

  • Admin: DitoContext.user and parentItemDataPath are nullable, so
    callbacks under strict TypeScript must handle null; new
    DitoContext.itemLabelText; notify() takes text or html;
    rootComponent is DitoRootInstance;
    setLoading() / isLoading moved to resource, view and root interfaces;
    panel schemas mapped in SchemaByType; DateSchema.dateFormat marked
    deprecated (no effect).
  • Server: graph and fetch methods return SingleQueryBuilder or
    ArrayQueryBuilder depending on the input; allowScope(),
    clearAllowScope(), allowFilter() and omit() return the builder (they
    are now chainable at runtime too); filterGraph(), populateGraph() and
    fromJson() are typed by model class; asset methods return arrays without
    undefined / null; graph data accepts nullable relations and #id /
    #ref; new fetchMember() (with a modify(query, trx) callback),
    relateAndFetch(), resolveModelClass(), resolveConfigPath(). Code
    relying on the previous wider types may need narrowing or casts removed.

Security

  • Stored XSS through item labels: item values are escaped in labels of lists,
    objects, trees and the trail.
  • Stored XSS through notifications: title and text are escaped; item
    labels and server error messages in form notifications go through html
    with escaped values.

Fixes

admin

  • Compare route paths by whole segments (/items/1 vs /items/12), for
    closing forms, active menu items and tree items.
  • Validate unmounted fields before closing mutating forms.
  • Don't take over user edits made while options load as clean changes.
  • Run compute() when options resolve to undefined; forget options of
    removed components; don't mutate computed values when setting defaults.
  • Stop waiting for pending loads after a timeout.
  • Key computed values, options and item stores by data identity instead of
    list index; transfer uids by item id after saving.
  • Data loads only abort the previous load; request callbacks are awaited.
  • Buttons: running state, error handlers receiving the error, notifications
    per context.
  • Don't cache failed requests; key resource requests by URL.
  • showDialog() rejects when its components fail to set up.
  • notifications.destroyAll() works.
  • Ignore malformed filter entries in the query; show server errors of list
    filters at their fields.
  • new DitoAdmin(el, { views }) falls back to the global dito object.
  • schema.data() receives a context.
  • Dialogs: only close clickToClose dialogs on overlay clicks; Escape honours
    cancel buttons; label dialogs with settings.label.
  • Markup: TipTap 3 autofocus / enableRules, swapped sub- / superscript,
    pending edits written before destroy, no <p>null</p>.
  • Multiselect: added tags no longer leak into shared options and emit
    change; stored tags show without options.
  • Checkboxes keep 0 and false values.
  • Uploads: rows keyed by file, download responses checked, API headers and
    credentials sent with uploads and downloads, live upload status, video/mp4.
    Relative download URLs (files/1) now resolve against the API URL, for
    links and downloads alike; absolute and root-relative URLs are used as is.
  • Code editor calls onInput; slider number input labelled.
  • decimals: 0 in the decimals validator; clear errors on empty validation
    results.
  • Resolve items of named sub-menus.
  • Temporary ids of unrelated sources removed on the clipboard.
  • Select the default tab once the item has loaded, keeping a user-selected
    tab; focus tabs by key.
  • Only open tree items along existing edit paths.
  • Section labels render in flow; panels of components show in tabs, and
    validation errors in them select their tab.
  • isApiUrl() requires a boundary; store defaults aren't written in getters;
    defaultMultiple, defaultWidth, getSourceType merged through
    extends.
  • Buttons inside forms no longer submit them (type="button").
  • Dead and leaking CSS selectors fixed; edit buttons of tree items stay
    compact.
  • The cache: 'global' load cache is cleared on logout and user change.

ui

  • Triggers close with Escape and clean up their listeners; positioning stops
    when hidden or unmounted.
  • Pagination: don't submit forms, clamp the page to 1.
  • Switch with a single custom label sized correctly.
  • Input size binding restored.
  • Date stepping clamps the day; spaces aren't parsed as digits; custom date
    format functions applied in DitoDateTimePicker; disabled dates and times
    are never committed.
  • Focused fields keep their error border.
  • Calendar resets the time to 00:00 after the date was cleared.
  • Time inputs let Cmd / Ctrl / Alt shortcuts through.

server

  • Query parameters: range separator, nulls order error message.
  • Validator: _-prefixed keywords and formats resolved; instanceof
    accepts constructors; self-referencing and additionalProperties schemas
    converted; failed conversions not cached.
  • Controllers: member lookups with named parameters, per-request loggers,
    ModelController model class lookup.
  • Graphs: parseable relation(mod) as alias expressions; only relatable
    models reduced to references; plain graph data filtered and populated;
    relate: true in collection graphs; relates through non-owned relations;
    Location header of related models.
  • clone() keeps all scopes; upsertGraph() child queries keep the default
    scope.
  • Relations loaded through fetchGraph(), including properties of relations.
  • Streamed response logging and connect middleware (next(), errors, mount
    paths).
  • Storages: empty uploads, mime-type detection.
  • Silent env restore, migration errors, JSON seeds.
  • UUID / hyphenated ids in sessions; AssetMixin calls super.$parseJson();
    rollback warning about modified asset files.

build

  • Externalize dependency subpaths in library builds; UMD globals derived from
    module ids.
  • Base64-encode raw bytes of non-svg data URIs.
  • New dist:check script, also run in CI, checks built bundles for
    undeclared or bundled dependencies.
  • Test harnesses are no longer published with admin and ui.

utils

  • deprecate() test made independent of test order (no runtime change).

Improvements

  • Keyboard accessibility: tabs (arrow keys, Home / End, Enter / Space),
    menus and create button, date and time pickers (calendar grid with arrow
    keys), drag handles (Alt+ArrowUp / Down, also inlined items), editor resize
    handles, tree edit buttons on focus, focus rings via :focus-visible.
  • New DitoMenuButton in @ditojs/ui, replacing the mouse-only pulldowns of
    the create button and account menu; DitoTrigger gains click toggle,
    open() / close() / toggle() and matchTargetWidth.
  • DitoButton supports verbs, link rendering and disabled links; buttons
    across the admin render through it.
  • Menus and trails use real links (modifier-clicks open new tabs); sub-menus
    open on hover and focus.
  • Validation errors are announced to assistive tech; aria fixes in trail and
    table head.
  • Radio and checkboxes support groupBy, editable and multiple: false.
  • New type option rendersOwnLabel.
  • addEvents() in ui supports abort signals.
  • Container queries for breakpoints instead of JS width measurement.
  • Upload drop targets tracked reactively.

Internal

  • Tests: Vitest coverage (pnpm coverage), component tests in happy-dom
    mounting the real admin, unit tests across admin, ui, server, utils and
    build; line coverage now ~96%. Known bugs marked with test.fails().
  • Screenshot tests of all admin components and states in the Playwright
    Docker image (pnpm -C tests screenshots).
  • Refactors behind unchanged APIs: Session, ViewRegistry,
    LoadingTracker, ItemStores, ListQuery, FileDropTargets and clipboard
    state as plain modules; LoadingMixin and PulldownMixin removed;
    AssetManager and shared handler parsing / ParameterValidator on the
    server; shared components DitoSourceItem, DitoDragHandle,
    DitoInputAffixes, DitoResizeHandle, DitoPickerInput, DitoTimePanel.
  • DOM handlers removed via abort signals; release docs in the README.