Skip to content

v4 #694

Description

@titouanmathis

Vision

v3: a micro-framework for writing JS components.
v4: a toolkit of HTML-first meta-components for adding interactivity.

HTML becomes the primary interface. Custom JS is only needed for complex cases (Slider, Drag, etc.). Meta-components (Action, Fetch, DataBind, Transition) cover 80% of use cases directly from markup.

Principles:

  • 0 runtime dependencies
  • HTML-first, progressive enhancement, Light DOM
  • What the browser does natively, we do not reimplement
  • The registry is the catalog of components available to HTML

Architectural changes

$emit — bubbling events (#630)

$emit dispatches a standard CustomEvent with bubbles: true and an __source property referencing the emitting instance:

$emit(event, ...args) {
  const e = new CustomEvent(event, { bubbles: true, detail: args });
  e.__source = this;
  this.$el.dispatchEvent(e);
}
  • Events bubble up the DOM natively — enables event delegation
  • __source identifies the emitting instance for on{Child}{Event} resolution
  • Action keeps working as-isdata-on:fetch-before listens to fetch-before which bubbles normally
  • $on / $off keep working as-is — direct listener on $el

on{Child}{Event} — event delegation

The parent listens on this.$el instead of binding on each child. The handler walks up from event.target to this.$el, looking for mounted instances.

handler(event) {
  let el = event.target;
  while (el && el !== this.$el) {
    if (el.__base__) {
      for (const [name, instance] of el.__base__) {
        if (instance === "terminated") continue;
        const method = `on${name}${pascalCase(event.type)}`;
        if (typeof this[method] === "function") {
          this[method]({ event, target: instance });
          return;
        }
      }
    }
    el = el.parentElement;
  }
}
  • el.__base__ is a Map<string, Base> keyed by config.name (already implemented on develop)
  • Dynamically added children work automatically (no binding/unbinding needed)
  • The parent still needs config.components to know which events to listen to (resolving method name ambiguity: onSliderDragStartSliderDrag + Start)
  • mouseenter/mouseleave do not bubble — these keep direct binding on the child element (accepted limitation)

$parent$closest(name)

$parent is removed. Replaced by an explicit query:

// v3
this.$parent.goNext();

// v4
this.$closest("Slider").goNext();

$children$query(name)

// v3
for (const item of this.$children.SliderItem) { ... }

// v4
for (const item of this.$query("SliderItem")) { ... }

Sugar for queryComponentAll(name, { from: this.$el }) (already implemented as helper, #629).

createApp removed

The registry + MutationObserver handle everything. Features are configured separately (#691):

// v4
import { defineFeatures, registerComponent } from "@studiometa/js-toolkit";
defineFeatures({ breakpoints: { ... } });
registerComponent(MyComponent);

No root "App" component required. Each component is independent.

SafeAction — CSP-safe declarative commands

New component alongside Action. No new Function(), just declarative method calls:

<!-- Action (CSP-unsafe, kept for backwards compat) -->
<button data-component="Action" data-on:click="Modal->target.open()">

<!-- SafeAction (CSP-safe, recommended) -->
<button data-component="SafeAction" data-on:click="Modal.open">
<button data-component="SafeAction" data-on:click="Modal(#my-modal).open">
<button data-component="SafeAction" data-on:click="Modal(.sidebar).close">

Format: Component[(selector)].method — parsed with a simple regex, resolved via the registry.


Breaking changes

  • Make custom events bubble up by default with __source annotation ([Feature] Enable bubbling and cancelation for custom events #630)
  • Refactor EventsManager — event delegation on this.$el instead of binding on each child
  • Remove createApp (replaced by registerComponent + defineFeatures)
  • Remove $parent property (replaced by $closest(name))
  • Remove $root property
  • Remove $children property (replaced by $query(name))
  • Remove LoadService
  • Remove KeyService
  • Remove ability to configure the blocking feature
  • Maybe remove prefix and attributes feature configuration (to reduce size)
  • Update default breakpoints to match @studiometa/tailwind-config
  • Make ResponsiveOptionsManager the default
  • Config merge strategy for refs ([Base] Maybe improve config merge strategy? #627) — merge by default instead of override

New features

Meta-components integrated into core

Some components from @studiometa/ui are promoted to the core framework:

  • Action — event → JS expression on a target (CSP-unsafe)
  • SafeAction — event → declarative command on a target (CSP-safe)
  • Fetch — HTTP request → DOM swap
  • Transition — enter/leave animations
  • DataBind, DataModel, DataEffect, DataComputed — reactive data binding

UI components removed (replaced by web platform)

These components no longer need to exist in @studiometa/ui:

Component Native replacement
Modal <dialog> + showModal() + Invoker Commands (commandfor)
Accordion / AccordionItem <details> + <summary>
AnchorScrollTo scroll-behavior: smooth + <a href="#id">
Sticky position: sticky
Figure loading="lazy"
Hoverable CSS :hover + :has()
Prefetch <link rel="prefetch"> + Speculation Rules API
Sentinel IntersectionObserver (trivial)

Services

Service v4 status
RafService ✅ Keep — read/write scheduling has no native equivalent
DragService ✅ Keep — custom drag + inertia has no native equivalent
ScrollService ✅ Keep — aggregation + throttle still useful
ResizeService ⚠️ Simplify — recommend ResizeObserver
PointerService ⚠️ Simplify
LoadService 🗑 Remove
KeyService 🗑 Remove
MutationService ⚠️ Internal use only (registry)

Already done on develop (v3.4.x)

Migration path

Phase 1 — v3.x (non-breaking)

Phase 2 — v4 (breaking)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions