v0.21

components

introduction

dframework ships a set of web components and a performance tailored base class for authoring custom ones. components are standard custom elements with no virtual dom, no compiler, no build step. the framework compiles and bundles component files in public/js/components/ automatically.

built in components

d-checkbox

1<d-checkbox text="enable notifications" checked></d-checkbox>
attribute description
text label text
checked initial checked state

events: change

d-color-picker

1<d-color-picker value="#ff0000" name="theme color"></d-color-picker>

methods: toggle(), setHex(hex) events: change

d-combobox

a searchable, extensible select component.

1<d-combobox
2 options='[{"value":"a","text":"option A"},{"value":"b","text":"option B"}]'
3 placeholder="select an option"
4 allow-search>
5</d-combobox>
attribute description
options json array of {value, text} objects
placeholder placeholder text
allow-search enable search filtering
allow-input allow custom values not in the options list

methods: addOption(value, text), removeOption(value), clearOptions() events: change, add, open, close

d-context-menu

renders a context menu on right click within the parent element:

1<div>
2 right-click anywhere here
3 <d-context-menu>
4 <span>copy</span>
5 <span>paste</span>
6 <span>delete</span>
7 </d-context-menu>
8</div>

d-drawer

1<d-drawer direction="left" opened>
2 <nav>navigation content</nav>
3</d-drawer>
attribute description
direction top, right, bottom, left
opened initial open state

methods: toggle()

d-dropdown

1<d-dropdown header="account">
2 <a href="/profile">profile</a>
3 <a href="/settings">settings</a>
4 <a href="/logout" full>log out</a>
5</d-dropdown>

d-hold-button

fires click only after the user holds for the specified duration. prevents accidental activation of destructive actions.

1<d-hold-button delay="2000">
2 delete
3</d-hold-button>

d-icon-button

1<d-icon-button icon="dstrn-heart" size="2em" color="red"></d-icon-button>

d-image-input

1<d-image-input accept="image/png,image/jpeg" name="avatar" fit="contain"></d-image-input>
attribute description
accept allowed file types (default image/png, image/jpeg, image/gif)
icon placeholder icon class (default dstrn-picture)
name input name
id input id
placeholder primary dropzone label text (default drag & drop image)
subtitle secondary dropzone helper text (default or click to browse)
replace-text label text for the replace button (default replace)
delete-text label text for the delete button (default delete)
no-replace disables replace action (default false)
no-delete disables delete action (default false)
fit object fit property applied to preview image (default contain)
value file object or url string path

methods: reset() events: change

d-file-input

1<d-file-input accept="*/*" name="document" multiple compact></d-file-input>
attribute description
accept allowed file types (default */*)
icon placeholder icon class (default dstrn-folder)
name input name
id input id
placeholder primary dropzone label text (default drag & drop file)
subtitle secondary dropzone helper text (default or click to browse)
compact render as a slim horizontal bar (default true)
multiple allow selecting multiple files (default false)
value file object, url string path, or array of files/urls

methods: reset() events: change

d-loader

1<d-loader></d-loader>

d-modal

1<d-modal>
2 <h2>confirm action</h2>
3 <p>this cannot be undone.</p>
4 <button>confirm</button>
5</d-modal>

methods: open(), close()

d-notification

1<d-notification timer="4000">
2 <p>your changes have been saved.</p>
3</d-notification>

methods: show(), hide(), destroy()

d-skeleton

placeholder shapes rendered during content loading:

1<d-skeleton type="text" lines="3"></d-skeleton>
2<d-skeleton type="circle" size="3em"></d-skeleton>
3<d-skeleton type="rect" width="100%" height="200px"></d-skeleton>
4<d-skeleton type="card"></d-skeleton>
attribute default description
type text, circle, rect, card
lines 1 number of lines (for text type)
size shorthand for equal width and height
width / height explicit dimensions
radius border radius override

d-slider

1<d-slider min="0" max="100" value="50" step="1"></d-slider>

methods: getValue(), setValue(value) events: change

d-text-input

1<d-text-input placeholder="search" icon="dstrn-search" shortcut="cmd+k"></d-text-input>
attribute description
placeholder placeholder text
icon input icon class
type input type (text, password, search, number)
shortcut keyboard shortcut to quickly focus the input
autocomplete enable browser autocomplete (default false)
disabled disables the input element (default false)
readonly sets the input to read only (default false)

d-toggle

renders a segmented toggle between two or more options. mark the default with default:

1<d-toggle>
2 <div default>option 1</div>
3 <div>option 2</div>
4</d-toggle>

events: change

d-morph

standalone morphing elements. the dMotion singleton engine orchestrates physics based animated transitions between named elements placed anywhere in the document.

1<d-morph name="compact" d-action="long-press" d-to="expanded" d-duration="400" d-visible>
2 <div class="compact-card">compact view</div>
3</d-morph>
4
5<d-morph name="expanded" d-action="click" d-to="compact" d-duration="300">
6 <div class="expanded-card">expanded view</div>
7</d-morph>
attribute description
name unique identifier for this morph element
d-visible marks this element as initially visible
d-action trigger type: click, long-press or click-out
d-to name of the target morph element to transition to
d-duration transition duration in milliseconds

methods: show(), hide()

place d-no-morph on child elements to exempt them from the transition and allow standard interaction.

window.dMotion api:

1dMotion.get('compact') // get a morph instance by name
2dMotion.getPrevious() // returns the immediately previous morph, null if no transition has occurred
3dMotion.transition('compact', 'expanded', triggerNode) // trigger a transition programmatically
4dMotion.listen('compact', 'expanded', fn, 'before') // hook: 'before' or 'after'
5dMotion.unlisten('compact', 'expanded', fn, 'before')

dComponent (base class)

dComponent is the authoring base class for all custom components. it provides property reflection, reactive state, form integration, lifecycle management, and automatic memory cleanup. it does not use a virtual dom, rendering is surgical by design.

1class dCounter extends dComponent {
2 static tag = 'd-counter'
3 static form = true // opt into native form element integration
4
5 static props = {
6 value: { type: 'number', default: 0, form: true },
7 step: { type: 'number', default: 1 },
8 disabled: { type: 'boolean', default: false },
9 label: { type: 'string', default: 'count' },
10 }
11
12 template() {
13 // called once on mount and returns initial innerHTML
14 return `
15 <span class="label">${this.label}</span>
16 <button class="dec">−</button>
17 <span class="value">${this.value}</span>
18 <button class="inc">+</button>
19 `;
20 }
21
22 mount() {
23 // called once after template initialisation
24 // good for effects that depend on reactive state
25 this.effect(() => {
26 // reading this.state.value registers it as a dependency
27 // the effect reruns whenever state.value changes
28 document.title = `count: ${this.state.value}`;
29 return () => { document.title = 'myapp'; }; // optional teardown
30 });
31 }
32
33 render() {
34 // called automatically on every prop or state change
35 // use for surgical dom updates only (never modify this.state here)
36 this.refs('.value')[0].textContent = this.state.value ?? this.value;
37 this.refs('button.dec')[0].disabled = this.disabled;
38 this.refs('button.inc')[0].disabled = this.disabled;
39
40 // listeners are cleared and rebound between renders (no duplicate handlers)
41 this.listen(this.refs('button.dec')[0], 'click', () => this.decrement());
42 this.listen(this.refs('button.inc')[0], 'click', () => this.increment());
43 }
44
45 onPropChanged(name, oldVal, newVal) {
46 if (name === 'value') Log.debug('value changed', oldVal, '->', newVal);
47 }
48
49 increment() {
50 this.setState(s => { s.value = (s.value ?? this.value) + this.step; });
51 }
52
53 decrement() {
54 this.setState(s => { s.value = (s.value ?? this.value) - this.step; });
55 }
56}
57
58dComponent.define(dCounter);

rendering strategy

dComponent does not provide a virtual dom. structure belongs in template(). all dom mutations belong in render() or onPropChanged(). this eliminates reflows, preserves listener state, and prevents flicker. any error thrown inside render() is caught and logged without crashing the component.

this.state cannot be modified inside render(), the engine ignores such writes to prevent infinite loops.

reactive state

1// update state and trigger render()
2this.setState({ active: true });
3this.setState(s => { s.count++; s.lastUpdated = Date.now(); });
4
5// update state without triggering render() but still triggers effect() loops
6this.setState({ timer: Date.now() }, false);

auto cleanup api

use these instead of native browser apis to prevent memory leaks when the component is removed from the dom:

1this.listen(target, event, callback, opts?) // addEventListener with cleanup
2this.listenAll(targets, event, callback) // attach to multiple elements
3this.setTimeout(callback, ms)
4this.setInterval(callback, ms)
5this.requestAnimationFrame(callback)

dom caching

1this.refs(selector) // cached querySelectorAll, returns an array

inner content capture

1this.originalChildren // array of cloned light dom nodes from before template()
2this.originalHTML // original innerHTML as a string

internal event emitter

1this.on(event, callback)
2this.emit(event, ...args)

websocket

1this.wire(event, payload) // emit a d-wire event through the global socket loop