New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

data-double-dash

Package Overview
Dependencies
Maintainers
0
Versions
115
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

data-double-dash

A lightweight, HTML-first and type-safe front-end framework.

  • 1.19.2
  • latest
  • npm
  • Socket score

Version published
Weekly downloads
846
decreased by-64.57%
Maintainers
0
Weekly downloads
 
Created
Source

data-double-dash

A lightweight, HTML-first and type-safe front-end framework.

Key features

  • 🪶 Lightweight without dependencies
  • 📄 HTML-first
  • 📦 Component based framework
  • 🔎 Schema validation
  • 🔊 Event handling
  • 🔒 Type-safety

What is it ?

data-double-dash is a great MPA front-end solution when working with templating engines (such as ejs, handlebars, blade, twig, jinja, etc) or plain HTML. Once a component is defined it is automatically mounted wherever you need it in your HTML (using attributes starting with "data--"). Each component is self-contained, having its own state, event handlers, lifecycle hooks and more. When a DOM element which you mounted your component on is deleted (with DOM APIs such as element.remove()), all of its attached event listeners, intervals and timeouts are automatically removed.

Example

A very basic example showing how to create a simple counter :

ddd({
	name: "counter",
	// type validation schema for the component's props
	props: ddd.object({
		step: ddd.number(),
	}),
	// initial state
	state: {
		count: 0,
	},
	// event handlers
	handlers: {
		increment(event, { element, props, state }) {
			state.count += props.step // increment count
			element.textContent = state.count.toString() // update UI
		},
	},
})
<button
	data--counter='{ "step": 1 }'
	data--counter--click='{ "handler": "increment" }'
>
	0
</button>

Installation

# npm
npm install data-double-dash
# yarn
yarn add data-double-dash
# pnpm
pnpm add data-double-dash

Manager

The manager monitors DOM changes and is responsible for registering/unregistering templates and mounting/unmounting components. Unless you have a specific use case that requires to manually control these steps you probably don't need to interact with it.

Templates

Templates are registered using the ddd function, they are used by the manager as a blueprint to automatically create its corresponding components in the DOM.

Name

The name is a unique identifier for your template, it must only contain lower case alphanumeric characters and single hyphens. It is the only mandatory property for a template. If a template with the same name has already been registered, the registration will be ignored. Components will be mounted when they match the name of their template, for example if the name of a template is "foo-bar" all elements with a data--foo-bar attribute name will be mounted.

ddd({
	name: "carousel",
})
<div data--carousel></div>

Props

When declaring a component in your HTML, you can configure it by giving it properties, for example data--carousel='{ "loop": true, "autoplay": true }'. Providing a type schema will validate the props, this will ensure they are of the expected type. You can also choose to not provide any schema, however if you do so you won't have type safety.

ddd({
	name: "carousel",
	props: ddd.object({
		loop: ddd.boolean(),
		autoplay: ddd.boolean({ optional: true }),
	}),
})
<div data--carousel='{ "loop": true }'></div>

Element

The structure is an element schema allowing you to enforce a certain element structure (mandatory attributes, required children, etc) when the component is created.

ddd({
	name: "carousel",
	element: {
		tag: "ul",
		attributes: {
			class: {
				value: /carousel/
				optional: true
			}
		}
		children: [
			tag: "li"
			min: 0,
			children: [
				{
					tag: "img",
					max: 1
				}
			]
		]
	}
})

Refs

Refs are a way to quickly access key elements. You declare them in the template using the following syntax in an array:

  • An optional tag, for example "<ul>" or "<div>".
  • The name of the ref, for example "foo-bar".
  • An optional modifier, it can be either "[]" which will transform your ref to an element into a ref to an array of elements or "?" to make the ref optional.

For example this is an array of valid refs ["foo", "bar?", "baz[]", "<section>fizz", "<div>buzz[]"]

ddd({
	name: "carousel",
	refs: ["<ul>image-list", "<ul>dot-list", "<img>images[]"],
	handlers: {
		display(event, component, params) {
			component.refs["image-list"] satisfies HTMLUListElement
			component.refs["dot-list"] satisfies HTMLUListElement
			component.refs.images satisfies HTMLImageElement[]
		},
	},
})
<div data--carousel>
	<ul data--carousel.image-list>
		<li><img data--carousel.images src="..." /></li>
		<li><img data--carousel.images src="..." /></li>
	</ul>
	<ul data--carousel.dot-list>
		<li>
			<button
				data--carousel--click='{ "handler": "display", "params": { "slide": 0 } }'
			>
				1
			</button>
			<button
				data--carousel--click='{ "handler": "display", "params": { "slide": 1 } }'
			>
				2
			</button>
		</li>
	</ul>
</div>

State

The state of a component only refers to its variables and methods. Unlike frameworks like React data-double-dash is not reactive, state changes will not automatically update the DOM. When creating a template you can provide its initial state, either directly as an object or using a callback which receives the current component as a parameter including props. If the typeof your state is "object", then you can use it. In other words you can have regular objects, but also arrays, classes (including anonymous classes new (class { /* ... */ })()) or your favourite state library.

ddd({
	name: "carousel",
	props: ddd.object({
		loop: ddd.boolean(),
		autoplay: ddd.boolean({ optional: true }),
	}),
	state: ({ props }) => {
		return {
			currentSlide: 0,
			pauseOnHover: props.autoplay ?? false,
		}
	},
})

Handlers

Handlers are methods which handle all the component associated events. Events are defined in the HTML and may or may not have an associated handler, for example data--carousel--click='{ "handler": "display", "params": { "slide": 3 } }'. An handler can be defined as a function or as an object, when defining it as an object you can have validation on the event params and detail property for CustomEvents.

ddd({
	name: "carousel",
	props: ddd.object({
		loop: ddd.boolean(),
		autoplay: ddd.boolean({ optional: true }),
	}),
	state: ({ props }) => {
		return {
			currentSlide: 0,
			pauseOnHover: props.autoplay ?? false,
		}
	},
	handlers: {
		display: {
			params: ddd.object({
				slide: ddd.number(),
			}),
			handler(event, component, params) {
				component.state.currentSlide = params.slide
				// ...
			},
		},
	},
})
<div data--carousel='{ "loop": true, "autoplay": false }'>
	<ul>
		<li><img src="..." /></li>
		<li><img src="..." /></li>
	</ul>
	<ul>
		<li>
			<button
				data--carousel--click='{ "handler": "display", "params": { "slide": 0 } }'
			>
				1
			</button>
			<button
				data--carousel--click='{ "handler": "display", "params": { "slide": 1 } }'
			>
				2
			</button>
		</li>
	</ul>
</div>

Lifecycle hooks

Lifecycle hooks are methods allowing you to run custom code at a certain point of the lifecycle of a template/component. These hooks are :

  • register: This hook executes right after the template has succesfully been registered/created.

  • mount: This hook executes right after a component has been mounted, it receives that component as a parameter.

  • unmount: This hook executes right after a component's event listeners, intervals and timeouts have been removed and just before it gets unmounted, it receives that component as a parameter.

  • unregister: This hook executes right after the template has succesfully been unregistered/removed.

ddd({
	name: "foo-bar",
	register() {
		// ...
	},
	mount(component) {
		// ...
	},
	unmount(component) {
		// ...
	},
	unregister() {
		// ...
	},
})

Components

As seen in the templates documentation, components are instanciated in every DOM element with a corresponding attribute. When handling a component you will have access to the following properties.

Template

The template is a string refering to the unique name of the template this component originated from. This is the name you use to declare a new component in your HTML.

<div data--template-name>
	This is a component for the "template-name" template.
</div>

Element

The element property is a reference to the element on which the component is mounted.

<div data--component>
	Here the component's element property will refer to this div.
</div>

Props

The props of a component is an object passed when declaring your component.

<div data--component='{ "foo": true, "bar": false }'>
	This component will have access to a props object with two properties, one
	being `foo` which will be `true` and the other being `bar` which will be
	`false`.
</div>

Refs

You can access the refs declared in the template by using the component.refs object

State

The state is the current state of your component. The initial state will be equal to what has been defined in the template, however you are free to mutate it over time as needed.

Events

You can declare component events in your HTML using the following syntax :

  1. data--: Start with the framework identifier.
  2. template--: Then the template name and two dashes to indicate an event for that component.
  3. target.: This part is optional, this is the target to put the event listener on. The available event targets are : "" (default target corresponding to the current element), "window", "document" or the name of a template (this will attach the event listener to the closest element with a component of that type).
  4. type: The event type, for example "click".
<div data--component data--component--window.resize='{ "handler": "onResize" }'>
	This will call the "onResize" handler on window resize on the "component"
	template.
</div>
<div data--file-drop>
	<ul
		data--file-list
		data--file-list--file-drop.drop='{ "handler": "onFileDrop" }'
	>
		<li>
			When the parent "file-drop" component emits a "drop" event the
			"onFileDrop" handler will be executed.
		</li>
	</ul>
</div>

Reference

ddd

function ddd(
	template: Template,
	options: { target?: Node; control?: "auto" | "manual" | "full" },
): () => void // returns a function to remove the template and all its components
  • template.name: The unique name of the template.

  • template.props: An optional schema to validate the props.

  • template.state: An optional object or a method returning an object as the initial state of a component.

  • template.element: An optional element schema to enforce a certain element structure when the component is created.

// Initial state providing function
function (
	component: {
		template: string,
		element: Element,
		// schema validated props found in the component's HTML attribute :
		props: Record<string, unknown>
	}
): object
  • template.handlers: An optional object containing either event listener methods or objects containing the event listener method and validation schemas.
// Handler as a method :
function (
	event: Event,
	component: {
		template: string
		element: Element
		// schema validated props found in the component's HTML attribute :
		props: Record<string, unknown>
		// current state for the component :
		state: object
		// dispatch a CustomEvent on `element` that bubbles up the DOM tree :
		dispatch: (type: string, detail?: { [key: string]: unknown }, options?: { cancelable?: boolean; composed?: boolean }) => boolean
		// returns a function to remove the timeout :
		timeout: (handler: (...args: any[]) => unknown,	timeout: number) => () => void
		// returns a function to remove the interval :
		interval: (handler: (...args: any[]) => unknown, timeout: number) => () => void
	},
	// params found in the event's HTML attribute :
	params: Record<string, unknown>
): any
// Handler as an object :
{
	// constructor or array of constructors extending  EventTarget used to validate the handler's event.target
	event?: EventTargetConstructor | EventTargetConstructor[],
	// constructor or array of constructors extending Event used to validate the handler's event
	event?: EventConstructor | EventConstructor[],
	// schema used to validate CustomEvent's detail property :
	detail?: Schema,
	// schema to validate the params found in the event's HTML attribute :
	params?: Schema,
	handler: function (
		event: Event,
		component: {
			template: string
			element: Element
			// schema validated props found in the component's HTML attribute :
			props: Record<string, unknown>
			// current state for the component :
			state: object
			// dispatch a CustomEvent on `element` that bubbles up the DOM tree :
			dispatch: (type: string, detail?: { [key: string]: unknown }, options?: { cancelable?: boolean; composed?: boolean }) => boolean
			// returns a function to remove the timeout :
			timeout: (handler: (...args: any[]) => unknown,	timeout: number) => () => void
			// returns a function to remove the interval :
			interval: (handler: (...args: any[]) => unknown, timeout: number) => () => void
		},
		// params found in the event's HTML attribute :
		params: Record<string, unknown>
	): any
}
  • template.register: The optional register lifecycle hook method. Executes on succesful template creation.

  • template.mount: The optional mount lifecycle hook method. Executes on succesful component mount.

function (component: Component): any
  • template.unmount: The optional unmount lifecycle hook method. Executes on succesful component unmount.
function (component: Component): any
  • template.unregister: The optional unregister lifecycle hook method. Executes on succesful template removal.

ddd.get

function (): Manager

ddd.get.components

function (element: Element): { [template: string]: Component }

ddd.get.component

function (element: Element, template: string): Component | null

ddd.element

function (
	html: string,
	options?: {
		// allows you to replace substrings in `html` before converting it to a node
		// this is useful for placeholders such as <div id="__ID__">...</div>
		replace?: Record<string, string>;
		// defaults to true, removes everything that can execute code from the HTML
		sanitize?: boolean;
	}
): DocumentFragment

ddd.element.validate

function (
	element: Element,
	schema: ElementSchema<U>,
): asserts element is GetElementFromHtmlTag<U>

ddd.validate

function (data: unknown, schema: Schema): asserts data is ResolvedSchema<Schema>

ddd.string

// creates a schema to validate strings
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: string) => boolean // additional custom validation function
	}
): StringSchema

ddd.number

// creates a schema to validate numbers
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: number) => boolean // additional custom validation function
	}
): NumberSchema

ddd.boolean

// creates a schema to validate booleans
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: boolean) => boolean // additional custom validation function
	}
): BooleanSchema

ddd.undefined

// creates a schema to validate undefined values
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: undefined) => boolean // additional custom validation function
	}
): UndefinedSchema

ddd.null

// creates a schema to validate null values
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: null) => boolean // additional custom validation function
	}
): NullSchema

ddd.nullish

// creates a schema to validate nullish values
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: null | undefined) => boolean // additional custom validation function
	}
): NullishSchema

ddd.function

// creates a schema to validate functions
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: (...args: any[]) => unknown) => boolean // additional custom validation function
	}
): FunctionSchema

ddd.instance

// creates a schema to validate instances of a constructor
function (
	constructor: new (...args: any[]) => Object // for example `File` or `Element`
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: CorrespondingInstance) => boolean // additional custom validation function
	}
): InstanceSchema

ddd.object

// creates a schema to validate object
function (
	object: { [property: string]: Schema }
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: CorrespondingObject) => boolean // additional custom validation function
	}
): ObjectSchema

ddd.array

// creates a schema to validate arrays
function (
	types: Schema | Schema[] // allowed types in the array
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: CorrespondingArray) => boolean // additional custom validation function
	}
): ArraySchema

ddd.union

// creates a schema to validate union (an union is when there's more than a single valid type)
function (
	types: Schema | Schema[] // allowed types
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: CorrespondingUnion) => boolean // additional custom validation function
	}
): UnionSchema

ddd.record

// creates a schema to validate records (records are objects with arbitrary keys)
function (
	types: Schema | Schema[] // allowed types in the record values
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: CorrespondingRecord) => boolean // additional custom validation function
	}
): ArraySchema

ddd.set

// creates a schema to validate sets
function (
	types: Schema | Schema[] // allowed types in the set
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: CorrespondingSet) => boolean // additional custom validation function
	}
): SetSchema

ddd.map

// creates a schema to validate maps
function (
	keyTypes: Schema | Schema[], // allowed types for keys in the map
	valueTypes: Schema | Schema[] // allowed types for values in the map
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: CorrespondingSet) => boolean // additional custom validation function
	}
): MapSchema

ddd.any

// creates a schema to accept any value
function (
	options?: {
		optional: true // makes this schema optional in an object
		predicate: (data: unknown) => boolean // additional custom validation function
	}
): AnySchema

Contributors

Alexandre Fernandez
Alexandre Fernandez

Keywords

FAQs

Package last updated on 10 Feb 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc