Skip to content

Commit 1e2adb2

Browse files
committed
v1.0.0-RC.3
1 parent 5981514 commit 1e2adb2

File tree

19 files changed

+444
-549
lines changed

19 files changed

+444
-549
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!-- This is auto-generated by Datastar. DO NOT EDIT. -->
2-
[![Version](https://img.shields.io/badge/version-1.0.0–RC.2-orange)](https://github.com/starfederation/datastar/releases)
2+
[![Version](https://img.shields.io/badge/version-1.0.0–RC.3-orange)](https://github.com/starfederation/datastar/releases)
33
[![License](https://img.shields.io/github/license/starfederation/datastar)](https://github.com/starfederation/datastar/blob/main/LICENSE.md)
44
[![Stars](https://img.shields.io/github/stars/starfederation/datastar?style=flat)](https://github.com/starfederation/datastar/stargazers)
55

@@ -11,7 +11,7 @@
1111

1212
Datastar helps you build reactive web applications with the simplicity of server-side rendering and the power of a full-stack SPA framework.
1313

14-
Getting started is as easy as adding a single 10.54 KiB script tag to your HTML.
14+
Getting started is as easy as adding a single 10.68 KiB script tag to your HTML.
1515

1616
```html
1717
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/datastar@main/bundles/datastar.js"></script>
@@ -39,4 +39,4 @@ Read the [Getting Started Guide »](https://data-star.dev/guide/getting_started)
3939

4040
Read the [Contribution Guidelines »](https://github.com/starfederation/datastar/blob/develop/CONTRIBUTING.md)
4141

42-
[![Star History Chart](https://api.star-history.com/svg?repos=starfederation/datastar&type=Date)](https://www.star-history.com/#starfederation/datastar&Date)
42+
[![Star History Chart](https://api.star-history.com/svg?repos=starfederation/datastar&type=Date)](https://www.star-history.com/#starfederation/datastar&Date)

bundles/datastar-aliased.js

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundles/datastar-aliased.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundles/datastar-core.js

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundles/datastar-core.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundles/datastar.js

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bundles/datastar.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

library/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "@starfederation/datastar",
3-
"author": "Delaney Gillilan",
3+
"author": "Star Federation",
44
"description": "The hypermedia framework.",
5-
"version": "1.0.0-RC.2",
5+
"version": "1.0.0-RC.3",
66
"license": "MIT",
77
"private": false,
88
"homepage": "https://data-star.dev",

library/src/engine/engine.ts

Lines changed: 134 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ let activeSub: ReactiveNode | undefined
8484
const startBatch = (): void => {
8585
batchDepth++
8686
}
87+
8788
const endBatch = (): void => {
8889
if (!--batchDepth) {
8990
flush()
@@ -576,19 +577,17 @@ const isValidLink = (checkLink: Link, sub: ReactiveNode): boolean => {
576577
return false
577578
}
578579

579-
const getPath = <T = any>(path: string): T =>
580-
path.split('.').reduce((acc, key) => acc[key], root) as T
581-
582-
const hasPath = (path: string): boolean =>
583-
peek(
584-
() =>
585-
path
586-
.split('.')
587-
.reduce(
588-
(obj, key) => (obj && Object.hasOwn(obj, key) ? obj[key] : undefined),
589-
root,
590-
) !== undefined,
591-
)
580+
const getPath = <T = any>(path: string): T | undefined => {
581+
let result = root
582+
const split = path.split('.')
583+
for (const path of split) {
584+
if (result == null || !Object.hasOwn(result, path)) {
585+
return
586+
}
587+
result = result[path]
588+
}
589+
return result as T
590+
}
592591

593592
export const DELETE = Symbol('delete')
594593
const deep = (value: any, prefix = ''): any => {
@@ -608,6 +607,9 @@ const deep = (value: any, prefix = ''): any => {
608607
keys()
609608
return deepObj[prop]
610609
} else {
610+
if (typeof prop === 'symbol') {
611+
return deepObj[prop]
612+
}
611613
if (!Object.hasOwn(deepObj, prop) || deepObj[prop]() == null) {
612614
deepObj[prop] = signal('')
613615
dispatch({ [prefix + prop]: '' })
@@ -767,7 +769,10 @@ function filtered(
767769
for (const key in node) {
768770
if (isPojo(node[key])) {
769771
stack.push([node[key], `${prefix + key}.`])
770-
} else if (include.test(prefix + key) && !exclude.test(prefix + key)) {
772+
} else if (
773+
toRegExp(include).test(prefix + key) &&
774+
!toRegExp(exclude).test(prefix + key)
775+
) {
771776
pathObj[prefix + key] = getPath(prefix + key)
772777
}
773778
}
@@ -776,6 +781,14 @@ function filtered(
776781
return pathToObj({}, pathObj)
777782
}
778783

784+
function toRegExp(val: string | RegExp): RegExp {
785+
if (typeof val === 'string') {
786+
return RegExp(val.replace(/^\/|\/$/g, ''))
787+
}
788+
789+
return val
790+
}
791+
779792
const root: Record<string, any> = deep({})
780793

781794
/**
@@ -813,10 +826,12 @@ export function load(...pluginsToLoad: DatastarPlugin[]) {
813826
mergePatch,
814827
peek,
815828
getPath,
816-
hasPath,
817829
startBatch,
818830
endBatch,
831+
initErr: 0 as any,
819832
}
833+
ctx.initErr = initErr.bind(0, ctx)
834+
820835
if (plugin.type === 'action') {
821836
actions[plugin.name] = plugin
822837
} else if (plugin.type === 'attribute') {
@@ -825,7 +840,7 @@ export function load(...pluginsToLoad: DatastarPlugin[]) {
825840
} else if (plugin.type === 'watcher') {
826841
plugin.onGlobalInit?.(ctx)
827842
} else {
828-
throw initErr('InvalidPluginType', ctx)
843+
throw ctx.initErr('InvalidPluginType')
829844
}
830845
}
831846

@@ -850,6 +865,19 @@ function applyEls(els: Iterable<HTMLOrSVG>): void {
850865
}
851866
}
852867

868+
function cleanupEls(els: Iterable<HTMLOrSVG>): void {
869+
for (const el of els) {
870+
const cleanups = removals.get(el)
871+
// If removals has el, delete it and run all cleanup functions
872+
if (removals.delete(el)) {
873+
for (const cleanup of cleanups!.values()) {
874+
cleanup()
875+
}
876+
cleanups!.clear()
877+
}
878+
}
879+
}
880+
853881
// Apply all plugins to the entire DOM or a provided element
854882
export function apply(root: HTMLOrSVG = document.body) {
855883
// Delay applying plugins to give custom plugins a chance to load
@@ -875,90 +903,96 @@ function applyAttributePlugin(
875903
attrKey: string,
876904
value: string,
877905
): void {
878-
const rawKey = camel(alias ? attrKey.slice(alias.length) : attrKey)
879-
const plugin = plugins.find((_, i) => pluginRegexs[i].test(rawKey))
880-
if (plugin) {
881-
// Extract the key and modifiers
882-
let [key, ...rawModifiers] = rawKey.slice(plugin.name.length).split(/__+/)
883-
884-
const hasKey = !!key
885-
if (hasKey) {
886-
key = camel(key)
887-
}
888-
const hasValue = !!value
889-
890-
// Create the runtime context
891-
const ctx: RuntimeContext = {
892-
plugin,
893-
actions,
894-
root,
895-
filtered,
896-
signal,
897-
computed,
898-
effect,
899-
mergePatch,
900-
peek,
901-
getPath,
902-
hasPath,
903-
startBatch,
904-
endBatch,
905-
el,
906-
rawKey,
907-
key,
908-
value,
909-
mods: new Map(),
910-
runtimeErr: 0 as any,
911-
rx: 0 as any,
912-
}
913-
ctx.runtimeErr = runtimeErr.bind(0, ctx)
914-
if (plugin.shouldEvaluate === undefined || plugin.shouldEvaluate === true) {
915-
ctx.rx = generateReactiveExpression(ctx)
916-
}
917-
918-
// Check the requirements
919-
const keyReq = plugin.keyReq || 'allowed'
920-
if (hasKey) {
921-
if (keyReq === 'denied') {
922-
throw ctx.runtimeErr(`${plugin.name}KeyNotAllowed`)
906+
if (attrKey.startsWith(alias)) {
907+
const rawKey = camel(alias ? attrKey.slice(alias.length) : attrKey)
908+
const plugin = plugins.find((_, i) => pluginRegexs[i].test(rawKey))
909+
if (plugin) {
910+
// Extract the key and modifiers
911+
let [key, ...rawModifiers] = rawKey.slice(plugin.name.length).split(/__+/)
912+
913+
const hasKey = !!key
914+
if (hasKey) {
915+
key = camel(key)
916+
}
917+
const hasValue = !!value
918+
919+
// Create the runtime context
920+
const ctx: RuntimeContext = {
921+
plugin,
922+
actions,
923+
root,
924+
filtered,
925+
signal,
926+
computed,
927+
effect,
928+
mergePatch,
929+
peek,
930+
getPath,
931+
startBatch,
932+
endBatch,
933+
initErr: 0 as any,
934+
el,
935+
rawKey,
936+
key,
937+
value,
938+
mods: new Map(),
939+
runtimeErr: 0 as any,
940+
rx: 0 as any,
941+
}
942+
ctx.initErr = initErr.bind(0, ctx)
943+
ctx.runtimeErr = runtimeErr.bind(0, ctx)
944+
if (
945+
plugin.shouldEvaluate === undefined ||
946+
plugin.shouldEvaluate === true
947+
) {
948+
ctx.rx = generateReactiveExpression(ctx)
923949
}
924-
} else if (keyReq === 'must') {
925-
throw ctx.runtimeErr(`${plugin.name}KeyRequired`)
926-
}
927950

928-
const valReq = plugin.valReq || 'allowed'
929-
if (hasValue) {
930-
if (valReq === 'denied') {
931-
throw ctx.runtimeErr(`${plugin.name}ValueNotAllowed`)
951+
// Check the requirements
952+
const keyReq = plugin.keyReq || 'allowed'
953+
if (hasKey) {
954+
if (keyReq === 'denied') {
955+
throw ctx.runtimeErr(`${plugin.name}KeyNotAllowed`)
956+
}
957+
} else if (keyReq === 'must') {
958+
throw ctx.runtimeErr(`${plugin.name}KeyRequired`)
932959
}
933-
} else if (valReq === 'must') {
934-
throw ctx.runtimeErr(`${plugin.name}ValueRequired`)
935-
}
936960

937-
// Check for exclusive requirements
938-
if (keyReq === 'exclusive' || valReq === 'exclusive') {
939-
if (hasKey && hasValue) {
940-
throw ctx.runtimeErr(`${plugin.name}KeyAndValueProvided`)
961+
const valReq = plugin.valReq || 'allowed'
962+
if (hasValue) {
963+
if (valReq === 'denied') {
964+
throw ctx.runtimeErr(`${plugin.name}ValueNotAllowed`)
965+
}
966+
} else if (valReq === 'must') {
967+
throw ctx.runtimeErr(`${plugin.name}ValueRequired`)
941968
}
942-
if (!hasKey && !hasValue) {
943-
throw ctx.runtimeErr(`${plugin.name}KeyOrValueRequired`)
969+
970+
// Check for exclusive requirements
971+
if (keyReq === 'exclusive' || valReq === 'exclusive') {
972+
if (hasKey && hasValue) {
973+
throw ctx.runtimeErr(`${plugin.name}KeyAndValueProvided`)
974+
}
975+
if (!hasKey && !hasValue) {
976+
throw ctx.runtimeErr(`${plugin.name}KeyOrValueRequired`)
977+
}
944978
}
945-
}
946979

947-
for (const rawMod of rawModifiers) {
948-
const [label, ...mod] = rawMod.split('.')
949-
ctx.mods.set(camel(label), new Set(mod.map((t) => t.toLowerCase())))
950-
}
980+
for (const rawMod of rawModifiers) {
981+
const [label, ...mod] = rawMod.split('.')
982+
ctx.mods.set(camel(label), new Set(mod.map((t) => t.toLowerCase())))
983+
}
951984

952-
const cleanup = plugin.onLoad(ctx)
953-
if (cleanup) {
954-
let cleanups = removals.get(el)
955-
if (cleanups) {
956-
cleanups.get(rawKey)?.()
957-
} else {
958-
cleanups = new Map()
959-
removals.set(el, cleanups)
985+
const cleanup = plugin.onLoad(ctx)
986+
if (cleanup) {
987+
let cleanups = removals.get(el)
988+
if (cleanups) {
989+
cleanups.get(rawKey)?.()
990+
} else {
991+
cleanups = new Map()
992+
removals.set(el, cleanups)
993+
}
994+
cleanups.set(rawKey, cleanup)
960995
}
961-
cleanups.set(rawKey, cleanup)
962996
}
963997
}
964998
}
@@ -977,14 +1011,8 @@ function observe(mutations: MutationRecord[]) {
9771011
if (type === 'childList') {
9781012
for (const node of removedNodes) {
9791013
if (isHTMLOrSVG(node)) {
980-
const cleanups = removals.get(node)
981-
// If removals has el, delete it and run all cleanup functions
982-
if (removals.delete(node)) {
983-
for (const cleanup of cleanups!.values()) {
984-
cleanup()
985-
}
986-
cleanups!.clear()
987-
}
1014+
cleanupEls([node])
1015+
cleanupEls(node.querySelectorAll<HTMLOrSVG>('*'))
9881016
}
9891017
}
9901018

@@ -1072,13 +1100,17 @@ function generateReactiveExpression(
10721100
// $['foo'] → $['foo']
10731101
// $foo[obj.bar] → $['foo'][obj.bar]
10741102
// $foo['bar.baz'] → $['foo']['bar.baz']
1103+
// $1 → $['1']
1104+
// $123 → $['123']
1105+
// $foo.0.name → $['foo']['0']['name']
1106+
// $foo.0.1.2.bar.0 → $['foo']['0']['1']['2']['bar']['0']
10751107

10761108
// Transform all signal patterns
10771109
expr = expr
10781110
// $['x'] → $x (normalize existing bracket notation)
1079-
.replace(/\$\['([a-zA-Z_$][\w$]*)'\]/g, '$$$1')
1111+
.replace(/\$\['([a-zA-Z_$\d][\w$]*)'\]/g, '$$$1')
10801112
// $x → $['x'] (including dots and hyphens)
1081-
.replace(/\$([a-zA-Z_]\w*(?:[.-]\w+)*)/g, (_, signalName) => {
1113+
.replace(/\$([a-zA-Z_\d]\w*(?:[.-]\w+)*)/g, (_, signalName) => {
10821114
const parts = signalName.split('.')
10831115
return parts.reduce(
10841116
(acc: string, part: string) => `${acc}['${part}']`,
@@ -1087,7 +1119,7 @@ function generateReactiveExpression(
10871119
})
10881120
// $ inside brackets: [$x] → [$['x']]
10891121
.replace(
1090-
/\[(\$[a-zA-Z_]\w*)\]/g,
1122+
/\[(\$[a-zA-Z_\d]\w*)\]/g,
10911123
(_, varName) => `[$['${varName.slice(1)}']]`,
10921124
)
10931125

library/src/engine/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export function internalErr(from: string, reason: string, args = {}) {
2525
return dserr('internal', reason, Object.assign({ from }, args))
2626
}
2727

28-
export function initErr(reason: string, ctx: InitContext, metadata = {}) {
28+
export function initErr(ctx: InitContext, reason: string, metadata = {}) {
2929
const errCtx = {
3030
plugin: {
3131
name: ctx.plugin.name,

0 commit comments

Comments
 (0)