From 9182afaecb345ad2ddf461fcf8f7dbe6962e1176 Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Tue, 29 Jul 2025 23:22:05 -0400 Subject: [PATCH 1/2] Less case modifications for keys --- library/src/engine/engine.ts | 19 +++++++---------- library/src/plugins/attributes/attr.ts | 2 -- library/src/plugins/attributes/class.ts | 4 ++-- library/src/plugins/attributes/jsonSignals.ts | 2 +- library/src/plugins/attributes/on.ts | 5 ++--- library/src/plugins/attributes/onIntersect.ts | 2 +- library/src/plugins/attributes/onInterval.ts | 2 +- library/src/plugins/attributes/onLoad.ts | 2 +- .../src/plugins/attributes/onSignalPatch.ts | 2 +- library/src/plugins/attributes/style.ts | 2 -- library/src/utils/text.ts | 21 +++++++++++-------- 11 files changed, 29 insertions(+), 34 deletions(-) diff --git a/library/src/engine/engine.ts b/library/src/engine/engine.ts index 292f78a8..bb77a6f9 100644 --- a/library/src/engine/engine.ts +++ b/library/src/engine/engine.ts @@ -1,6 +1,6 @@ import { isHTMLOrSVG } from '../utils/dom' import { isEmpty, isPojo, pathToObj } from '../utils/paths' -import { camel, snake } from '../utils/text' +import { snake } from '../utils/text' import { DATASTAR, DSP, DSS } from './consts' import { initErr, runtimeErr } from './errors' import type { @@ -851,7 +851,7 @@ export function load(...pluginsToLoad: DatastarPlugin[]) { return a.name.localeCompare(b.name) }) - pluginRegexs = plugins.map((plugin) => RegExp(`^${plugin.name}([A-Z]|_|$)`)) + pluginRegexs = plugins.map((plugin) => RegExp(`^${plugin.name}(-|$)`)) } function applyEls(els: Iterable): void { @@ -859,7 +859,7 @@ function applyEls(els: Iterable): void { for (const el of els) { if (!el.closest(ignore)) { for (const key in el.dataset) { - applyAttributePlugin(el, key, el.dataset[key]!) + applyAttributePlugin(el, key.replace(/[A-Z]/g, '-$&').toLowerCase(), el.dataset[key]!) } } } @@ -903,17 +903,14 @@ function applyAttributePlugin( attrKey: string, value: string, ): void { - if (attrKey.startsWith(alias)) { - const rawKey = camel(alias ? attrKey.slice(alias.length) : attrKey) + if (!alias || attrKey.startsWith(alias + '-')) { + const rawKey = alias ? attrKey.slice(alias.length + 1) : attrKey const plugin = plugins.find((_, i) => pluginRegexs[i].test(rawKey)) if (plugin) { // Extract the key and modifiers - let [key, ...rawModifiers] = rawKey.slice(plugin.name.length).split(/__+/) + let [key, ...rawModifiers] = rawKey.slice(plugin.name.length + 1).split(/__+/) const hasKey = !!key - if (hasKey) { - key = camel(key) - } const hasValue = !!value // Create the runtime context @@ -979,7 +976,7 @@ function applyAttributePlugin( for (const rawMod of rawModifiers) { const [label, ...mod] = rawMod.split('.') - ctx.mods.set(camel(label), new Set(mod.map((t) => t.toLowerCase()))) + ctx.mods.set(label, new Set(mod)) } const cleanup = plugin.onLoad(ctx) @@ -1025,7 +1022,7 @@ function observe(mutations: MutationRecord[]) { } else if (type === 'attributes') { // If el has a parent with data-ignore, skip it if (isHTMLOrSVG(target) && !target.closest(ignore)) { - const key = camel(attributeName!.slice(5)) + const key = attributeName!.slice(5) const value = target.getAttribute(attributeName!) if (value === null) { const cleanups = removals.get(target) diff --git a/library/src/plugins/attributes/attr.ts b/library/src/plugins/attributes/attr.ts index 4f77abad..c48ab157 100644 --- a/library/src/plugins/attributes/attr.ts +++ b/library/src/plugins/attributes/attr.ts @@ -3,7 +3,6 @@ // Description: Sets the value of any HTML attribute to an expression, and keeps it in sync. import type { AttributePlugin } from '../../engine/types' -import { kebab } from '../../utils/text' export const Attr: AttributePlugin = { type: 'attribute', @@ -23,7 +22,6 @@ export const Attr: AttributePlugin = { } } - key = kebab(key) const update = key ? () => { observer.disconnect() diff --git a/library/src/plugins/attributes/class.ts b/library/src/plugins/attributes/class.ts index 51b9966e..99e53dde 100644 --- a/library/src/plugins/attributes/class.ts +++ b/library/src/plugins/attributes/class.ts @@ -3,7 +3,7 @@ // Description: Adds or removes a class to or from an element based on an expression. import type { AttributePlugin } from '../../engine/types' -import { kebab, modifyCasing } from '../../utils/text' +import { modifyCasing } from '../../utils/text' export const Class: AttributePlugin = { type: 'attribute', @@ -12,7 +12,7 @@ export const Class: AttributePlugin = { returnsValue: true, onLoad: ({ key, el, effect, mods, rx }) => { if (key) { - key = modifyCasing(kebab(key), mods) + key = modifyCasing(key, mods, 'kebab') } const callback = () => { diff --git a/library/src/plugins/attributes/jsonSignals.ts b/library/src/plugins/attributes/jsonSignals.ts index 85fc7db9..19434063 100644 --- a/library/src/plugins/attributes/jsonSignals.ts +++ b/library/src/plugins/attributes/jsonSignals.ts @@ -7,7 +7,7 @@ import { jsStrToObject } from '../../utils/text' export const JsonSignals: AttributePlugin = { type: 'attribute', - name: 'jsonSignals', + name: 'json-signals', keyReq: 'denied', onLoad: ({ el, effect, value, filtered, mods }) => { const spaces = mods.has('terse') ? 0 : 2 diff --git a/library/src/plugins/attributes/on.ts b/library/src/plugins/attributes/on.ts index 097c3567..661aaf24 100644 --- a/library/src/plugins/attributes/on.ts +++ b/library/src/plugins/attributes/on.ts @@ -6,7 +6,7 @@ import { type AttributePlugin, DATASTAR_SIGNAL_PATCH_EVENT, } from '../../engine/types' -import { kebab, modifyCasing } from '../../utils/text' +import { modifyCasing } from '../../utils/text' import { modifyTiming } from '../../utils/timing' import { modifyViewTransition } from '../../utils/view-transitions' import { DATASTAR_FETCH_EVENT } from '../backend/shared' @@ -58,8 +58,7 @@ export const On: AttributePlugin = { } } // Default to kebab-case and allow modifying - let eventName = kebab(key) - eventName = modifyCasing(eventName, mods) + let eventName = modifyCasing(key, mods, 'kebab') // Listen for Datastar events on the document if ( eventName === DATASTAR_FETCH_EVENT || diff --git a/library/src/plugins/attributes/onIntersect.ts b/library/src/plugins/attributes/onIntersect.ts index eeaaa73c..67479faf 100644 --- a/library/src/plugins/attributes/onIntersect.ts +++ b/library/src/plugins/attributes/onIntersect.ts @@ -10,7 +10,7 @@ const once = new WeakSet() export const OnIntersect: AttributePlugin = { type: 'attribute', - name: 'onIntersect', + name: 'on-intersect', keyReq: 'denied', onLoad: ({ el, mods, rx, startBatch, endBatch }) => { let callback = () => { diff --git a/library/src/plugins/attributes/onInterval.ts b/library/src/plugins/attributes/onInterval.ts index f1c2ec90..23f02dcd 100644 --- a/library/src/plugins/attributes/onInterval.ts +++ b/library/src/plugins/attributes/onInterval.ts @@ -8,7 +8,7 @@ import { modifyViewTransition } from '../../utils/view-transitions' export const OnInterval: AttributePlugin = { type: 'attribute', - name: 'onInterval', + name: 'on-interval', keyReq: 'denied', valReq: 'must', onLoad: ({ mods, rx, startBatch, endBatch }) => { diff --git a/library/src/plugins/attributes/onLoad.ts b/library/src/plugins/attributes/onLoad.ts index f8384a9e..f0881f8d 100644 --- a/library/src/plugins/attributes/onLoad.ts +++ b/library/src/plugins/attributes/onLoad.ts @@ -9,7 +9,7 @@ import { modifyViewTransition } from '../../utils/view-transitions' export const OnLoad: AttributePlugin = { type: 'attribute', - name: 'onLoad', + name: 'on-load', keyReq: 'denied', valReq: 'must', onLoad: ({ rx, mods, startBatch, endBatch }) => { diff --git a/library/src/plugins/attributes/onSignalPatch.ts b/library/src/plugins/attributes/onSignalPatch.ts index 454041b1..ab82a584 100644 --- a/library/src/plugins/attributes/onSignalPatch.ts +++ b/library/src/plugins/attributes/onSignalPatch.ts @@ -14,7 +14,7 @@ import { modifyTiming } from '../../utils/timing' export const OnSignalPatch: AttributePlugin = { type: 'attribute', - name: 'onSignalPatch', + name: 'on-signal-patch', valReq: 'must', argNames: ['patch'], returnsValue: true, diff --git a/library/src/plugins/attributes/style.ts b/library/src/plugins/attributes/style.ts index e89874ac..0f02dff4 100644 --- a/library/src/plugins/attributes/style.ts +++ b/library/src/plugins/attributes/style.ts @@ -14,8 +14,6 @@ export const Style: AttributePlugin = { const { style } = el const initialStyles = new Map() - key &&= kebab(key) - const apply = (prop: string, value: any) => { const initial = initialStyles.get(prop) if (!value && value !== 0) { diff --git a/library/src/utils/text.ts b/library/src/utils/text.ts index a9070232..289ce388 100644 --- a/library/src/utils/text.ts +++ b/library/src/utils/text.ts @@ -9,14 +9,8 @@ export const kebab = (str: string) => .replace(/([0-9]+)([a-z])/gi, '$1-$2') .toLowerCase() -export const camel = (str: string) => - kebab(str).replace(/-./g, (x) => x[1].toUpperCase()) - export const snake = (str: string) => kebab(str).replace(/-/g, '_') -export const pascal = (str: string) => - camel(str).replace(/(^.|(?<=\.).)/g, (x) => x[0].toUpperCase()) - export const jsStrToObject = (raw: string) => { try { return JSON.parse(raw) @@ -27,10 +21,19 @@ export const jsStrToObject = (raw: string) => { } } -const caseFns: Record string> = { kebab, snake, pascal } +// The case mods expect the input to be raw attribute names (kebab-case) +export const modCamel = (str: string) => + str.replace(/-[a-z]/g, (x) => x[1].toUpperCase()) + +export const modSnake = (str: string) => str.replace(/-/g, '_') + +export const modPascal = (str: string) => + str[0].toUpperCase() + modCamel(str.slice(1)) + +const caseFns: Record string> = { camel: modCamel, snake: modSnake, pascal: modPascal } -export function modifyCasing(str: string, mods: Modifiers) { - for (const c of mods.get('case') || []) { +export function modifyCasing(str: string, mods: Modifiers, defaultCase: string = 'camel') { + for (const c of mods.get('case') || [defaultCase]) { const fn = caseFns[c] if (fn) str = fn(str) } From 4a0496389a4235c736a2fbc5abb215d38b9e9027 Mon Sep 17 00:00:00 2001 From: Chase Sterling Date: Sun, 5 Oct 2025 13:50:43 -0400 Subject: [PATCH 2/2] Update key separator from - to : --- library/src/engine/engine.ts | 2 +- library/src/plugins/attributes/bind.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/engine/engine.ts b/library/src/engine/engine.ts index bb77a6f9..39a20d45 100644 --- a/library/src/engine/engine.ts +++ b/library/src/engine/engine.ts @@ -851,7 +851,7 @@ export function load(...pluginsToLoad: DatastarPlugin[]) { return a.name.localeCompare(b.name) }) - pluginRegexs = plugins.map((plugin) => RegExp(`^${plugin.name}(-|$)`)) + pluginRegexs = plugins.map((plugin) => RegExp(`^${plugin.name}(:|$)`)) } function applyEls(els: Iterable): void { diff --git a/library/src/plugins/attributes/bind.ts b/library/src/plugins/attributes/bind.ts index 5e633615..4e27d738 100644 --- a/library/src/plugins/attributes/bind.ts +++ b/library/src/plugins/attributes/bind.ts @@ -180,7 +180,7 @@ export const Bind: AttributePlugin = { !(el instanceof HTMLSelectElement && el.multiple) ) { const inputs = document.querySelectorAll( - `[${aliasify('bind')}-${key}],[${aliasify('bind')}="${value}"]`, + `[${aliasify('bind')}\\:${key}],[${aliasify('bind')}="${value}"]`, ) as NodeListOf const pathObj: Record = {}