@vielzeug/craftit 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +449 -0
- package/dist/craftit.cjs +18 -0
- package/dist/craftit.cjs.map +1 -0
- package/dist/craftit.js +580 -0
- package/dist/craftit.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +265 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"craftit.js","sources":["../src/craftit.ts"],"sourcesContent":["/** biome-ignore-all lint/suspicious/noExplicitAny: Template values can be any type */\n\n// craftit - Lightweight, type-safe web component creation library\n\n/* ==================== Types ==================== */\n\n/**\n * Lifecycle hook function called with component instance\n * @template T - Root element type\n * @template S - State object type\n */\nexport type LifecycleHook<T = HTMLElement, S extends object = object> = (el: WebComponent<T, S>) => void;\n\n/**\n * Callback invoked when an observed attribute changes\n * @template T - Root element type\n * @template S - State object type\n */\nexport type AttributeChangeHook<T = HTMLElement, S extends object = object> = (\n name: string,\n oldValue: string | null,\n newValue: string | null,\n el: WebComponent<T, S>,\n) => void;\n\n/**\n * Component template - can be a string, DOM node, or function returning either\n * @template T - Root element type\n * @template S - State object type\n */\nexport type Template<T = HTMLElement, S extends object = object> =\n | string\n | Node\n | ((el: WebComponent<T, S>) => string | Node | DocumentFragment);\n\n/**\n * Callbacks for form-associated custom elements\n * @template T - Root element type\n * @template S - State object type\n */\nexport type FormCallbacks<T = HTMLElement, S extends object = object> = {\n /** Invoked when parent form's disabled state changes */\n onFormDisabled?: (disabled: boolean, el: WebComponent<T, S>) => void;\n /** Invoked when parent form is reset */\n onFormReset?: (el: WebComponent<T, S>) => void;\n /** Invoked when browser restores form state (navigation/autocomplete) */\n onFormStateRestore?: (\n state: string | File | FormData | null,\n mode: 'restore' | 'autocomplete',\n el: WebComponent<T, S>,\n ) => void;\n};\n\n/**\n * Configuration options for creating a web component\n * @template T - Root element type\n * @template S - State object type\n */\nexport type ComponentOptions<T = HTMLElement, S extends object = object> = {\n /** Template for rendering the component */\n template: Template<T, S>;\n /** Initial reactive state */\n state?: S;\n /** CSS styles (strings or CSSStyleSheet objects) */\n styles?: (string | CSSStyleSheet)[];\n /** Attributes to observe for changes */\n observedAttributes?: readonly string[];\n /** Enable form association (allows component to participate in forms) */\n formAssociated?: boolean;\n /** Called when component is added to DOM */\n onConnected?: LifecycleHook<T, S>;\n /** Called when component is removed from DOM */\n onDisconnected?: LifecycleHook<T, S>;\n /** Called when an observed attribute changes */\n onAttributeChanged?: AttributeChangeHook<T, S>;\n /** Called after each render completes */\n onUpdated?: LifecycleHook<T, S>;\n} & FormCallbacks<T, S>;\n\n/**\n * Web component instance interface\n * @template T - Root element type (first child in shadow DOM)\n * @template S - State object type\n */\nexport type WebComponent<T = HTMLElement, S extends object = object> = HTMLElement & {\n /** Reactive state object (changes trigger re-renders) */\n readonly state: S;\n /** Shadow DOM root */\n readonly shadow: ShadowRoot;\n /** First element in shadow DOM */\n readonly root: T;\n /** ElementInternals (only when formAssociated: true) */\n readonly internals?: ElementInternals;\n /** Form value (only when formAssociated: true) */\n value?: string;\n\n /** Schedule a render in the next animation frame */\n render(): void;\n /** Wait for the pending render to complete */\n flush(): Promise<void>;\n /** Update state (merge, replace, or via function) */\n set(\n patch: Partial<S> | ((state: S) => S | Promise<S>),\n options?: { replace?: boolean; silent?: boolean },\n ): Promise<void>;\n /** Watch a state slice and react to changes */\n watch<U>(selector: (state: S) => U, callback: (value: U, prev: U) => void): () => void;\n /** Find a single element in shadow DOM */\n find<E extends Element = Element>(selector: string): E | null;\n /** Find all matching elements in shadow DOM */\n findAll<E extends Element = Element>(selector: string): E[];\n /** Add event listener with automatic cleanup and delegation support */\n on(target: string | EventTarget, event: string, handler: EventListener, options?: AddEventListenerOptions): void;\n /** Dispatch custom event */\n emit(name: string, detail?: unknown, options?: CustomEventInit): void;\n /** Set timeout with automatic cleanup */\n delay(callback: () => void, ms: number): number;\n /** Clear scheduled timeout */\n clear(id: number): void;\n /** Form utilities (only when formAssociated: true) */\n form?: {\n /** Set form value */\n value(value: string | File | FormData | null, state?: File | FormData | null): void;\n /** Set a validation state */\n valid(flags?: ValidityStateFlags, message?: string, anchor?: HTMLElement): void;\n };\n};\n\n/* ==================== Utilities ==================== */\n\nconst camelCache = new Map<string, string>();\n\n/**\n * Convert kebab-case to camelCase (memoized for performance)\n * @param str - String in kebab-case\n * @returns String in camelCase\n * @example toCamel('my-component') // 'myComponent'\n */\nconst toCamel = (str: string): string => {\n if (camelCache.has(str)) return camelCache.get(str)!;\n const result = str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());\n camelCache.set(str, result);\n return result;\n};\n\nconst kebabCache = new Map<string, string>();\n\n/**\n * Convert camelCase to a kebab-case (memoized for performance)\n * @param str - String in camelCase\n * @returns String in kebab-case\n * @example toKebab('myComponent') // 'my-component'\n */\nconst toKebab = (str: string): string => {\n if (kebabCache.has(str)) return kebabCache.get(str)!;\n const result = str.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);\n kebabCache.set(str, result);\n return result;\n};\n\n/**\n * Parse HTML string into DocumentFragment\n * @param html - HTML string to parse\n * @returns DocumentFragment containing parsed nodes\n */\nconst parseHTML = (html: string): DocumentFragment => {\n const template = document.createElement('template');\n template.innerHTML = html.trim();\n return template.content;\n};\n\n/**\n * Load CSS stylesheet (handles both strings and CSSStyleSheet objects)\n * @param style - CSS string or CSSStyleSheet object\n * @returns Promise resolving to CSSStyleSheet\n */\nconst loadStylesheet = async (style: string | CSSStyleSheet): Promise<CSSStyleSheet> => {\n if (style instanceof CSSStyleSheet) return style;\n\n const sheet = new CSSStyleSheet();\n await sheet.replace(style);\n return sheet;\n};\n\n/**\n * Create reactive state proxy with automatic re-rendering\n *\n * Features:\n * - Nested objects proxied lazily on first access\n * - `null`, `undefined`, and DOM Nodes are NOT proxied\n * - Properties starting with `_` don't trigger re-renders (private convention)\n * - Uses reference equality for change detection\n *\n * @template S - State object type\n * @param initial - Initial state object (will be shallow-copied)\n * @param onChange - Callback invoked when state changes\n * @returns Reactive state proxy\n *\n * @example\n * const state = createReactiveState({ count: 0 }, () => render());\n * state.count++; // Triggers onChange callback\n */\nconst createReactiveState = <S extends object>(initial: S, onChange: () => void): S => {\n const internalState = { ...initial } as S;\n\n const proxyCache = new WeakMap<object, object>();\n\n const createNestedProxy = <T>(obj: T): T => {\n if (obj == null || typeof obj !== 'object' || obj instanceof Node) {\n return obj;\n }\n\n const cached = proxyCache.get(obj as object);\n if (cached) {\n return cached as T;\n }\n\n const proxy = new Proxy(obj as object, {\n get(target, prop) {\n const value = Reflect.get(target, prop);\n return createNestedProxy(value);\n },\n set(target, prop, value) {\n const oldValue = Reflect.get(target, prop);\n\n if (oldValue === value) return true;\n\n const result = Reflect.set(target, prop, value);\n\n if (typeof prop === 'string' && !prop.startsWith('_')) {\n onChange();\n }\n\n return result;\n },\n }) as T;\n\n proxyCache.set(obj as object, proxy as object);\n return proxy;\n };\n\n return new Proxy(internalState, {\n get(target, prop) {\n const value = Reflect.get(target, prop);\n return createNestedProxy(value);\n },\n set(target, prop, value) {\n const oldValue = Reflect.get(target, prop);\n\n if (oldValue === value) return true;\n\n const result = Reflect.set(target, prop, value);\n\n if (typeof prop === 'string' && !prop.startsWith('_')) {\n onChange();\n }\n\n return result;\n },\n }) as S;\n};\n\n/* ==================== Component Base Class ==================== */\n\nclass BaseComponent<T = HTMLElement, S extends object = object> extends HTMLElement implements WebComponent<T, S> {\n public readonly shadow: ShadowRoot;\n public readonly state: S;\n public value?: string;\n\n #abortController = new AbortController();\n #timeouts = new Set<number>();\n #renderScheduled = false;\n #renderSuppressed = false;\n #options: ComponentOptions<T, S>;\n #internals?: ElementInternals;\n #renderPromiseResolve?: () => void;\n #renderPromise?: Promise<void>;\n #watchers = new Map<\n number,\n { selector: (state: S) => unknown; callback: (value: unknown, prev: unknown) => void; lastValue: unknown }\n >();\n #watcherId = 0;\n\n constructor(options: ComponentOptions<T, S>) {\n super();\n\n this.#options = options;\n this.shadow = this.attachShadow({ mode: 'open' });\n this.state = createReactiveState((options.state || {}) as S, () => {\n this.render();\n this.notifyWatchers();\n });\n\n if (options.formAssociated && 'attachInternals' in this) {\n this.#internals = this.attachInternals();\n }\n\n this.initStyles();\n\n this.initAttributes();\n }\n\n get root(): T {\n return this.shadow.firstElementChild as T;\n }\n\n get internals(): ElementInternals | undefined {\n return this.#internals;\n }\n\n get form() {\n if (!this.#internals) return undefined;\n\n return {\n /**\n * Set validity state\n * @example component.form.valid({ valueMissing: true }, 'Required')\n */\n valid: (flags?: ValidityStateFlags, message?: string, anchor?: HTMLElement) => {\n if (!flags || Object.keys(flags).length === 0) {\n this.#internals!.setValidity({});\n } else {\n this.#internals!.setValidity(flags, message, anchor);\n }\n },\n /**\n * Set form value and sync with ElementInternals\n * @example component.form.value('new value')\n */\n value: (value: string | File | FormData | null, state?: File | FormData | null) => {\n if (typeof value === 'string') {\n this.value = value;\n }\n this.#internals!.setFormValue(value, state);\n },\n };\n }\n\n private async initStyles(): Promise<void> {\n if (!this.#options.styles?.length) return;\n\n const sheets = await Promise.all(this.#options.styles.map(loadStylesheet));\n this.shadow.adoptedStyleSheets = sheets;\n }\n\n private initAttributes(): void {\n const attrs = this.#options.observedAttributes;\n if (!attrs?.length) return;\n\n for (const attr of attrs) {\n const prop = toCamel(attr);\n\n if (prop in this) continue;\n\n Object.defineProperty(this, prop, {\n configurable: true,\n enumerable: true,\n get: () => {\n const value = this.getAttribute(attr);\n return value === '' ? true : value;\n },\n set: (value: any) => {\n if (value == null || value === false) {\n this.removeAttribute(attr);\n } else {\n this.setAttribute(attr, value === true ? '' : String(value));\n }\n },\n });\n }\n }\n\n private notifyWatchers(): void {\n for (const [, watcher] of this.#watchers) {\n try {\n const currentValue = watcher.selector(this.state);\n if (currentValue !== watcher.lastValue) {\n watcher.callback(currentValue, watcher.lastValue);\n watcher.lastValue = currentValue;\n }\n } catch {\n // Swallow watcher errors\n }\n }\n }\n\n /* ==================== Lifecycle Callbacks ==================== */\n\n connectedCallback(): void {\n if (this.#abortController.signal.aborted) {\n this.#abortController = new AbortController();\n }\n\n if (!this.shadow.hasChildNodes()) {\n this.performRender();\n }\n\n this.#options.onConnected?.(this as WebComponent<T, S>);\n }\n\n disconnectedCallback(): void {\n this.#options.onDisconnected?.(this as WebComponent<T, S>);\n\n this.#abortController.abort();\n for (const id of this.#timeouts) {\n clearTimeout(id);\n }\n this.#timeouts.clear();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n this.#options.onAttributeChanged?.(name, oldValue, newValue, this as WebComponent<T, S>);\n this.render();\n }\n\n /* ==================== Form Callbacks ==================== */\n\n formDisabledCallback(disabled: boolean): void {\n this.#options.onFormDisabled?.(disabled, this as WebComponent<T, S>);\n }\n\n formResetCallback(): void {\n this.#options.onFormReset?.(this as WebComponent<T, S>);\n }\n\n formStateRestoreCallback(state: string | File | FormData | null, mode: 'restore' | 'autocomplete'): void {\n this.#options.onFormStateRestore?.(state, mode, this as WebComponent<T, S>);\n }\n\n /* ==================== Public API ==================== */\n\n /**\n * Schedule a render in the next animation frame\n */\n render(): void {\n if (this.#renderScheduled || this.#renderSuppressed) return;\n\n this.#renderScheduled = true;\n\n if (!this.#renderPromise) {\n this.#renderPromise = new Promise((resolve) => {\n this.#renderPromiseResolve = resolve;\n });\n }\n\n requestAnimationFrame(() => {\n this.#renderScheduled = false;\n this.performRender();\n\n this.#renderPromiseResolve?.();\n this.#renderPromise = undefined;\n this.#renderPromiseResolve = undefined;\n });\n }\n\n /**\n * Wait for pending render to complete\n * @returns Promise that resolves after render finishes\n * @example\n * component.set({ count: 10 });\n * await component.flush();\n * expect(component.find('.count')?.textContent).toBe('10');\n */\n async flush(): Promise<void> {\n if (this.#renderPromise) {\n await this.#renderPromise;\n }\n }\n\n /**\n * Update component state\n * @param patchOrUpdater - State patch object or updater function\n * @param options - Update options\n * @param options.replace - Replace entire state instead of merging\n * @param options.silent - Update without triggering render\n * @returns Promise that resolves after update completes\n * @example\n * // Merge state\n * component.set({ count: 1 });\n *\n * // Replace state\n * component.set({ count: 1 }, { replace: true });\n *\n * // Updater function (sync)\n * component.set(state => ({ ...state, count: state.count + 1 }));\n *\n * // Updater function (async)\n * await component.set(async state => {\n * const data = await fetch('/api').then(r => r.json());\n * return { ...state, data };\n * });\n */\n async set(\n patchOrUpdater: Partial<S> | ((state: S) => S | Promise<S>),\n options?: { replace?: boolean; silent?: boolean },\n ): Promise<void> {\n if (typeof patchOrUpdater === 'function') {\n const newState = await Promise.resolve(patchOrUpdater(this.state));\n\n this.#renderSuppressed = true;\n\n for (const key of Object.keys(this.state)) {\n delete (this.state as any)[key];\n }\n\n Object.assign(this.state, newState);\n this.#renderSuppressed = false;\n\n if (!options?.silent) {\n this.render();\n await this.flush();\n }\n return;\n }\n\n const patch = patchOrUpdater;\n\n if (options?.replace) {\n this.#renderSuppressed = true;\n\n for (const key of Object.keys(this.state)) {\n delete (this.state as any)[key];\n }\n Object.assign(this.state, patch);\n\n this.#renderSuppressed = false;\n\n if (!options.silent) {\n this.render();\n }\n } else {\n if (options?.silent) {\n this.#renderSuppressed = true;\n Object.assign(this.state, patch);\n this.#renderSuppressed = false;\n } else {\n Object.assign(this.state, patch);\n }\n }\n }\n\n /**\n * Watch a state slice and react to changes\n * @param selector - Function to select a slice of state\n * @param callback - Function called when selected value changes\n * @returns Unsubscribe function\n * @example\n * const unwatch = component.watch(\n * state => state.count,\n * (count, prevCount) => console.log('Count changed:', count, prevCount)\n * );\n * unwatch(); // Stop watching\n */\n watch<U>(selector: (state: S) => U, callback: (value: U, prev: U) => void): () => void {\n const id = ++this.#watcherId;\n const lastValue = selector(this.state);\n\n this.#watchers.set(id, {\n callback: callback as (value: unknown, prev: unknown) => void,\n lastValue,\n selector: selector as (state: S) => unknown,\n });\n\n try {\n callback(lastValue, lastValue);\n } catch {\n // Swallow callback errors\n }\n\n return () => this.#watchers.delete(id);\n }\n\n /**\n * Find single element in shadow DOM\n * @param selector - CSS selector\n * @returns First matching element or null\n * @example component.find<HTMLInputElement>('input[name=\"email\"]')\n */\n find<E extends Element = Element>(selector: string): E | null {\n return this.shadow.querySelector<E>(selector);\n }\n\n /**\n * Find all matching elements in shadow DOM\n * @param selector - CSS selector\n * @returns Array of matching elements\n * @example component.findAll<HTMLButtonElement>('button')\n */\n findAll<E extends Element = Element>(selector: string): E[] {\n return Array.from(this.shadow.querySelectorAll<E>(selector));\n }\n\n /**\n * Add event listener with automatic cleanup\n *\n * Supports event delegation for dynamic elements:\n * - If target is a string selector, uses delegation on shadow root\n * - Events bubble up and handler is called when target matches selector\n * - Works for elements added after registration\n *\n * @param target - CSS selector or EventTarget\n * @param event - Event name\n * @param handler - Event handler function\n * @param options - Event listener options\n *\n * @example\n * // Direct element binding\n * const button = component.find('button')!;\n * component.on(button, 'click', () => console.log('clicked'));\n *\n * // Delegated binding (works for dynamic elements)\n * component.on('button', 'click', (e) => {\n * console.log('Button clicked:', e.target);\n * });\n *\n * // Delegated with event filtering\n * component.on('.todo-item', 'click', (e) => {\n * const item = e.target as HTMLElement;\n * console.log('Todo clicked:', item.dataset.id);\n * });\n */\n on(target: string | EventTarget, event: string, handler: EventListener, options?: AddEventListenerOptions): void {\n if (typeof target === 'string') {\n const selector = target;\n\n const delegatedHandler = (e: Event) => {\n const targetElement = e.target as Element;\n\n if (!targetElement?.matches) return;\n\n const matchedElement = targetElement.matches(selector) ? targetElement : targetElement.closest(selector);\n\n if (matchedElement && this.shadow.contains(matchedElement)) {\n Object.defineProperty(e, 'currentTarget', {\n configurable: true,\n value: matchedElement,\n });\n\n handler.call(matchedElement, e);\n }\n };\n\n this.shadow.addEventListener(event, delegatedHandler, {\n ...options,\n signal: this.#abortController.signal,\n });\n } else {\n target.addEventListener(event, handler, {\n ...options,\n signal: this.#abortController.signal,\n });\n }\n }\n\n /**\n * Dispatch custom event\n * @param name - Event name\n * @param detail - Event detail data\n * @param options - CustomEvent options\n */\n emit(name: string, detail?: unknown, options?: CustomEventInit): void {\n this.dispatchEvent(\n new CustomEvent(name, {\n bubbles: true,\n composed: true,\n detail,\n ...options,\n }),\n );\n }\n\n /**\n * Set timeout with automatic cleanup\n * @param callback - Function to call after delay\n * @param ms - Delay in milliseconds\n * @returns Timeout ID\n */\n delay(callback: () => void, ms: number): number {\n const id = setTimeout(() => {\n this.#timeouts.delete(id);\n callback();\n }, ms);\n\n this.#timeouts.add(id);\n return id;\n }\n\n /**\n * Clear a scheduled timeout\n * @param id - Timeout ID returned from delay()\n */\n clear(id: number): void {\n clearTimeout(id);\n this.#timeouts.delete(id);\n }\n\n /* ==================== Rendering ==================== */\n\n /**\n * Perform rendering with error handling and node cloning\n */\n private performRender(): void {\n const { template } = this.#options;\n\n try {\n let result: string | Node | DocumentFragment;\n if (typeof template === 'function') {\n result = template(this as WebComponent<T, S>);\n } else {\n result = template;\n }\n\n let nodes: Node[];\n if (typeof result === 'string') {\n nodes = Array.from(parseHTML(result).childNodes);\n } else if (result instanceof DocumentFragment) {\n const fragClone = result.cloneNode(true) as DocumentFragment;\n nodes = Array.from(fragClone.childNodes);\n } else {\n nodes = [result.cloneNode(true)];\n }\n\n this.reconcile(this.shadow, nodes);\n\n this.#options.onUpdated?.(this as WebComponent<T, S>);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n const errorStack = error instanceof Error ? error.stack : '';\n\n console.error('[craftit] Render error:', error);\n\n this.shadow.innerHTML = `\n <div style=\"color: red; padding: 1rem; border: 1px solid red; border-radius: 4px; font-family: monospace;\" data-debug=\"render-error\">\n <strong>Render Error</strong>\n <p style=\"margin: 0.5rem 0;\">${errorMessage}</p>\n ${errorStack ? `<details style=\"margin: 0.5rem 0;\"><summary>Stack Trace</summary><pre style=\"overflow: auto; font-size: 0.8em;\">${errorStack}</pre></details>` : ''}\n <button onclick=\"this.getRootNode().host.render()\" style=\"margin-top: 0.5rem; padding: 0.25rem 0.5rem; cursor: pointer;\">\n Retry Render\n </button>\n </div>\n `;\n }\n }\n\n /**\n * Reconcile DOM nodes using index-based diffing\n * @remarks Works well for static/append-only lists. For frequently reordered lists,\n * consider keyed diffing libraries (lit-html, uhtml).\n */\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: DOM reconciliation requires conditional logic\n private reconcile(parent: Node, newNodes: Node[]): void {\n const oldNodes = Array.from(parent.childNodes);\n const maxLength = Math.max(oldNodes.length, newNodes.length);\n\n for (let i = 0; i < maxLength; i++) {\n const oldNode = oldNodes[i];\n const newNode = newNodes[i];\n\n if (!oldNode && newNode) {\n parent.appendChild(newNode);\n continue;\n }\n\n if (oldNode && !newNode) {\n parent.removeChild(oldNode);\n continue;\n }\n\n if (oldNode && newNode) {\n if (oldNode.nodeType !== newNode.nodeType) {\n parent.replaceChild(newNode, oldNode);\n continue;\n }\n\n if (oldNode.nodeType === Node.TEXT_NODE) {\n if (oldNode.textContent !== newNode.textContent) {\n oldNode.textContent = newNode.textContent;\n }\n continue;\n }\n\n if (oldNode instanceof Element && newNode instanceof Element) {\n if (oldNode.tagName !== newNode.tagName) {\n parent.replaceChild(newNode, oldNode);\n continue;\n }\n\n this.updateElement(oldNode, newNode);\n }\n }\n }\n }\n\n /**\n * Update element attributes and children\n */\n private updateElement(oldEl: Element, newEl: Element): void {\n const oldAttrs = Array.from(oldEl.attributes);\n const newAttrs = Array.from(newEl.attributes);\n\n for (const { name } of oldAttrs) {\n if (!newEl.hasAttribute(name)) {\n oldEl.removeAttribute(name);\n }\n }\n\n for (const { name, value } of newAttrs) {\n if (oldEl.getAttribute(name) !== value) {\n oldEl.setAttribute(name, value);\n }\n }\n\n if (oldEl instanceof HTMLInputElement) {\n this.updateInputElement(oldEl, newEl as HTMLInputElement);\n } else if (oldEl instanceof HTMLTextAreaElement) {\n this.updateTextAreaElement(oldEl, newEl as HTMLTextAreaElement);\n } else if (oldEl instanceof HTMLSelectElement) {\n this.updateSelectElement(oldEl, newEl as HTMLSelectElement);\n }\n\n this.reconcile(oldEl, Array.from(newEl.childNodes));\n }\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Form element syncing requires handling multiple input types\n private updateInputElement(oldEl: HTMLInputElement, newEl: Element): void {\n if (oldEl.type === 'checkbox' || oldEl.type === 'radio') {\n const checked = newEl.hasAttribute('checked');\n if (oldEl.checked !== checked) {\n oldEl.checked = checked;\n }\n } else if (oldEl.type === 'file') {\n // File inputs are read-only, skip value sync\n return;\n } else {\n // For text, email, password, number, etc.\n // Sync from attribute if present, otherwise clear\n if (newEl.hasAttribute('value')) {\n const newValue = newEl.getAttribute('value') || '';\n if (oldEl.value !== newValue) {\n oldEl.value = newValue;\n }\n } else if (oldEl.value !== '') {\n // Clear value if no value attribute in the new template\n oldEl.value = '';\n }\n }\n\n // Sync disabled state\n const disabled = newEl.hasAttribute('disabled');\n if (oldEl.disabled !== disabled) {\n oldEl.disabled = disabled;\n }\n\n // Sync readonly state\n const readonly = newEl.hasAttribute('readonly');\n if (oldEl.readOnly !== readonly) {\n oldEl.readOnly = readonly;\n }\n }\n\n private updateTextAreaElement(oldEl: HTMLTextAreaElement, newEl: Element): void {\n // For textarea, value comes from textContent, not attribute\n const newValue = newEl.textContent || '';\n if (oldEl.value !== newValue) {\n oldEl.value = newValue;\n }\n\n // Sync disabled state\n const disabled = newEl.hasAttribute('disabled');\n if (oldEl.disabled !== disabled) {\n oldEl.disabled = disabled;\n }\n\n // Sync readonly state\n const readonly = newEl.hasAttribute('readonly');\n if (oldEl.readOnly !== readonly) {\n oldEl.readOnly = readonly;\n }\n }\n\n private updateSelectElement(oldEl: HTMLSelectElement, newEl: Element): void {\n // Sync disabled state\n const disabled = newEl.hasAttribute('disabled');\n if (oldEl.disabled !== disabled) {\n oldEl.disabled = disabled;\n }\n\n // Select value is synced via reconciling child options\n // After options are reconciled, sync the selected value\n if (newEl.hasAttribute('value')) {\n const newValue = newEl.getAttribute('value') || '';\n if (oldEl.value !== newValue) {\n oldEl.value = newValue;\n }\n }\n }\n}\n\n/* ==================== Public API ==================== */\n\n/**\n * Create a web component class\n * @template T - Root element type\n * @template S - State object type\n * @param options - Component configuration\n * @returns Custom element constructor\n */\nexport const createComponent = <T = HTMLElement, S extends object = object>(\n options: ComponentOptions<T, S>,\n): CustomElementConstructor => {\n class Component extends BaseComponent<T, S> {\n static formAssociated = options.formAssociated;\n\n static get observedAttributes() {\n return options.observedAttributes || [];\n }\n\n constructor() {\n super(options);\n }\n }\n\n return Component as unknown as CustomElementConstructor;\n};\n\n/**\n * Define a custom element\n * @template T - Root element type\n * @template S - State object type\n * @param name - Element tag name (must contain hyphen)\n * @param options - Component configuration\n */\nexport const defineElement = <T = HTMLElement, S extends object = object>(\n name: string,\n options: ComponentOptions<T, S>,\n): void => {\n if (customElements.get(name)) {\n console.warn(`[craftit] Element \"${name}\" already defined`);\n return;\n }\n\n customElements.define(name, createComponent<T, S>(options));\n};\n\n/**\n * Create and define an element in one call\n * @template T - Root element type\n * @template S - State object type\n * @param name - Element tag name (must contain hyphen)\n * @param options - Component configuration\n * @returns Custom element constructor\n */\nexport const element = <T = HTMLElement, S extends object = object>(\n name: string,\n options: ComponentOptions<T, S>,\n): CustomElementConstructor => {\n defineElement(name, options);\n return customElements.get(name)!;\n};\n\n/* ==================== Utilities Export ==================== */\n\n/**\n * HTML template string helper\n * @param strings - Template string array\n * @param values - Template values\n * @returns Interpolated HTML string\n */\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): string => {\n return strings.reduce((result, str, i) => {\n const value = values[i] ?? '';\n return result + str + value;\n }, '');\n};\n\n/**\n * CSS template string helper with CSS variable utilities\n * @param strings - Template string array\n * @param values - Template values\n * @returns Interpolated CSS string\n * @example css`.button { color: ${color}; }`\n */\n\n/** Type helper for theme variable proxy */\ntype ThemeVars<T extends Record<string, string | number>> = {\n [K in keyof T]: string;\n};\n\nexport const css = Object.assign(\n (strings: TemplateStringsArray, ...values: unknown[]): string => {\n return strings.reduce((result, str, i) => {\n const value = values[i] ?? '';\n return result + str + value;\n }, '');\n },\n {\n /**\n * Create a typed theme with CSS variables and autocomplete\n *\n * Single theme mode:\n * Returns a typed proxy with autocomplete for all theme properties\n *\n * Light/dark mode:\n * Returns the same typed proxy - CSS handles which theme applies via media queries\n * You reference variables the same way regardless of theme mode\n *\n * @param light - Theme variables (or light theme for dual-mode)\n * @param dark - Optional dark theme variables\n * @param options - Configuration options\n * @param options.selector - CSS selector (default: ':host')\n * @param options.attribute - Attribute for manual override (default: 'data-theme')\n * @returns Typed proxy with autocomplete\n *\n * @example\n * // Single theme\n * const theme = css.theme({\n * primaryColor: '#3b82f6',\n * spacing: '1rem',\n * });\n *\n * css`\n * ${theme}\n * .button {\n * color: ${theme.primaryColor}; // Autocomplete!\n * padding: ${theme.spacing};\n * }\n * `\n *\n * @example\n * // Light/dark theme - same variable references!\n * const theme = css.theme(\n * { bg: '#fff', text: '#000' }, // Light\n * { bg: '#000', text: '#fff' } // Dark\n * );\n *\n * css`\n * ${theme}\n * .card {\n * background: ${theme.bg}; // Autocomplete! CSS handles light/dark\n * color: ${theme.text}; // Same variable for both themes\n * }\n * `\n */\n theme: (<T extends Record<string, string | number>>(\n light: T,\n dark?: T,\n options?: { selector?: string; attribute?: string },\n ): ThemeVars<T> => {\n const selector = options?.selector ?? ':host';\n\n // Build CSS variables string helper\n const toVars = (obj: T): string => {\n return Object.entries(obj)\n .map(([key, value]) => {\n const cssVar = key.startsWith('--') ? key : `--${toKebab(key)}`;\n return `${cssVar}: ${value};`;\n })\n .join(' ');\n };\n\n let cssRule: string;\n\n if (!dark) {\n // Single theme mode\n cssRule = `${selector} { ${toVars(light)} }`;\n } else {\n // Light/dark mode - generate media queries\n const attr = options?.attribute ?? 'data-theme';\n const lightVars = toVars(light);\n const darkVars = toVars(dark);\n\n cssRule = `\n${selector} { ${lightVars} }\n@media (prefers-color-scheme: dark) {\n ${selector}:not([${attr}=\"light\"]) { ${darkVars} }\n}\n${selector}[${attr}=\"dark\"] { ${darkVars} }\n${selector}[${attr}=\"light\"] { ${lightVars} }\n `.trim();\n }\n\n // Create single typed proxy that references CSS variables\n // The same variable names work for both light and dark themes\n return new Proxy({} as ThemeVars<T>, {\n get(_target, prop) {\n // Handle string coercion for template literals\n if (prop === 'toString' || prop === Symbol.toPrimitive) {\n return () => cssRule;\n }\n\n // Return var() reference for theme properties\n if (typeof prop === 'string' && prop in light) {\n const cssVar = prop.startsWith('--') ? prop : `--${toKebab(prop)}`;\n return `var(${cssVar})`;\n }\n\n return undefined;\n },\n });\n }) as {\n // Single theme overload\n <T extends Record<string, string | number>>(\n vars: T,\n dark?: undefined,\n options?: { selector?: string },\n ): ThemeVars<T>;\n // Light/dark theme overload - same return type!\n <T extends Record<string, string | number>>(\n light: T,\n dark: T,\n options?: { selector?: string; attribute?: string },\n ): ThemeVars<T>;\n },\n /**\n * Reference a CSS custom property with var()\n * Automatically converts camelCase to --kebab-case\n * @param name - Variable name (with or without --)\n * @param fallback - Optional fallback value\n * @returns var() function string\n * @example css.var('primaryColor') // \"var(--primary-color)\"\n */\n var: (name: string, fallback?: string | number): string => {\n const cssVar = name.startsWith('--') ? name : `--${toKebab(name)}`;\n return fallback !== undefined ? `var(${cssVar}, ${fallback})` : `var(${cssVar})`;\n },\n },\n);\n\n/**\n * Conditional class helper\n * @param classes - Object mapping class names to boolean conditions\n * @returns Space-separated class string\n * @example classMap({ active: true, disabled: false }) // 'active'\n */\nexport const classMap = (classes: Record<string, boolean | undefined>): string => {\n return Object.entries(classes)\n .filter(([, value]) => value)\n .map(([key]) => key)\n .join(' ');\n};\n\n/**\n * Conditional style helper\n * @param styles - Object mapping CSS properties to values\n * @returns Semicolon-separated style string\n * @example styleMap({ color: 'red', display: undefined }) // 'color: red'\n */\nexport const styleMap = (styles: Partial<CSSStyleDeclaration>): string => {\n return Object.entries(styles)\n .filter(([, value]) => value != null)\n .map(([key, value]) => `${toKebab(key)}: ${value}`)\n .join('; ');\n};\n\n/* ==================== Testing Utilities ==================== */\n\n/**\n * Attach/mount a component to the DOM and wait for first render\n * @param element - The element to attach\n * @param container - Optional container (defaults to document.body)\n * @returns Promise that resolves when component is mounted and rendered\n * @example\n * const el = document.createElement('my-component');\n * await attach(el);\n * // Component is now in DOM and rendered\n */\nexport async function attach<T extends HTMLElement>(element: T, container: HTMLElement = document.body): Promise<T> {\n container.appendChild(element);\n\n if ('flush' in element && typeof element.flush === 'function') {\n await element.flush();\n } else {\n await new Promise((resolve) => requestAnimationFrame(resolve));\n }\n\n return element;\n}\n\n/**\n * Remove a component from the DOM with cleanup\n * @param element - The element to destroy\n * @example\n * const el = await attach(document.createElement('my-component'));\n * // ... test code ...\n * destroy(el); // Clean removal\n */\nexport function destroy(element: HTMLElement): void {\n element.remove();\n}\n"],"names":["camelCache","toCamel","str","result","_","c","kebabCache","toKebab","parseHTML","html","template","loadStylesheet","style","sheet","createReactiveState","initial","onChange","internalState","proxyCache","createNestedProxy","obj","cached","proxy","target","prop","value","BaseComponent","#abortController","#timeouts","#renderScheduled","#renderSuppressed","#options","#internals","#renderPromiseResolve","#renderPromise","#watchers","#watcherId","options","flags","message","anchor","state","sheets","attrs","attr","watcher","currentValue","id","name","oldValue","newValue","disabled","mode","resolve","patchOrUpdater","newState","key","patch","selector","callback","lastValue","event","handler","delegatedHandler","e","targetElement","matchedElement","detail","ms","nodes","fragClone","error","errorMessage","errorStack","parent","newNodes","oldNodes","maxLength","i","oldNode","newNode","oldEl","newEl","oldAttrs","newAttrs","checked","readonly","createComponent","Component","defineElement","element","strings","values","css","light","dark","toVars","cssRule","lightVars","darkVars","_target","fallback","cssVar","classMap","classes","styleMap","styles","attach","container","destroy"],"mappings":"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"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./craftit.cjs");exports.attach=e.attach;exports.classMap=e.classMap;exports.createComponent=e.createComponent;exports.css=e.css;exports.defineElement=e.defineElement;exports.destroy=e.destroy;exports.element=e.element;exports.html=e.html;exports.styleMap=e.styleMap;
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attach/mount a component to the DOM and wait for first render
|
|
3
|
+
* @param element - The element to attach
|
|
4
|
+
* @param container - Optional container (defaults to document.body)
|
|
5
|
+
* @returns Promise that resolves when component is mounted and rendered
|
|
6
|
+
* @example
|
|
7
|
+
* const el = document.createElement('my-component');
|
|
8
|
+
* await attach(el);
|
|
9
|
+
* // Component is now in DOM and rendered
|
|
10
|
+
*/
|
|
11
|
+
export declare function attach<T extends HTMLElement>(element: T, container?: HTMLElement): Promise<T>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Callback invoked when an observed attribute changes
|
|
15
|
+
* @template T - Root element type
|
|
16
|
+
* @template S - State object type
|
|
17
|
+
*/
|
|
18
|
+
export declare type AttributeChangeHook<T = HTMLElement, S extends object = object> = (name: string, oldValue: string | null, newValue: string | null, el: WebComponent<T, S>) => void;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Conditional class helper
|
|
22
|
+
* @param classes - Object mapping class names to boolean conditions
|
|
23
|
+
* @returns Space-separated class string
|
|
24
|
+
* @example classMap({ active: true, disabled: false }) // 'active'
|
|
25
|
+
*/
|
|
26
|
+
export declare const classMap: (classes: Record<string, boolean | undefined>) => string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Configuration options for creating a web component
|
|
30
|
+
* @template T - Root element type
|
|
31
|
+
* @template S - State object type
|
|
32
|
+
*/
|
|
33
|
+
export declare type ComponentOptions<T = HTMLElement, S extends object = object> = {
|
|
34
|
+
/** Template for rendering the component */
|
|
35
|
+
template: Template<T, S>;
|
|
36
|
+
/** Initial reactive state */
|
|
37
|
+
state?: S;
|
|
38
|
+
/** CSS styles (strings or CSSStyleSheet objects) */
|
|
39
|
+
styles?: (string | CSSStyleSheet)[];
|
|
40
|
+
/** Attributes to observe for changes */
|
|
41
|
+
observedAttributes?: readonly string[];
|
|
42
|
+
/** Enable form association (allows component to participate in forms) */
|
|
43
|
+
formAssociated?: boolean;
|
|
44
|
+
/** Called when component is added to DOM */
|
|
45
|
+
onConnected?: LifecycleHook<T, S>;
|
|
46
|
+
/** Called when component is removed from DOM */
|
|
47
|
+
onDisconnected?: LifecycleHook<T, S>;
|
|
48
|
+
/** Called when an observed attribute changes */
|
|
49
|
+
onAttributeChanged?: AttributeChangeHook<T, S>;
|
|
50
|
+
/** Called after each render completes */
|
|
51
|
+
onUpdated?: LifecycleHook<T, S>;
|
|
52
|
+
} & FormCallbacks<T, S>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a web component class
|
|
56
|
+
* @template T - Root element type
|
|
57
|
+
* @template S - State object type
|
|
58
|
+
* @param options - Component configuration
|
|
59
|
+
* @returns Custom element constructor
|
|
60
|
+
*/
|
|
61
|
+
export declare const createComponent: <T = HTMLElement, S extends object = object>(options: ComponentOptions<T, S>) => CustomElementConstructor;
|
|
62
|
+
|
|
63
|
+
export declare const css: ((strings: TemplateStringsArray, ...values: unknown[]) => string) & {
|
|
64
|
+
/**
|
|
65
|
+
* Create a typed theme with CSS variables and autocomplete
|
|
66
|
+
*
|
|
67
|
+
* Single theme mode:
|
|
68
|
+
* Returns a typed proxy with autocomplete for all theme properties
|
|
69
|
+
*
|
|
70
|
+
* Light/dark mode:
|
|
71
|
+
* Returns the same typed proxy - CSS handles which theme applies via media queries
|
|
72
|
+
* You reference variables the same way regardless of theme mode
|
|
73
|
+
*
|
|
74
|
+
* @param light - Theme variables (or light theme for dual-mode)
|
|
75
|
+
* @param dark - Optional dark theme variables
|
|
76
|
+
* @param options - Configuration options
|
|
77
|
+
* @param options.selector - CSS selector (default: ':host')
|
|
78
|
+
* @param options.attribute - Attribute for manual override (default: 'data-theme')
|
|
79
|
+
* @returns Typed proxy with autocomplete
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* // Single theme
|
|
83
|
+
* const theme = css.theme({
|
|
84
|
+
* primaryColor: '#3b82f6',
|
|
85
|
+
* spacing: '1rem',
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* css`
|
|
89
|
+
* ${theme}
|
|
90
|
+
* .button {
|
|
91
|
+
* color: ${theme.primaryColor}; // Autocomplete!
|
|
92
|
+
* padding: ${theme.spacing};
|
|
93
|
+
* }
|
|
94
|
+
* `
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* // Light/dark theme - same variable references!
|
|
98
|
+
* const theme = css.theme(
|
|
99
|
+
* { bg: '#fff', text: '#000' }, // Light
|
|
100
|
+
* { bg: '#000', text: '#fff' } // Dark
|
|
101
|
+
* );
|
|
102
|
+
*
|
|
103
|
+
* css`
|
|
104
|
+
* ${theme}
|
|
105
|
+
* .card {
|
|
106
|
+
* background: ${theme.bg}; // Autocomplete! CSS handles light/dark
|
|
107
|
+
* color: ${theme.text}; // Same variable for both themes
|
|
108
|
+
* }
|
|
109
|
+
* `
|
|
110
|
+
*/
|
|
111
|
+
theme: {
|
|
112
|
+
<T extends Record<string, string | number>>(vars: T, dark?: undefined, options?: {
|
|
113
|
+
selector?: string;
|
|
114
|
+
}): ThemeVars<T>;
|
|
115
|
+
<T extends Record<string, string | number>>(light: T, dark: T, options?: {
|
|
116
|
+
selector?: string;
|
|
117
|
+
attribute?: string;
|
|
118
|
+
}): ThemeVars<T>;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Reference a CSS custom property with var()
|
|
122
|
+
* Automatically converts camelCase to --kebab-case
|
|
123
|
+
* @param name - Variable name (with or without --)
|
|
124
|
+
* @param fallback - Optional fallback value
|
|
125
|
+
* @returns var() function string
|
|
126
|
+
* @example css.var('primaryColor') // "var(--primary-color)"
|
|
127
|
+
*/
|
|
128
|
+
var: (name: string, fallback?: string | number) => string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Define a custom element
|
|
133
|
+
* @template T - Root element type
|
|
134
|
+
* @template S - State object type
|
|
135
|
+
* @param name - Element tag name (must contain hyphen)
|
|
136
|
+
* @param options - Component configuration
|
|
137
|
+
*/
|
|
138
|
+
export declare const defineElement: <T = HTMLElement, S extends object = object>(name: string, options: ComponentOptions<T, S>) => void;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Remove a component from the DOM with cleanup
|
|
142
|
+
* @param element - The element to destroy
|
|
143
|
+
* @example
|
|
144
|
+
* const el = await attach(document.createElement('my-component'));
|
|
145
|
+
* // ... test code ...
|
|
146
|
+
* destroy(el); // Clean removal
|
|
147
|
+
*/
|
|
148
|
+
export declare function destroy(element: HTMLElement): void;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create and define an element in one call
|
|
152
|
+
* @template T - Root element type
|
|
153
|
+
* @template S - State object type
|
|
154
|
+
* @param name - Element tag name (must contain hyphen)
|
|
155
|
+
* @param options - Component configuration
|
|
156
|
+
* @returns Custom element constructor
|
|
157
|
+
*/
|
|
158
|
+
export declare const element: <T = HTMLElement, S extends object = object>(name: string, options: ComponentOptions<T, S>) => CustomElementConstructor;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Callbacks for form-associated custom elements
|
|
162
|
+
* @template T - Root element type
|
|
163
|
+
* @template S - State object type
|
|
164
|
+
*/
|
|
165
|
+
export declare type FormCallbacks<T = HTMLElement, S extends object = object> = {
|
|
166
|
+
/** Invoked when parent form's disabled state changes */
|
|
167
|
+
onFormDisabled?: (disabled: boolean, el: WebComponent<T, S>) => void;
|
|
168
|
+
/** Invoked when parent form is reset */
|
|
169
|
+
onFormReset?: (el: WebComponent<T, S>) => void;
|
|
170
|
+
/** Invoked when browser restores form state (navigation/autocomplete) */
|
|
171
|
+
onFormStateRestore?: (state: string | File | FormData | null, mode: 'restore' | 'autocomplete', el: WebComponent<T, S>) => void;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* HTML template string helper
|
|
176
|
+
* @param strings - Template string array
|
|
177
|
+
* @param values - Template values
|
|
178
|
+
* @returns Interpolated HTML string
|
|
179
|
+
*/
|
|
180
|
+
export declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => string;
|
|
181
|
+
|
|
182
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: Template values can be any type */
|
|
183
|
+
/**
|
|
184
|
+
* Lifecycle hook function called with component instance
|
|
185
|
+
* @template T - Root element type
|
|
186
|
+
* @template S - State object type
|
|
187
|
+
*/
|
|
188
|
+
export declare type LifecycleHook<T = HTMLElement, S extends object = object> = (el: WebComponent<T, S>) => void;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Conditional style helper
|
|
192
|
+
* @param styles - Object mapping CSS properties to values
|
|
193
|
+
* @returns Semicolon-separated style string
|
|
194
|
+
* @example styleMap({ color: 'red', display: undefined }) // 'color: red'
|
|
195
|
+
*/
|
|
196
|
+
export declare const styleMap: (styles: Partial<CSSStyleDeclaration>) => string;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Component template - can be a string, DOM node, or function returning either
|
|
200
|
+
* @template T - Root element type
|
|
201
|
+
* @template S - State object type
|
|
202
|
+
*/
|
|
203
|
+
export declare type Template<T = HTMLElement, S extends object = object> = string | Node | ((el: WebComponent<T, S>) => string | Node | DocumentFragment);
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* CSS template string helper with CSS variable utilities
|
|
207
|
+
* @param strings - Template string array
|
|
208
|
+
* @param values - Template values
|
|
209
|
+
* @returns Interpolated CSS string
|
|
210
|
+
* @example css`.button { color: ${color}; }`
|
|
211
|
+
*/
|
|
212
|
+
/** Type helper for theme variable proxy */
|
|
213
|
+
declare type ThemeVars<T extends Record<string, string | number>> = {
|
|
214
|
+
[K in keyof T]: string;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Web component instance interface
|
|
219
|
+
* @template T - Root element type (first child in shadow DOM)
|
|
220
|
+
* @template S - State object type
|
|
221
|
+
*/
|
|
222
|
+
export declare type WebComponent<T = HTMLElement, S extends object = object> = HTMLElement & {
|
|
223
|
+
/** Reactive state object (changes trigger re-renders) */
|
|
224
|
+
readonly state: S;
|
|
225
|
+
/** Shadow DOM root */
|
|
226
|
+
readonly shadow: ShadowRoot;
|
|
227
|
+
/** First element in shadow DOM */
|
|
228
|
+
readonly root: T;
|
|
229
|
+
/** ElementInternals (only when formAssociated: true) */
|
|
230
|
+
readonly internals?: ElementInternals;
|
|
231
|
+
/** Form value (only when formAssociated: true) */
|
|
232
|
+
value?: string;
|
|
233
|
+
/** Schedule a render in the next animation frame */
|
|
234
|
+
render(): void;
|
|
235
|
+
/** Wait for the pending render to complete */
|
|
236
|
+
flush(): Promise<void>;
|
|
237
|
+
/** Update state (merge, replace, or via function) */
|
|
238
|
+
set(patch: Partial<S> | ((state: S) => S | Promise<S>), options?: {
|
|
239
|
+
replace?: boolean;
|
|
240
|
+
silent?: boolean;
|
|
241
|
+
}): Promise<void>;
|
|
242
|
+
/** Watch a state slice and react to changes */
|
|
243
|
+
watch<U>(selector: (state: S) => U, callback: (value: U, prev: U) => void): () => void;
|
|
244
|
+
/** Find a single element in shadow DOM */
|
|
245
|
+
find<E extends Element = Element>(selector: string): E | null;
|
|
246
|
+
/** Find all matching elements in shadow DOM */
|
|
247
|
+
findAll<E extends Element = Element>(selector: string): E[];
|
|
248
|
+
/** Add event listener with automatic cleanup and delegation support */
|
|
249
|
+
on(target: string | EventTarget, event: string, handler: EventListener, options?: AddEventListenerOptions): void;
|
|
250
|
+
/** Dispatch custom event */
|
|
251
|
+
emit(name: string, detail?: unknown, options?: CustomEventInit): void;
|
|
252
|
+
/** Set timeout with automatic cleanup */
|
|
253
|
+
delay(callback: () => void, ms: number): number;
|
|
254
|
+
/** Clear scheduled timeout */
|
|
255
|
+
clear(id: number): void;
|
|
256
|
+
/** Form utilities (only when formAssociated: true) */
|
|
257
|
+
form?: {
|
|
258
|
+
/** Set form value */
|
|
259
|
+
value(value: string | File | FormData | null, state?: File | FormData | null): void;
|
|
260
|
+
/** Set a validation state */
|
|
261
|
+
valid(flags?: ValidityStateFlags, message?: string, anchor?: HTMLElement): void;
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { attach as a, classMap as s, createComponent as l, css as m, defineElement as n, destroy as o, element as c, html as p, styleMap as r } from "./craftit.js";
|
|
2
|
+
export {
|
|
3
|
+
a as attach,
|
|
4
|
+
s as classMap,
|
|
5
|
+
l as createComponent,
|
|
6
|
+
m as css,
|
|
7
|
+
n as defineElement,
|
|
8
|
+
o as destroy,
|
|
9
|
+
c as element,
|
|
10
|
+
p as html,
|
|
11
|
+
r as styleMap
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vielzeug/craftit",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist"
|
|
7
|
+
],
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && vite build",
|
|
19
|
+
"fix": "biome check --write --unsafe src",
|
|
20
|
+
"lint": "biome check src",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"preview": "vite preview",
|
|
23
|
+
"test": "vitest"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"registry": "https://registry.npmjs.org/"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"typescript": "~5.9.3",
|
|
31
|
+
"vite": "^7.3.1",
|
|
32
|
+
"vite-plugin-dts": "^4.5.4",
|
|
33
|
+
"vitest": "^4.0.18"
|
|
34
|
+
}
|
|
35
|
+
}
|