From 20ad2ff9acfb7dafa6f2d3c76c77c26c6aa41db5 Mon Sep 17 00:00:00 2001 From: Delaney Date: Tue, 10 Dec 2024 15:21:06 -0800 Subject: [PATCH] Release/0.21.0 (#348) * scratch no longer has a /tmp dir, move to alpine we need all the files cause Docker COPY doesn't support glob * Update routes_home.templ (#319) Remove two typos * toggleAll & setAll use path instead of regex (#318) * toggleAll & setAll use path instead of regex Fixes #317 * Fix wording --------- Co-authored-by: Ben Croker <57572400+bencroker@users.noreply.github.com> * Update release note * Update go_deeper.md (#320) Minor edits to Signals section, for grammar, clarity * Make retries and backoff configurable (#316) Fixes #190 * Fix links to SDKs * Tweak intro code * Improve getting started guide * Fix and improve getting started guide * Add web components * evt.detail.value not working for third party libs (#326) * evt.detail.value not working for third party libs Fixes #324 * regex is never fun * fix possible xss on errors * Add circular logo * fix clipboard usage on front page * Support closing SSE connection from client side (#327) Fixes #276 * Fix link in readme * refactor: `nodejs`, `npm`, and `pnpm` are needed for the `libpub` task * data-ref bug (#333) * data-ref bug Fixes #331 * Merge branch 'develop' of github.com:starfederation/datastar into delaneyj/issue331 * adds context.WithTimeout to site smoketests (#334) removes got lib refs * Java SDK (#321) * Examples * SDK and Readme * Added build files * Fixed Readme * add SDKs to frontpage * TODONE: BEN! * Importing Datastar (#335) Fixes #329 * bad link * Rename default constant (#330) * Rename default constant * Fix PHP usage * Build * Remove Teleport from docs * Bundler bug (#347) Fixes #339 * make modifiers spec compliant (#346) * make modifiers spec compliant Fixes #345 * `_` for modifier args is now back to `.`. Please let it end * remove unused error * bump version * Release notes and VSCode release * Update VSCode version number * Fix up modifier docs --------- Co-authored-by: Wm Talcott <32972881+codetalcott@users.noreply.github.com> Co-authored-by: Ben Croker <57572400+bencroker@users.noreply.github.com> Co-authored-by: Ben Croker Co-authored-by: Andrew Welch Co-authored-by: zangster300 Co-authored-by: Peter Humulock --- .vscode/launch.json | 11 +- CHANGELOG.md | 14 +- Dockerfile | 5 +- Dockerfile-dev | 6 + README.md | 12 +- VERSION | 2 +- build/consts.go | 20 ++- build/consts_datastar_readme.qtpl | 12 +- build/consts_java.qtpl | 133 +++++++++++++++ build/run.go | 21 +-- bundles/datastar-core.js | 4 +- bundles/datastar-core.js.map | 6 +- bundles/datastar.js | 16 +- bundles/datastar.js.map | 6 +- examples/java/pom.xml | 54 +++++++ .../java/StarFederation/Datastar/Main.java | 67 ++++++++ .../Servlets/ExecuteScriptServlet.java | 45 ++++++ .../Datastar/Servlets/FeedServlet.java | 91 +++++++++++ .../Datastar/Servlets/GetServlet.java | 51 ++++++ .../Datastar/Servlets/LanguageServlet.java | 62 +++++++ .../Servlets/MergeSignalsServlet.java | 55 +++++++ .../Datastar/Servlets/RedirectServlet.java | 45 ++++++ .../Servlets/RemoveNestedSignalsServlet.java | 36 +++++ .../Servlets/RemoveSignalsServlet.java | 35 ++++ .../Servlets/RemoveTargetServlet.java | 43 +++++ .../Datastar/Servlets/TargetServlet.java | 43 +++++ .../src/main/resources/application.properties | 1 + examples/java/src/main/resources/index.html | 84 ++++++++++ go.mod | 7 +- go.sum | 8 + library/README.md | 12 +- library/package.json | 2 +- library/src/bundles/datastar-core.ts | 5 +- library/src/bundles/datastar.ts | 2 +- library/src/engine/consts.ts | 6 +- library/src/engine/engine.ts | 4 +- library/src/engine/errors.ts | 3 +- library/src/engine/nestedSignals.ts | 11 +- library/src/engine/version.ts | 2 +- .../plugins/official/backend/actions/sse.ts | 20 ++- .../backend/watchers/mergeFragments.ts | 4 +- .../backend/watchers/removeFragments.ts | 4 +- .../src/plugins/official/dom/attributes/on.ts | 88 +++++----- .../plugins/official/logic/actions/setAll.ts | 10 +- .../official/logic/actions/toggleAll.ts | 8 +- library/src/utils/dom.ts | 17 +- sdk/README.md | 2 +- sdk/dotnet/src/Consts.fs | 8 +- sdk/dotnet/src/ServerSentEvent.fs | 4 +- sdk/dotnet/src/ServerSentEventGenerator.fs | 4 +- sdk/go/consts.go | 10 +- sdk/go/fragments.go | 8 +- sdk/java/README.md | 151 ++++++++++++++++++ sdk/java/pom.xml | 83 ++++++++++ .../java/StarFederation/Datastar/Consts.java | 47 ++++++ .../Datastar/ServerSentEventGenerator.java | 78 +++++++++ .../request/AbstractRequestAdapter.java | 13 ++ .../request/HttpServletRequestAdapter.java | 33 ++++ .../adapters/request/RequestAdapter.java | 29 ++++ .../response/AbstractResponseAdapter.java | 33 ++++ .../response/HttpServletResponseAdapter.java | 23 +++ .../adapters/response/ResponseAdapter.java | 42 +++++ .../Datastar/enums/EventType.java | 32 ++++ .../Datastar/enums/FragmentMergeMode.java | 41 +++++ .../Datastar/events/AbstractSSEEvent.java | 67 ++++++++ .../Datastar/events/DataStore.java | 74 +++++++++ .../Datastar/events/ExecuteScript.java | 44 +++++ .../Datastar/events/ExecuteScriptOptions.java | 56 +++++++ .../Datastar/events/MergeFragments.java | 62 +++++++ .../events/MergeFragmentsOptions.java | 86 ++++++++++ .../Datastar/events/MergeSignals.java | 46 ++++++ .../Datastar/events/MergeSignalsOptions.java | 45 ++++++ .../Datastar/events/RemoveFragments.java | 45 ++++++ .../events/RemoveFragmentsOptions.java | 65 ++++++++ .../Datastar/events/RemoveSignals.java | 36 +++++ .../Datastar/events/SignalReader.java | 59 +++++++ .../Datastar/unit/DataStoreTest.java | 100 ++++++++++++ .../Datastar/unit/MergeFragmentsTest.java | 88 ++++++++++ .../Datastar/unit/MergeSignalsTest.java | 98 ++++++++++++ sdk/php/src/Consts.php | 10 +- sdk/php/src/events/MergeFragments.php | 4 +- sdk/php/src/events/RemoveFragments.php | 4 +- sdk/php/tests/Unit/MergeFragmentsTest.php | 2 +- sdk/php/tests/Unit/RemoveFragmentsTest.php | 2 +- site/routes_errors.go | 3 + site/routes_examples_active_search.templ | 2 +- site/routes_examples_inline_validation.templ | 8 +- site/routes_examples_model_bindings.templ | 3 + site/routes_examples_signals_ifmissing.go | 9 +- site/routes_home.templ | 10 +- site/shared.templ | 4 +- site/smoketests/custom_events_test.go | 34 ++-- site/smoketests/setup_test.go | 25 +-- site/static/images/circular.png | 3 + site/static/md/errors/InvalidValue.md | 3 - site/static/md/examples/active_search.md | 4 +- site/static/md/examples/bind_keys.md | 12 +- site/static/md/examples/bulk_update.md | 2 +- site/static/md/examples/classes.md | 4 +- site/static/md/examples/custom_events.md | 2 +- .../md/examples/debounce_and_throttle.md | 12 +- site/static/md/examples/dialogs_browser.md | 2 +- site/static/md/examples/offline_sync.md | 8 +- site/static/md/examples/raf_update.md | 2 +- .../md/examples/replace_url_from_signals.md | 6 +- site/static/md/examples/scroll_into_view.md | 4 +- site/static/md/examples/session_storage.md | 6 +- site/static/md/examples/signals_changed.md | 12 +- site/static/md/examples/signals_ifmissing.md | 12 +- site/static/md/guide/getting_started.md | 122 +++++++------- site/static/md/guide/go_deeper.md | 44 +++-- site/static/md/reference/plugins_backend.md | 9 ++ site/static/md/reference/plugins_browser.md | 45 ++---- site/static/md/reference/plugins_core.md | 2 +- site/static/md/reference/plugins_dom.md | 18 ++- site/static/md/reference/plugins_logic.md | 4 +- .../datastar-vscode-0.21.0.vsix | Bin 51955 -> 51601 bytes .../datastar-vscode-0.21.1.vsix | Bin 0 -> 51604 bytes tools/vscode-extension/package-lock.json | 4 +- tools/vscode-extension/package.json | 6 +- .../vscode-extension/src/data-attributes.json | 99 +++++++----- tools/vscode-extension/src/sse-plugin.json | 27 ---- 122 files changed, 2941 insertions(+), 458 deletions(-) create mode 100644 build/consts_java.qtpl create mode 100644 examples/java/pom.xml create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Main.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/ExecuteScriptServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/FeedServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/GetServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/LanguageServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/MergeSignalsServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/RedirectServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/RemoveNestedSignalsServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/RemoveSignalsServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/RemoveTargetServlet.java create mode 100644 examples/java/src/main/java/StarFederation/Datastar/Servlets/TargetServlet.java create mode 100644 examples/java/src/main/resources/application.properties create mode 100644 examples/java/src/main/resources/index.html create mode 100644 sdk/java/README.md create mode 100644 sdk/java/pom.xml create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/Consts.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/ServerSentEventGenerator.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/adapters/request/AbstractRequestAdapter.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/adapters/request/HttpServletRequestAdapter.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/adapters/request/RequestAdapter.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/adapters/response/AbstractResponseAdapter.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/adapters/response/HttpServletResponseAdapter.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/adapters/response/ResponseAdapter.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/enums/EventType.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/enums/FragmentMergeMode.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/AbstractSSEEvent.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/DataStore.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/ExecuteScript.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/ExecuteScriptOptions.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/MergeFragments.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/MergeFragmentsOptions.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/MergeSignals.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/MergeSignalsOptions.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/RemoveFragments.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/RemoveFragmentsOptions.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/RemoveSignals.java create mode 100644 sdk/java/src/main/java/StarFederation/Datastar/events/SignalReader.java create mode 100644 sdk/java/src/test/java/StarFederation/Datastar/unit/DataStoreTest.java create mode 100644 sdk/java/src/test/java/StarFederation/Datastar/unit/MergeFragmentsTest.java create mode 100644 sdk/java/src/test/java/StarFederation/Datastar/unit/MergeSignalsTest.java create mode 100644 site/static/images/circular.png delete mode 100644 site/static/md/errors/InvalidValue.md create mode 100644 tools/vscode-extension/datastar-vscode-0.21.1.vsix delete mode 100644 tools/vscode-extension/src/sse-plugin.json diff --git a/.vscode/launch.json b/.vscode/launch.json index d28343d28..858bdab31 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,22 +9,15 @@ "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceFolder}/code/go/cmd/site/main.go", + "program": "${workspaceFolder}/site/cmd/site/main.go", "preLaunchTask": "build datastar" }, - { - "name": "Smoke Test", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${workspaceFolder}/code/go/cmd/sitesmoketests/main.go" - }, { "name": "Build Library", "type": "go", "request": "launch", "mode": "auto", - "program": "${workspaceFolder}/code/go/cmd/build/main.go", + "program": "${workspaceFolder}/build/cmd/build/main.go", "cwd": "${workspaceFolder}" } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index c41bfacf1..2f05019d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ # WIP Release Notes for Datastar -## 0.21.0-beta - 2024-12-06 +## 0.21.0 - 2024-12-10 We’ve overhauled Datastar in v0.21.0, doubling down on making nestable signals declarative. To that end, we’ve removed special characters, made the API more explicit and consistent, and fixed some restrictions to nested signals that we discovered. Signal values are now accessed in expressions using the syntax `signalName.value`, actions no longer have a prefix, and attribute keys support nested signals using dot-delimited paths. -The new [Datastar VSCode extension](https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode) has autocomplete for all v0.21.0 `data-*` attributes and the `sse` action, and we’ve painstakingly added error pages for every error that can be thrown. +The new [Datastar VSCode extension](https://marketplace.visualstudio.com/items?itemName=starfederation.datastar-vscode) has autocomplete for all v0.21.0 `data-*`, and we’ve painstakingly added error pages for every error that can be thrown. This should be the final round of API changes before v1.0.0 🚀 @@ -16,6 +16,7 @@ This should be the final round of API changes before v1.0.0 🚀 - Added the ability to use a single classes using the syntax `data-class-hidden="foo.value"`. - Added the ability to use a key instead of a value to denote a signal name in the `data-bind`, `data-indicator` and `data-ref` attributes (`data-bind-foo`, `data-indicator-foo`, `data-ref-foo`). - Added error codes and links to descriptions in the console for every error thrown. +- Retries and backoff are now configurable for SSE connections. ### Changed @@ -24,11 +25,12 @@ This should be the final round of API changes before v1.0.0 🚀 - Renamed the `data-store` attribute to `data-signals`. - Renamed the `data-bind` attribute to `data-attributes`. - Renamed the `data-model` attribute to `data-bind`. -- Changed the `data-*` attribute modifier delimiter from `.` to `:` (`data-on-keydown:debounce_100ms:throttle_lead="value"`). -- The the `get()`, `post()`, `put()`, and `delete()` plugins have been replaced by a single `sse()` plugin that accepts the method as an option (`sse(url, {method: 'post'})`), defaulting to `get`. -- The `setAll()` and `toggleAll` plugins now accept a dot-delimited path format, instead of a regular expression. +- Changed the `data-*` attribute modifier delimiter from `.` to `__` for modifiers and from `_` to `.` for arguments. This is to be spec compliant while still parseable with new nested signal syntax (`data-on-keydown__debounce.100ms__throttle.noLead="value"`). +- The the `get()`, `post()`, `put()`, `patch()` and `delete()` plugins have been replaced by a single `sse()` plugin that accepts a method as an option (`sse(url, {method: 'post'})`), defaulting to `get`. +- The `setAll()` and `toggleAll` plugins now accept a path prefix, instead of a regular expression. +- Nested signals no longer allow `__` in the key. It causes a conflict with modifiers. ### Fixed - Fixed headers not merging correctly. -- Fixed new lines in the SDK protocol for paths. +- Fixed new lines in the SDK protocol for paths. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ae9988c05..d2f7e6937 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,13 +4,14 @@ RUN apk add --no-cache upx ENV PORT=8080 WORKDIR /src -COPY go.* *.go ./ +COPY . . RUN go mod download COPY site ./site RUN --mount=type=cache,target=/root/.cache/go-build \ go build -ldflags="-s" -o /out/site site/cmd/site/main.go RUN upx -9 -k /out/site -FROM scratch +FROM alpine +RUN chmod a=rwx,u+t /tmp COPY --from=build /out/site / ENTRYPOINT ["/site"] \ No newline at end of file diff --git a/Dockerfile-dev b/Dockerfile-dev index 8379bf102..5c373e7a3 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -14,6 +14,8 @@ RUN apt update && sudo apt upgrade \ git-lfs \ jq \ rsync \ + nodejs \ + npm \ # Needed for headless chrome/tests libglib2.0-dev \ libnss3-dev \ @@ -32,6 +34,10 @@ RUN apt update && sudo apt upgrade \ # Clean out directories that don't need to be part of the image rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ && \ + # Install node packages \ + npm install -g npm@^10.0.0 \ + npm install -g pnpm \ + && \ # Install needed Go tooling \ go install github.com/go-task/task/v3/cmd/task@latest \ && \ diff --git a/README.md b/README.md index 8ce9719c3..37ceb865b 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Read the [Getting Started Guide »](https://data-star.dev/guide/getting_started) ## Contributing -Read the [Contribution Guidelines »](CONTRIBUTING.md) +Read the [Contribution Guidelines »](https://github.com/starfederation/datastar/blob/develop/CONTRIBUTING.md) ## Custom Plugins @@ -51,10 +51,10 @@ You can manually add your own plugins to the core: } + Datastar.load( + // I can make my own plugins! + ) + ``` \ No newline at end of file diff --git a/VERSION b/VERSION index 2c835f025..1db0edecc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.21.0-beta1 \ No newline at end of file +0.21.0 \ No newline at end of file diff --git a/build/consts.go b/build/consts.go index c9d7b00ac..20f99b5d0 100644 --- a/build/consts.go +++ b/build/consts.go @@ -66,13 +66,25 @@ var Consts = &ConstTemplateData{ FileExtension: "go", Name: "Go", Icon: "vscode-icons:file-type-go-gopher", - SdkUrl: "https://github.com/starfederation/datastar/tree/main/code/go/sdk", + SdkUrl: "https://github.com/starfederation/datastar/tree/develop/sdk/go", }, { FileExtension: "php", Name: "PHP", Icon: "vscode-icons:file-type-php2", - SdkUrl: "https://github.com/starfederation/datastar/tree/main/code/php/sdk", + SdkUrl: "https://github.com/starfederation/datastar/tree/develop/sdk/php", + }, + { + FileExtension: "fs", + Name: "Dotnet", + Icon: "vscode-icons:file-type-fsharp2", + SdkUrl: "https://github.com/starfederation/datastar/tree/develop/sdk/dotnet", + }, + { + FileExtension: "java", + Name: "Java", + Icon: "vscode-icons:file-type-java", + SdkUrl: "https://github.com/starfederation/datastar/tree/develop/sdk/java", }, }, DatastarKey: "datastar", @@ -95,8 +107,8 @@ var Consts = &ConstTemplateData{ }, DefaultDurations: []*DefaultDuration{ { - Name: toolbelt.ToCasedString("settleDuration"), - Description: "The default duration for settling during merges. Allows for CSS transitions to complete.", + Name: toolbelt.ToCasedString("fragmentsSettleDuration"), + Description: "The default duration for settling during fragment merges. Allows for CSS transitions to complete.", Duration: 300 * time.Millisecond, }, { diff --git a/build/consts_datastar_readme.qtpl b/build/consts_datastar_readme.qtpl index 78b1ea0ca..052240e92 100644 --- a/build/consts_datastar_readme.qtpl +++ b/build/consts_datastar_readme.qtpl @@ -38,7 +38,7 @@ Read the [Getting Started Guide »](https://data-star.dev/guide/getting_started) ## Contributing -Read the [Contribution Guidelines »](CONTRIBUTING.md) +Read the [Contribution Guidelines »](https://github.com/starfederation/datastar/blob/develop/CONTRIBUTING.md) ## Custom Plugins @@ -53,12 +53,12 @@ You can manually add your own plugins to the core: } + Datastar.load( + // I can make my own plugins! + ) + ``` {%- endfunc -%} diff --git a/build/consts_java.qtpl b/build/consts_java.qtpl new file mode 100644 index 000000000..3ed1988d2 --- /dev/null +++ b/build/consts_java.qtpl @@ -0,0 +1,133 @@ +{%- func javaConsts(data *ConstTemplateData) -%} +package starfederation.datastar; + +import starfederation.datastar.enums.FragmentMergeMode; + +/** + * {%s data.DoNotEdit %} + */ +public final class Consts { + public static final String DATASTAR_KEY = "{%s data.DatastarKey %}"; + public static final String VERSION = "{%s data.Version %}"; + public static final int VERSION_CLIENT_BYTE_SIZE = {%d data.VersionClientByteSize %}; + public static final int VERSION_CLIENT_BYTE_SIZE_GZIP = {%d data.VersionClientByteSizeGzip %}; + {%- for _, d := range data.DefaultDurations %} + // {%s= d.Description %} + public static final int DEFAULT_{%s d.Name.ScreamingSnake %} = {%d durationToMs(d.Duration) %}; + {%- endfor -%} + {%- for _, b := range data.DefaultBools %} + // {%s= b.Description %} + public static final boolean DEFAULT_{%s b.Name.ScreamingSnake %} = {%v b.Value %}; + {%- endfor -%} + {%- for _, s := range data.DefaultStrings %} + // {%s= s.Description %} + public static final String DEFAULT_{%s s.Name.ScreamingSnake %} = "{%s s.Value %}"; + {%- endfor -%} + {%- for _, enum := range data.Enums -%} + {%- if enum.Default != nil %} + // {%s= enum.Description %} + public static final FragmentMergeMode DEFAULT_{%s enum.Name.ScreamingSnake %} = FragmentMergeMode.{%s enum.Default.Name.Pascal %}; + {%- endif -%} + {%- endfor -%} + + // Dataline literals. + {%- for _, literal := range data.DatalineLiterals -%} + public static final String {%s literal.ScreamingSnake %}_DATALINE_LITERAL = "{%s literal.Camel %} "; + {%- endfor -%} +} +{%- endfunc -%} + +{%- func javaEventType(data *ConstTemplateData) -%} +package starfederation.datastar.enums; + +/** + * {%s data.DoNotEdit %} + */ +public enum EventType { +{%- for _, enum := range data.Enums -%} + {%- if enum.Name.Pascal == "EventType" -%} + {%- for i, entry := range enum.Values %}{% if i < len(enum.Values) - 1 %} + // {%s entry.Description %} + {%s entry.Name.Pascal %}("{%s entry.Value %}"), +{%- else -%} + + // {%s entry.Description %} + {%s entry.Name.Pascal %}("{%s entry.Value %}"); + {%- endif -%} +{%- endfor -%} + + private final String value; + + EventType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + {%- endif -%} +{%- endfor -%} +} +{%- endfunc -%} + +{%- func javaFragmentMergeMode(data *ConstTemplateData) -%} +package starfederation.datastar.enums; + +/** + * {%s data.DoNotEdit %} + */ +public enum FragmentMergeMode { +{%- for _, enum := range data.Enums -%} + {%- if enum.Name.Pascal == "FragmentMergeMode" -%} + {%- for i, entry := range enum.Values %}{% if i < len(enum.Values) - 1 %} + // {%s entry.Description %} + {%s entry.Name.Pascal %}("{%s entry.Value %}"), +{%- else -%} + + // {%s entry.Description %} + {%s entry.Name.Pascal %}("{%s entry.Value %}"); + {%- endif -%} +{%- endfor -%} + + private final String value; + + FragmentMergeMode(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + {%- endif -%} +{%- endfor -%} +} +{%- endfunc -%} + +{%- func javaConsoleMode(data *ConstTemplateData) -%} +package starfederation.datastar.enums; + +/** + * {%s data.DoNotEdit %} + */ +public enum ConsoleMode { +{%- for _, enum := range data.Enums -%} + {%- if enum.Name.Pascal == "ConsoleMode" -%} + {%- for _, entry := range enum.Values %} + // {%s entry.Description %} + {%s entry.Name.Pascal %}("{%s entry.Value %}"), + {%- endfor -%} + ; + + private final String value; + + ConsoleMode(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + {%- endif -%} +{%- endfor -%} +} +{%- endfunc -%} \ No newline at end of file diff --git a/build/run.go b/build/run.go index 79839b363..12584b620 100644 --- a/build/run.go +++ b/build/run.go @@ -127,15 +127,18 @@ func writeOutConsts(version string) error { }) templates := map[string]func(data *ConstTemplateData) string{ - "README.md": datastarREADME, - "library/README.md": datastarREADME, - "library/src/engine/consts.ts": datastarClientConsts, - "library/package.json": datastarClientPackageJSON, - "sdk/go/consts.go": goConsts, - "sdk/dotnet/src/Consts.fs": dotnetConsts, - "sdk/php/src/Consts.php": phpConsts, - "sdk/php/src/enums/EventType.php": phpEventType, - "sdk/php/src/enums/FragmentMergeMode.php": phpFragmentMergeMode, + "README.md": datastarREADME, + "library/README.md": datastarREADME, + "library/src/engine/consts.ts": datastarClientConsts, + "library/package.json": datastarClientPackageJSON, + "sdk/go/consts.go": goConsts, + "sdk/dotnet/src/Consts.fs": dotnetConsts, + "sdk/php/src/Consts.php": phpConsts, + "sdk/php/src/enums/EventType.php": phpEventType, + "sdk/php/src/enums/FragmentMergeMode.php": phpFragmentMergeMode, + "sdk/java/src/main/java/StarFederation/Datastar/Consts.java": javaConsts, + "sdk/java/src/main/java/StarFederation/Datastar/enums/EventType.java": javaEventType, + "sdk/java/src/main/java/StarFederation/Datastar/enums/FragmentMergeMode.java": javaFragmentMergeMode, } for path, tmplFn := range templates { diff --git a/bundles/datastar-core.js b/bundles/datastar-core.js index b07644ec8..5ea09e60c 100644 --- a/bundles/datastar-core.js +++ b/bundles/datastar-core.js @@ -1,4 +1,4 @@ -"use strict";(()=>{var _e="computed",j={type:1,name:_e,keyReq:1,valReq:1,onLoad:({key:t,signals:e,genRX:n})=>{let s=n();e.setComputed(t,s)}};var U=t=>t.replace(/(?:^\w|[A-Z]|\b\w)/g,function(e,n){return n==0?e.toLowerCase():e.toUpperCase()}).replace(/\s+/g,""),W=t=>new Function(`return Object.assign({}, ${t})`)();var J={type:1,name:"signals",valReq:1,removeOnLoad:!0,onLoad:t=>{let{key:e,genRX:n,signals:s}=t;if(e!="")s.setValue(e,n()());else{let r=W(t.value);t.value=JSON.stringify(r),s.merge(n()())}}};var z={type:1,name:"star",keyReq:2,valReq:2,onLoad:()=>{alert("YOU ARE PROBABLY OVERCOMPLICATING IT")}};var K={name:"signalValue",type:0,fn:t=>{let e=/(?[\w0-9.]*)((\.value))/gm;return t.replaceAll(e,"ctx.signals.signal('$1').value")}};var H="datastar";var X="0.21.0-beta1";var me={Morph:"morph",Inner:"inner",Outer:"outer",Prepend:"prepend",Append:"append",Before:"before",After:"after",UpsertAttributes:"upsertAttributes"},Le=me.Morph;function Y(t){if(t.id)return t.id;let e=0,n=r=>(e=(e<<5)-e+r,e&e),s=r=>r.split("").forEach(i=>n(i.charCodeAt(0)));for(;t.parentNode;){if(t.id){s(`${t.id}`);break}else if(t===t.ownerDocument.documentElement)s(t.tagName);else{for(let r=1,i=t;i.previousElementSibling;i=i.previousElementSibling,r++)n(r);t=t.parentNode}t=t.parentNode}return H+e}var ve="http://localhost:8080/errors";var u=(t,e)=>{let n=new Error;n.name=`error ${t}`;let s=`${ve}/${t}?${new URLSearchParams(e)}`;return n.message=`for more info see ${s}`,n};var ye=Symbol.for("preact-signals"),g=1,S=2,N=4,b=8,M=16,x=32;function B(){O++}function G(){if(O>1){O--;return}let t,e=!1;for(;T!==void 0;){let n=T;for(T=void 0,L++;n!==void 0;){let s=n._nextBatchedEffect;if(n._nextBatchedEffect=void 0,n._flags&=~S,!(n._flags&b)&&Q(n))try{n._callback()}catch(r){e||(t=r,e=!0)}n=s}}if(L=0,O--,e)throw u("BatchError, error")}var a;var T,O=0,L=0,C=0;function Z(t){if(a===void 0)return;let e=t._node;if(e===void 0||e._target!==a)return e={_version:0,_source:t,_prevSource:a._sources,_nextSource:void 0,_target:a,_prevTarget:void 0,_nextTarget:void 0,_rollbackNode:e},a._sources!==void 0&&(a._sources._nextSource=e),a._sources=e,t._node=e,a._flags&x&&t._subscribe(e),e;if(e._version===-1)return e._version=0,e._nextSource!==void 0&&(e._nextSource._prevSource=e._prevSource,e._prevSource!==void 0&&(e._prevSource._nextSource=e._nextSource),e._prevSource=a._sources,e._nextSource=void 0,a._sources._nextSource=e,a._sources=e),e}function f(t){this._value=t,this._version=0,this._node=void 0,this._targets=void 0}f.prototype.brand=ye;f.prototype._refresh=function(){return!0};f.prototype._subscribe=function(t){this._targets!==t&&t._prevTarget===void 0&&(t._nextTarget=this._targets,this._targets!==void 0&&(this._targets._prevTarget=t),this._targets=t)};f.prototype._unsubscribe=function(t){if(this._targets!==void 0){let e=t._prevTarget,n=t._nextTarget;e!==void 0&&(e._nextTarget=n,t._prevTarget=void 0),n!==void 0&&(n._prevTarget=e,t._nextTarget=void 0),t===this._targets&&(this._targets=n)}};f.prototype.subscribe=function(t){return P(()=>{let e=this.value,n=a;a=void 0;try{t(e)}finally{a=n}})};f.prototype.valueOf=function(){return this.value};f.prototype.toString=function(){return this.value+""};f.prototype.toJSON=function(){return this.value};f.prototype.peek=function(){let t=a;a=void 0;try{return this.value}finally{a=t}};Object.defineProperty(f.prototype,"value",{get(){let t=Z(this);return t!==void 0&&(t._version=this._version),this._value},set(t){if(t!==this._value){if(L>100)throw u("SignalCycleDetected");this._value=t,this._version++,C++,B();try{for(let e=this._targets;e!==void 0;e=e._nextTarget)e._target._notify()}finally{G()}}}});function Q(t){for(let e=t._sources;e!==void 0;e=e._nextSource)if(e._source._version!==e._version||!e._source._refresh()||e._source._version!==e._version)return!0;return!1}function ee(t){for(let e=t._sources;e!==void 0;e=e._nextSource){let n=e._source._node;if(n!==void 0&&(e._rollbackNode=n),e._source._node=e,e._version=-1,e._nextSource===void 0){t._sources=e;break}}}function te(t){let e=t._sources,n;for(;e!==void 0;){let s=e._prevSource;e._version===-1?(e._source._unsubscribe(e),s!==void 0&&(s._nextSource=e._nextSource),e._nextSource!==void 0&&(e._nextSource._prevSource=s)):n=e,e._source._node=e._rollbackNode,e._rollbackNode!==void 0&&(e._rollbackNode=void 0),e=s}t._sources=n}function y(t){f.call(this,void 0),this._fn=t,this._sources=void 0,this._globalVersion=C-1,this._flags=N}y.prototype=new f;y.prototype._refresh=function(){if(this._flags&=~S,this._flags&g)return!1;if((this._flags&(N|x))===x||(this._flags&=~N,this._globalVersion===C))return!0;if(this._globalVersion=C,this._flags|=g,this._version>0&&!Q(this))return this._flags&=~g,!0;let t=a;try{ee(this),a=this;let e=this._fn();(this._flags&M||this._value!==e||this._version===0)&&(this._value=e,this._flags&=~M,this._version++)}catch(e){this._value=e,this._flags|=M,this._version++}return a=t,te(this),this._flags&=~g,!0};y.prototype._subscribe=function(t){if(this._targets===void 0){this._flags|=N|x;for(let e=this._sources;e!==void 0;e=e._nextSource)e._source._subscribe(e)}f.prototype._subscribe.call(this,t)};y.prototype._unsubscribe=function(t){if(this._targets!==void 0&&(f.prototype._unsubscribe.call(this,t),this._targets===void 0)){this._flags&=~x;for(let e=this._sources;e!==void 0;e=e._nextSource)e._source._unsubscribe(e)}};y.prototype._notify=function(){if(!(this._flags&S)){this._flags|=N|S;for(let t=this._targets;t!==void 0;t=t._nextTarget)t._target._notify()}};Object.defineProperty(y.prototype,"value",{get(){if(this._flags&g)throw u("SignalCycleDetected");let t=Z(this);if(this._refresh(),t!==void 0&&(t._version=this._version),this._flags&M)throw u("GetComputedError",{value:this._value});return this._value}});function ne(t){return new y(t)}function se(t){let e=t._cleanup;if(t._cleanup=void 0,typeof e=="function"){B();let n=a;a=void 0;try{e()}catch(s){throw t._flags&=~g,t._flags|=b,$(t),u("CleanupEffectError",{error:s})}finally{a=n,G()}}}function $(t){for(let e=t._sources;e!==void 0;e=e._nextSource)e._source._unsubscribe(e);t._fn=void 0,t._sources=void 0,se(t)}function Se(t){if(a!==this)throw u("EndEffectError");te(this),a=t,this._flags&=~g,this._flags&b&&$(this),G()}function w(t){this._fn=t,this._cleanup=void 0,this._sources=void 0,this._nextBatchedEffect=void 0,this._flags=x}w.prototype._callback=function(){let t=this._start();try{if(this._flags&b||this._fn===void 0)return;let e=this._fn();typeof e=="function"&&(this._cleanup=e)}finally{t()}};w.prototype._start=function(){if(this._flags&g)throw u("SignalCycleDetected");this._flags|=g,this._flags&=~b,se(this),ee(this),B();let t=a;return a=this,Se.bind(this,t)};w.prototype._notify=function(){this._flags&S||(this._flags|=S,this._nextBatchedEffect=T,T=this)};w.prototype._dispose=function(){this._flags|=b,this._flags&g||$(this)};function P(t){let e=new w(t);try{e._callback()}catch(n){throw e._dispose(),u("EffectError",{error:n})}return e._dispose.bind(e)}function re(t,e=!1){let n={};for(let s in t)if(t.hasOwnProperty(s)){let r=t[s];if(r instanceof f){if(e&&s.startsWith("_"))continue;n[s]=r.value}else n[s]=re(r)}return n}function ie(t,e,n=!1){for(let s in e)if(e.hasOwnProperty(s)){let r=e[s];if(r instanceof Object&&!Array.isArray(r))t[s]||(t[s]={}),ie(t[s],r,n);else{if(n&&t[s])continue;t[s]=new f(r)}}}function oe(t,e){for(let n in t)if(t.hasOwnProperty(n)){let s=t[n];s instanceof f?e(n,s):oe(s,e)}}function xe(t,...e){let n={};for(let s of e){let r=s.split("."),i=t,o=n;for(let d=0;dn());this.setSignal(e,s)}value(e){return this.signal(e)?.value}setValue(e,n){let s=this.upsert(e,n);s.value=n}upsert(e,n){let s=e.split("."),r=this._signals;for(let d=0;d{let s;switch(n.type){case 0:this.macros.push(n);break;case 2:let r=n;this.watchers.push(r),s=r.onGlobalInit;break;case 3:this.actions[n.name]=n;break;case 1:let i=n;this.plugins.push(i),s=i.onGlobalInit;break;default:throw u("InvalidPluginType",{name:n.name,type:n.type})}if(s){let r=this;s({get signals(){return r._signals},effect:i=>P(i),actions:this.actions,apply:this.apply.bind(this),cleanup:this.cleanup.bind(this)})}}),this.apply(document.body)}cleanup(e){let n=this.removals.get(e);if(n){for(let s of n.set)s();this.removals.delete(e)}}apply(e){let n=new Set;this.plugins.forEach((s,r)=>{this.walkDownDOM(e,i=>{r||this.cleanup(i);for(let o in i.dataset){if(!o.startsWith(s.name))continue;let c=o.slice(s.name.length),[d,...p]=c.split(":"),h=d.length>0;h&&(d=d[0].toLowerCase()+d.slice(1));let D=`${i.dataset[o]}`||"",_=D,m=_.length>0,l=s.keyReq||0;if(h){if(l===2)throw u(s.name+"KeyNotAllowed")}else if(l===1)throw u(s.name+"KeyRequired");let E=s.valReq||0;if(m){if(E===2)throw u(s.name+"ValueNotAllowed")}else if(E===1)throw u(s.name+"ValueRequired");if(l===3||E===3){if(h&&m)throw u(s.name+"KeyAndValueProvided");if(!h&&!m)throw u(s.name+"KeyOrValueRequired")}i.id.length||(i.id=Y(i)),n.clear();let R=new Map;p.forEach(v=>{let[ge,...he]=v.split("_");R.set(U(ge),new Set(he))});let ue=[...s.macros?.pre||[],...this.macros,...s.macros?.post||[]];for(let v of ue)n.has(v)||(n.add(v),_=v.fn(_));let{actions:ce,apply:fe,cleanup:de}=this,pe=this,F;F={get signals(){return pe._signals},effect:v=>P(v),apply:fe.bind(this),cleanup:de.bind(this),actions:ce,genRX:()=>this.genRX(F,...s.argNames||[]),el:i,rawKey:o,rawValue:D,key:d,value:_,mods:R};let q=s.onLoad(F);q&&(this.removals.has(i)||this.removals.set(i,{id:i.id,set:new Set}),this.removals.get(i).set.add(q)),s?.removeOnLoad&&delete i.dataset[o]}})})}genRX(e,...n){let s=e.value.split(/;|\n/).map(l=>l.trim()).filter(l=>l!=""),r=s.length-1;s[r].startsWith("return")||(s[r]=`return (${s[r]});`);let o=s.join(` +"use strict";(()=>{var _e="computed",U={type:1,name:_e,keyReq:1,valReq:1,onLoad:({key:t,signals:e,genRX:n})=>{let s=n();e.setComputed(t,s)}};var W=t=>t.replace(/(?:^\w|[A-Z]|\b\w)/g,function(e,n){return n==0?e.toLowerCase():e.toUpperCase()}).replace(/\s+/g,""),J=t=>new Function(`return Object.assign({}, ${t})`)();var K={type:1,name:"signals",valReq:1,removeOnLoad:!0,onLoad:t=>{let{key:e,genRX:n,signals:s}=t;if(e!="")s.setValue(e,n()());else{let r=J(t.value);t.value=JSON.stringify(r),s.merge(n()())}}};var z={type:1,name:"star",keyReq:2,valReq:2,onLoad:()=>{alert("YOU ARE PROBABLY OVERCOMPLICATING IT")}};var H={name:"signalValue",type:0,fn:t=>{let e=/(?[\w0-9.]*)((\.value))/gm;return t.replaceAll(e,"ctx.signals.signal('$1').value")}};var X="datastar";var Y="0.21.0";var me={Morph:"morph",Inner:"inner",Outer:"outer",Prepend:"prepend",Append:"append",Before:"before",After:"after",UpsertAttributes:"upsertAttributes"},Le=me.Morph;function Z(t){if(t.id)return t.id;let e=0,n=r=>(e=(e<<5)-e+r,e&e),s=r=>r.split("").forEach(i=>n(i.charCodeAt(0)));for(;t.parentNode;){if(t.id){s(`${t.id}`);break}else if(t===t.ownerDocument.documentElement)s(t.tagName);else{for(let r=1,i=t;i.previousElementSibling;i=i.previousElementSibling,r++)n(r);t=t.parentNode}t=t.parentNode}return X+e}var ve="https://data-star.dev/errors";var u=(t,e)=>{let n=new Error;n.name=`error ${t}`;let s=`${ve}/${t}?${new URLSearchParams(e)}`;return n.message=`for more info see ${s}`,n};var ye=Symbol.for("preact-signals"),g=1,S=2,N=4,b=8,M=16,x=32;function B(){O++}function G(){if(O>1){O--;return}let t,e=!1;for(;T!==void 0;){let n=T;for(T=void 0,L++;n!==void 0;){let s=n._nextBatchedEffect;if(n._nextBatchedEffect=void 0,n._flags&=~S,!(n._flags&b)&&ee(n))try{n._callback()}catch(r){e||(t=r,e=!0)}n=s}}if(L=0,O--,e)throw u("BatchError, error",{error:t})}var a;var T,O=0,L=0,C=0;function Q(t){if(a===void 0)return;let e=t._node;if(e===void 0||e._target!==a)return e={_version:0,_source:t,_prevSource:a._sources,_nextSource:void 0,_target:a,_prevTarget:void 0,_nextTarget:void 0,_rollbackNode:e},a._sources!==void 0&&(a._sources._nextSource=e),a._sources=e,t._node=e,a._flags&x&&t._subscribe(e),e;if(e._version===-1)return e._version=0,e._nextSource!==void 0&&(e._nextSource._prevSource=e._prevSource,e._prevSource!==void 0&&(e._prevSource._nextSource=e._nextSource),e._prevSource=a._sources,e._nextSource=void 0,a._sources._nextSource=e,a._sources=e),e}function f(t){this._value=t,this._version=0,this._node=void 0,this._targets=void 0}f.prototype.brand=ye;f.prototype._refresh=function(){return!0};f.prototype._subscribe=function(t){this._targets!==t&&t._prevTarget===void 0&&(t._nextTarget=this._targets,this._targets!==void 0&&(this._targets._prevTarget=t),this._targets=t)};f.prototype._unsubscribe=function(t){if(this._targets!==void 0){let e=t._prevTarget,n=t._nextTarget;e!==void 0&&(e._nextTarget=n,t._prevTarget=void 0),n!==void 0&&(n._prevTarget=e,t._nextTarget=void 0),t===this._targets&&(this._targets=n)}};f.prototype.subscribe=function(t){return P(()=>{let e=this.value,n=a;a=void 0;try{t(e)}finally{a=n}})};f.prototype.valueOf=function(){return this.value};f.prototype.toString=function(){return this.value+""};f.prototype.toJSON=function(){return this.value};f.prototype.peek=function(){let t=a;a=void 0;try{return this.value}finally{a=t}};Object.defineProperty(f.prototype,"value",{get(){let t=Q(this);return t!==void 0&&(t._version=this._version),this._value},set(t){if(t!==this._value){if(L>100)throw u("SignalCycleDetected");this._value=t,this._version++,C++,B();try{for(let e=this._targets;e!==void 0;e=e._nextTarget)e._target._notify()}finally{G()}}}});function ee(t){for(let e=t._sources;e!==void 0;e=e._nextSource)if(e._source._version!==e._version||!e._source._refresh()||e._source._version!==e._version)return!0;return!1}function te(t){for(let e=t._sources;e!==void 0;e=e._nextSource){let n=e._source._node;if(n!==void 0&&(e._rollbackNode=n),e._source._node=e,e._version=-1,e._nextSource===void 0){t._sources=e;break}}}function ne(t){let e=t._sources,n;for(;e!==void 0;){let s=e._prevSource;e._version===-1?(e._source._unsubscribe(e),s!==void 0&&(s._nextSource=e._nextSource),e._nextSource!==void 0&&(e._nextSource._prevSource=s)):n=e,e._source._node=e._rollbackNode,e._rollbackNode!==void 0&&(e._rollbackNode=void 0),e=s}t._sources=n}function y(t){f.call(this,void 0),this._fn=t,this._sources=void 0,this._globalVersion=C-1,this._flags=N}y.prototype=new f;y.prototype._refresh=function(){if(this._flags&=~S,this._flags&g)return!1;if((this._flags&(N|x))===x||(this._flags&=~N,this._globalVersion===C))return!0;if(this._globalVersion=C,this._flags|=g,this._version>0&&!ee(this))return this._flags&=~g,!0;let t=a;try{te(this),a=this;let e=this._fn();(this._flags&M||this._value!==e||this._version===0)&&(this._value=e,this._flags&=~M,this._version++)}catch(e){this._value=e,this._flags|=M,this._version++}return a=t,ne(this),this._flags&=~g,!0};y.prototype._subscribe=function(t){if(this._targets===void 0){this._flags|=N|x;for(let e=this._sources;e!==void 0;e=e._nextSource)e._source._subscribe(e)}f.prototype._subscribe.call(this,t)};y.prototype._unsubscribe=function(t){if(this._targets!==void 0&&(f.prototype._unsubscribe.call(this,t),this._targets===void 0)){this._flags&=~x;for(let e=this._sources;e!==void 0;e=e._nextSource)e._source._unsubscribe(e)}};y.prototype._notify=function(){if(!(this._flags&S)){this._flags|=N|S;for(let t=this._targets;t!==void 0;t=t._nextTarget)t._target._notify()}};Object.defineProperty(y.prototype,"value",{get(){if(this._flags&g)throw u("SignalCycleDetected");let t=Q(this);if(this._refresh(),t!==void 0&&(t._version=this._version),this._flags&M)throw u("GetComputedError",{value:this._value});return this._value}});function se(t){return new y(t)}function re(t){let e=t._cleanup;if(t._cleanup=void 0,typeof e=="function"){B();let n=a;a=void 0;try{e()}catch(s){throw t._flags&=~g,t._flags|=b,$(t),u("CleanupEffectError",{error:s})}finally{a=n,G()}}}function $(t){for(let e=t._sources;e!==void 0;e=e._nextSource)e._source._unsubscribe(e);t._fn=void 0,t._sources=void 0,re(t)}function Se(t){if(a!==this)throw u("EndEffectError");ne(this),a=t,this._flags&=~g,this._flags&b&&$(this),G()}function w(t){this._fn=t,this._cleanup=void 0,this._sources=void 0,this._nextBatchedEffect=void 0,this._flags=x}w.prototype._callback=function(){let t=this._start();try{if(this._flags&b||this._fn===void 0)return;let e=this._fn();typeof e=="function"&&(this._cleanup=e)}finally{t()}};w.prototype._start=function(){if(this._flags&g)throw u("SignalCycleDetected");this._flags|=g,this._flags&=~b,re(this),te(this),B();let t=a;return a=this,Se.bind(this,t)};w.prototype._notify=function(){this._flags&S||(this._flags|=S,this._nextBatchedEffect=T,T=this)};w.prototype._dispose=function(){this._flags|=b,this._flags&g||$(this)};function P(t){let e=new w(t);try{e._callback()}catch(n){throw e._dispose(),u("EffectError",{error:n})}return e._dispose.bind(e)}function ie(t,e=!1){let n={};for(let s in t)if(t.hasOwnProperty(s)){let r=t[s];if(r instanceof f){if(e&&s.startsWith("_"))continue;n[s]=r.value}else n[s]=ie(r)}return n}function oe(t,e,n=!1){for(let s in e)if(e.hasOwnProperty(s)){if(s.match(/\_\_+/))throw u("InvalidSignalKey",{key:s});let r=e[s];if(r instanceof Object&&!Array.isArray(r))t[s]||(t[s]={}),oe(t[s],r,n);else{if(n&&t[s])continue;t[s]=new f(r)}}}function ae(t,e){for(let n in t)if(t.hasOwnProperty(n)){let s=t[n];s instanceof f?e(n,s):ae(s,e)}}function xe(t,...e){let n={};for(let s of e){let r=s.split("."),i=t,o=n;for(let d=0;dn());this.setSignal(e,s)}value(e){return this.signal(e)?.value}setValue(e,n){let s=this.upsert(e,n);s.value=n}upsert(e,n){let s=e.split("."),r=this._signals;for(let d=0;d{let s;switch(n.type){case 0:this.macros.push(n);break;case 2:let r=n;this.watchers.push(r),s=r.onGlobalInit;break;case 3:this.actions[n.name]=n;break;case 1:let i=n;this.plugins.push(i),s=i.onGlobalInit;break;default:throw u("InvalidPluginType",{name:n.name,type:n.type})}if(s){let r=this;s({get signals(){return r._signals},effect:i=>P(i),actions:this.actions,apply:this.apply.bind(this),cleanup:this.cleanup.bind(this)})}}),this.apply(document.body)}cleanup(e){let n=this.removals.get(e);if(n){for(let s of n.set)s();this.removals.delete(e)}}apply(e){let n=new Set;this.plugins.forEach((s,r)=>{this.walkDownDOM(e,i=>{r||this.cleanup(i);for(let o in i.dataset){if(!o.startsWith(s.name))continue;let c=o.slice(s.name.length),[d,...p]=c.split(/\_\_+/),h=d.length>0;h&&(d=d[0].toLowerCase()+d.slice(1));let V=`${i.dataset[o]}`||"",_=V,m=_.length>0,l=s.keyReq||0;if(h){if(l===2)throw u(s.name+"KeyNotAllowed")}else if(l===1)throw u(s.name+"KeyRequired");let E=s.valReq||0;if(m){if(E===2)throw u(s.name+"ValueNotAllowed")}else if(E===1)throw u(s.name+"ValueRequired");if(l===3||E===3){if(h&&m)throw u(s.name+"KeyAndValueProvided");if(!h&&!m)throw u(s.name+"KeyOrValueRequired")}i.id.length||(i.id=Z(i)),n.clear();let R=new Map;p.forEach(v=>{let[ge,...he]=v.split(".");R.set(W(ge),new Set(he))});let ue=[...s.macros?.pre||[],...this.macros,...s.macros?.post||[]];for(let v of ue)n.has(v)||(n.add(v),_=v.fn(_));let{actions:ce,apply:fe,cleanup:de}=this,pe=this,F;F={get signals(){return pe._signals},effect:v=>P(v),apply:fe.bind(this),cleanup:de.bind(this),actions:ce,genRX:()=>this.genRX(F,...s.argNames||[]),el:i,rawKey:o,rawValue:V,key:d,value:_,mods:R};let j=s.onLoad(F);j&&(this.removals.has(i)||this.removals.set(i,{id:i.id,set:new Set}),this.removals.get(i).set.add(j)),s?.removeOnLoad&&delete i.dataset[o]}})})}genRX(e,...n){let s=e.value.split(/;|\n/).map(l=>l.trim()).filter(l=>l!=""),r=s.length-1;s[r].startsWith("return")||(s[r]=`return (${s[r]});`);let o=s.join(` `),c=/(\w*)\(/gm,d=o.matchAll(c),p=new Set;for(let l of d)p.add(l[1]);let h=Object.keys(this.actions).filter(l=>p.has(l)),_=`${h.map(l=>`const ${l} = ctx.actions.${l}.fn;`).join(` -`)}return (()=> {${o}})()`,m=_.trim();h.forEach(l=>{m=m.replaceAll(l+"(",l+"(ctx,")});try{let l=n||[],E=new Function("ctx",...l,m);return(...R)=>E(e,...R)}catch(l){throw u("GeneratingExpressionFailed",{error:l,fnContent:_})}}walkDownDOM(e,n){if(!e||!(e instanceof HTMLElement||e instanceof SVGElement))return null;for(n(e),e=e.firstElementChild;e;)this.walkDownDOM(e,n),e=e.nextElementSibling}};var ae=new V;ae.load(z,K,J,j);var le=ae;le.load();})(); +`)}return (()=> {${o}})()`,m=_.trim();h.forEach(l=>{m=m.replaceAll(l+"(",l+"(ctx,")});try{let l=n||[],E=new Function("ctx",...l,m);return(...R)=>E(e,...R)}catch(l){throw u("GeneratingExpressionFailed",{error:l,fnContent:_})}}walkDownDOM(e,n){if(!e||!(e instanceof HTMLElement||e instanceof SVGElement))return null;for(n(e),e=e.firstElementChild;e;)this.walkDownDOM(e,n),e=e.nextElementSibling}};var le=new k;le.load(z,H,K,U);var q=le;q.load();var Et=q;})(); //# sourceMappingURL=datastar-core.js.map diff --git a/bundles/datastar-core.js.map b/bundles/datastar-core.js.map index ae32515c0..79f219b0d 100644 --- a/bundles/datastar-core.js.map +++ b/bundles/datastar-core.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../library/src/plugins/official/core/attributes/computed.ts", "../library/src/utils/text.ts", "../library/src/plugins/official/core/attributes/signals.ts", "../library/src/plugins/official/core/attributes/star.ts", "../library/src/plugins/official/core/macros/signals.ts", "../library/src/engine/consts.ts", "../library/src/utils/dom.ts", "../library/src/engine/errors.ts", "../library/src/vendored/preact-core.ts", "../library/src/engine/nestedSignals.ts", "../library/src/engine/engine.ts", "../library/src/engine/index.ts", "../library/src/bundles/datastar-core.ts"], - "sourcesContent": ["import {\n AttributePlugin,\n PluginType,\n Requirement,\n} from \"../../../../engine/types\";\n\nconst name = \"computed\";\nexport const Computed: AttributePlugin = {\n type: PluginType.Attribute,\n name,\n keyReq: Requirement.Must,\n valReq: Requirement.Must,\n onLoad: ({ key, signals, genRX }) => {\n const rx = genRX();\n signals.setComputed(key, rx);\n },\n};\n", "export const isBoolString = (str: string) => str.trim() === \"true\";\n\nexport const kebabize = (str: string) =>\n str.replace(\n /[A-Z]+(?![a-z])|[A-Z]/g,\n ($, ofs) => (ofs ? \"-\" : \"\") + $.toLowerCase(),\n );\n\nexport const camelize = (str: string) =>\n str.replace(/(?:^\\w|[A-Z]|\\b\\w)/g, function (word, index) {\n return index == 0 ? word.toLowerCase() : word.toUpperCase();\n }).replace(/\\s+/g, \"\");\n\nexport const jsStrToObject = (raw: string) => {\n return (new Function(`return Object.assign({}, ${raw})`))();\n};\n", "import {\n AttributePlugin,\n NestedValues,\n PluginType,\n Requirement,\n} from \"../../../../engine/types\";\nimport { jsStrToObject } from \"../../../../utils/text\";\n\nexport const Signals: AttributePlugin = {\n type: PluginType.Attribute,\n name: \"signals\",\n valReq: Requirement.Must,\n removeOnLoad: true,\n onLoad: (ctx) => {\n const { key, genRX, signals } = ctx;\n if (key != \"\") {\n signals.setValue(key, genRX()());\n } else {\n const obj = jsStrToObject(ctx.value);\n ctx.value = JSON.stringify(obj);\n signals.merge(genRX()());\n }\n },\n};\n", "import {\n AttributePlugin,\n PluginType,\n Requirement,\n} from \"../../../../engine/types\";\n\nexport const Star: AttributePlugin = {\n type: PluginType.Attribute,\n name: \"star\",\n keyReq: Requirement.Denied,\n valReq: Requirement.Denied,\n onLoad: () => {\n alert(\"YOU ARE PROBABLY OVERCOMPLICATING IT\");\n },\n};\n", "import { MacroPlugin, PluginType } from \"../../../../engine/types\";\n\nexport const SignalValueMacro: MacroPlugin = {\n name: \"signalValue\",\n type: PluginType.Macro,\n fn: (original: string) => {\n const validJS = /(?[\\w0-9.]*)((\\.value))/gm;\n const sub = `ctx.signals.signal('$1').value`;\n return original.replaceAll(validJS, sub);\n },\n};\n", "// This is auto-generated by Datastar. DO NOT EDIT.\n\nexport const DATASTAR = \"datastar\";\nexport const DATASTAR_EVENT = \"datastar-event\";\nexport const DATASTAR_REQUEST = \"Datastar-Request\";\nexport const VERSION = \"0.21.0-beta1\";\n\n// #region Defaults\n\n// #region Default durations\n\n// The default duration for settling during merges. Allows for CSS transitions to complete.\nexport const DefaultSettleDurationMs = 300;\n// The default duration for retrying SSE on connection reset. This is part of the underlying retry mechanism of SSE.\nexport const DefaultSseRetryDurationMs = 1000;\n\n// #endregion\n\n\n// #region Default strings\n\n// The default attributes for + + + + +
+
+ + +
+

+ Language + + Datastar Example +

+
+ + +
+
This will use executeScript if you open the dev tools console and click the rocket.
+
+ 🚀 +
+ + Trajectory Vector: Trajectory + +
+ +
+ +
+
+ + +
+ + +
+
This will use upsertAttributes fill the user state with data.
+ +
+ + +
+
This will use mergeSignals to merge whatever is in the input field with the output signal.
+ + + +
Output Signal State:
+
+ + +
+
This will use removeSignals to remove the User signal in its entirety.
+ +
+ + +
+
This will use removeSignals to remove the nested parts of the User signal.
+ +
+ + +
+
This will use mergeFragments to morph into a button you can click to remove.
+ +
+
+ + +
+
This is the current local store.
+

+        
+
+
+ + diff --git a/go.mod b/go.mod index 257c980d7..ab971c15c 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/klauspost/compress v1.17.11 github.com/lithammer/fuzzysearch v1.1.8 + github.com/microcosm-cc/bluemonday v1.0.27 github.com/nats-io/nats-server/v2 v2.10.22 github.com/nats-io/nats.go v1.37.0 github.com/samber/lo v1.47.0 @@ -35,13 +36,13 @@ require ( github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/quicktemplate v1.8.0 github.com/wcharczuk/go-chart/v2 v2.1.2 - github.com/ysmood/got v0.40.0 github.com/ysmood/gson v0.7.3 github.com/zeebo/xxh3 v1.0.2 ) require ( github.com/andybalholm/brotli v1.1.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/chewxy/math32 v1.11.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -49,6 +50,7 @@ require ( github.com/dlclark/regexp2 v1.11.4 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/securecookie v1.1.2 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/igrmk/treemap/v2 v2.0.1 // indirect @@ -68,11 +70,12 @@ require ( github.com/segmentio/asm v1.2.0 // indirect github.com/ysmood/fetchup v0.2.4 // indirect github.com/ysmood/goob v0.4.0 // indirect - github.com/ysmood/gop v0.2.0 // indirect + github.com/ysmood/got v0.40.0 // indirect github.com/ysmood/leakless v0.9.0 // indirect golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/image v0.22.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.27.0 // indirect golang.org/x/text v0.20.0 // indirect diff --git a/go.sum b/go.sum index ae6f41feb..1324de225 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQq github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/benbjohnson/hashfs v0.2.2 h1:vFZtksphM5LcnMRFctj49jCUkCc7wp3NP6INyfjkse4= github.com/benbjohnson/hashfs v0.2.2/go.mod h1:7OMXaMVo1YkfiIPxKrl7OXkUTUgWjmsAKyR+E6xDIRM= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -61,6 +63,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= @@ -92,6 +96,8 @@ github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8 github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q= github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ= github.com/nats-io/jwt/v2 v2.7.2 h1:SCRjfDLJ2q8naXp8YlGJJS5/yj3wGSODFYVi4nnwVMw= @@ -194,6 +200,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/library/README.md b/library/README.md index 8ce9719c3..37ceb865b 100644 --- a/library/README.md +++ b/library/README.md @@ -36,7 +36,7 @@ Read the [Getting Started Guide »](https://data-star.dev/guide/getting_started) ## Contributing -Read the [Contribution Guidelines »](CONTRIBUTING.md) +Read the [Contribution Guidelines »](https://github.com/starfederation/datastar/blob/develop/CONTRIBUTING.md) ## Custom Plugins @@ -51,10 +51,10 @@ You can manually add your own plugins to the core: } + Datastar.load( + // I can make my own plugins! + ) + ``` \ No newline at end of file diff --git a/library/package.json b/library/package.json index a8983bd5e..4c19f510b 100644 --- a/library/package.json +++ b/library/package.json @@ -2,7 +2,7 @@ "name": "@starfederation/datastar", "author": "Delaney Gillilan", "description": "Hypermedia first SPA replacement framework", - "version": "0.21.0-beta1", + "version": "0.21.0", "license": "MIT", "private": false, "homepage": "https://data-star.dev", diff --git a/library/src/bundles/datastar-core.ts b/library/src/bundles/datastar-core.ts index 4e631556f..6815a02c3 100644 --- a/library/src/bundles/datastar-core.ts +++ b/library/src/bundles/datastar-core.ts @@ -1,3 +1,4 @@ -import { Datastar } from "../engine"; +import { Datastar as DS } from "../engine"; -Datastar.load(); +DS.load(); +export const Datastar = DS; diff --git a/library/src/bundles/datastar.ts b/library/src/bundles/datastar.ts index 66bd3440c..ca0518291 100644 --- a/library/src/bundles/datastar.ts +++ b/library/src/bundles/datastar.ts @@ -1,5 +1,5 @@ import { Datastar } from "../engine"; -import { ServerSentEvents as SSE } from "../plugins/official/backend/actions/sse"; +import { SSE } from "../plugins/official/backend/actions/sse"; import { Indicator } from "../plugins/official/backend/attributes/indicator"; import { ExecuteScript } from "../plugins/official/backend/watchers/executeScript"; import { MergeFragments } from "../plugins/official/backend/watchers/mergeFragments"; diff --git a/library/src/engine/consts.ts b/library/src/engine/consts.ts index ba26b4dfd..a700be3a7 100644 --- a/library/src/engine/consts.ts +++ b/library/src/engine/consts.ts @@ -3,14 +3,14 @@ export const DATASTAR = "datastar"; export const DATASTAR_EVENT = "datastar-event"; export const DATASTAR_REQUEST = "Datastar-Request"; -export const VERSION = "0.21.0-beta1"; +export const VERSION = "0.21.0"; // #region Defaults // #region Default durations -// The default duration for settling during merges. Allows for CSS transitions to complete. -export const DefaultSettleDurationMs = 300; +// The default duration for settling during fragment merges. Allows for CSS transitions to complete. +export const DefaultFragmentsSettleDurationMs = 300; // The default duration for retrying SSE on connection reset. This is part of the underlying retry mechanism of SSE. export const DefaultSseRetryDurationMs = 1000; diff --git a/library/src/engine/engine.ts b/library/src/engine/engine.ts index 2a5169a5e..ab301a18f 100644 --- a/library/src/engine/engine.ts +++ b/library/src/engine/engine.ts @@ -101,7 +101,7 @@ export class Engine { // Extract the key and value from the dataset const keyRaw = rawKey.slice(p.name.length); - let [key, ...rawModifiers] = keyRaw.split(":"); + let [key, ...rawModifiers] = keyRaw.split(/\_\_+/); const hasKey = key.length > 0; if (hasKey) { @@ -148,7 +148,7 @@ export class Engine { appliedMacros.clear(); const mods: Modifiers = new Map>(); rawModifiers.forEach((m) => { - const [label, ...args] = m.split("_"); + const [label, ...args] = m.split("."); mods.set(camelize(label), new Set(args)); }); const macros = [ diff --git a/library/src/engine/errors.ts b/library/src/engine/errors.ts index 4073cb11c..3f3923758 100644 --- a/library/src/engine/errors.ts +++ b/library/src/engine/errors.ts @@ -1,5 +1,4 @@ -// TODO: set to https://data-star.dev/ -const url = `http://localhost:8080/errors`; +const url = `https://data-star.dev/errors`; export const hasValNonExpr = /([\w0-9.]+)\.value/gm; diff --git a/library/src/engine/nestedSignals.ts b/library/src/engine/nestedSignals.ts index 03c9873d1..4cac3afa5 100644 --- a/library/src/engine/nestedSignals.ts +++ b/library/src/engine/nestedSignals.ts @@ -31,6 +31,10 @@ function mergeNested( ): void { for (const key in values) { if (values.hasOwnProperty(key)) { + if (key.match(/\_\_+/)) { + throw dsErr("InvalidSignalKey", { key }); + } + const value = values[key]; if (value instanceof Object && !Array.isArray(value)) { if (!target[key]) { @@ -177,7 +181,12 @@ export class SignalsRoot { const last = parts[parts.length - 1]; const current = subSignals[last]; - if (!!current) return current as Signal; + if (!!current) { + if (current.value === null || current.value === undefined) { + current.value = value; + } + return current as Signal; + } const signal = new Signal(value); subSignals[last] = signal; diff --git a/library/src/engine/version.ts b/library/src/engine/version.ts index 888685af4..1e3925b0c 100644 --- a/library/src/engine/version.ts +++ b/library/src/engine/version.ts @@ -1 +1 @@ -export const VERSION = '0.21.0-beta1'; +export const VERSION = '0.21.0'; diff --git a/library/src/plugins/official/backend/actions/sse.ts b/library/src/plugins/official/backend/actions/sse.ts index 6b9002f83..7eaa88309 100644 --- a/library/src/plugins/official/backend/actions/sse.ts +++ b/library/src/plugins/official/backend/actions/sse.ts @@ -33,9 +33,13 @@ export type SSEArgs = { headers?: Record; includeLocal?: boolean; openWhenHidden?: boolean; + retryScaler?: number; + retryMaxWaitMs?: number; + retryMaxCount?: number; + abort?: AbortSignal; }; -export const ServerSentEvents: ActionPlugin = { +export const SSE: ActionPlugin = { type: PluginType.Action, name: "sse", fn: async ( @@ -49,12 +53,20 @@ export const ServerSentEvents: ActionPlugin = { headers: userHeaders, includeLocal, openWhenHidden, + retryScaler, + retryMaxWaitMs, + retryMaxCount, + abort, } = Object .assign({ method: "GET", headers: {}, includeLocal: false, - openWhenHidden: false, + openWhenHidden: false, // will keep the request open even if the document is hidden. + retryScaler: 2, // the amount to multiply the retry interval by each time + retryMaxWaitMs: 30_000, // the maximum retry interval in milliseconds + retryMaxCount: 10, // the maximum number of retries before giving up + abort: undefined, }, args); const method = methodAnyCase.toUpperCase(); try { @@ -72,6 +84,10 @@ export const ServerSentEvents: ActionPlugin = { method, headers, openWhenHidden, + retryScaler, + retryMaxWaitMs, + retryMaxCount, + signal: abort, onmessage: (evt) => { if (!evt.event.startsWith(DATASTAR)) { return; diff --git a/library/src/plugins/official/backend/watchers/mergeFragments.ts b/library/src/plugins/official/backend/watchers/mergeFragments.ts index a7d7cea18..87b38da72 100644 --- a/library/src/plugins/official/backend/watchers/mergeFragments.ts +++ b/library/src/plugins/official/backend/watchers/mergeFragments.ts @@ -4,8 +4,8 @@ import { DefaultFragmentMergeMode, + DefaultFragmentsSettleDurationMs, DefaultFragmentsUseViewTransitions, - DefaultSettleDurationMs, EventTypes, FragmentMergeModes, } from "../../../../engine/consts"; @@ -39,7 +39,7 @@ export const MergeFragments: WatcherPlugin = { selector = "", mergeMode = DefaultFragmentMergeMode, settleDuration: settleDurationRaw = - `${DefaultSettleDurationMs}`, + `${DefaultFragmentsSettleDurationMs}`, useViewTransition: useViewTransitionRaw = `${DefaultFragmentsUseViewTransitions}`, }) => { diff --git a/library/src/plugins/official/backend/watchers/removeFragments.ts b/library/src/plugins/official/backend/watchers/removeFragments.ts index 995adfd68..25ace2305 100644 --- a/library/src/plugins/official/backend/watchers/removeFragments.ts +++ b/library/src/plugins/official/backend/watchers/removeFragments.ts @@ -3,8 +3,8 @@ // Description: Remember, SSE is just a regular SSE request but with the ability to send 0-inf messages to the client. import { + DefaultFragmentsSettleDurationMs, DefaultFragmentsUseViewTransitions, - DefaultSettleDurationMs, EventTypes, } from "../../../../engine/consts"; import { dsErr } from "../../../../engine/errors"; @@ -24,7 +24,7 @@ export const RemoveFragments: WatcherPlugin = { EventTypes.RemoveFragments, ({ selector, - settleDuration: settleDurationRaw = `${DefaultSettleDurationMs}`, + settleDuration: settleDurationRaw = `${DefaultFragmentsSettleDurationMs}`, useViewTransition: useViewTransitionRaw = `${DefaultFragmentsUseViewTransitions}`, }) => { if (!!!selector.length) { diff --git a/library/src/plugins/official/dom/attributes/on.ts b/library/src/plugins/official/dom/attributes/on.ts index f5a1dbde2..c6d7c3d98 100644 --- a/library/src/plugins/official/dom/attributes/on.ts +++ b/library/src/plugins/official/dom/attributes/on.ts @@ -3,39 +3,63 @@ // Slug: Add an event listener to an element // Description: This action adds an event listener to an element. The event listener can be triggered by a variety of events, such as clicks, keypresses, and more. The event listener can also be set to trigger only once, or to be passive or capture. The event listener can also be debounced or throttled. The event listener can also be set to trigger only when the event target is outside the element. -import { dsErr } from "../../../../engine/errors"; import { AttributePlugin, PluginType, Requirement, } from "../../../../engine/types"; import { argsHas, argsMs } from "../../../../utils/arguments"; +import { onElementRemoved } from "../../../../utils/dom"; import { kebabize } from "../../../../utils/text"; import { debounce, throttle } from "../../../../utils/timing"; -const knownOnModifiers = new Set([ - "window", - "once", - "passive", - "capture", - "debounce", - "throttle", - "remote", - "outside", -]); +let lastSignalsMarshalled = new Map(); +const EVT = "evt"; export const On: AttributePlugin = { type: PluginType.Attribute, name: "on", keyReq: Requirement.Must, valReq: Requirement.Must, - argNames: ["evt"], + argNames: [EVT], + macros: { + pre: [ + { + // We need to escape the evt in case .value is used + type: PluginType.Macro, + name: "evtEsc", + fn: (original) => { + return original.replaceAll( + /evt.([\w\.]+)value/gm, + "EVT_$1_VALUE", + ); + }, + }, + ], + post: [ + { + // We need to unescape the evt in case .value is used + type: PluginType.Macro, + name: "evtUnesc", + fn: (original) => { + return original.replaceAll( + /EVT_([\w\.]+)_VALUE/gm, + "evt.$1value", + ); + }, + }, + ], + }, onLoad: ({ el, key, genRX, mods, signals, effect }) => { const rx = genRX(); let target: Element | Window | Document = el; if (mods.has("window")) target = window; let callback = (evt?: Event) => { + if (evt) { + if (!mods.has("noPrevent")) evt.preventDefault(); + if (!mods.has("noPropagation")) evt.stopPropagation(); + } rx(evt); }; @@ -64,38 +88,6 @@ export const On: AttributePlugin = { if (mods.has("passive")) evtListOpts.passive = true; if (mods.has("once")) evtListOpts.once = true; - const unknownModifierKeys = [...mods.keys()].filter( - (key) => !knownOnModifiers.has(key), - ); - - unknownModifierKeys.forEach((attrName) => { - const eventValues = mods.get(attrName) || []; - const cb = callback; - const revisedCallback = () => { - const evt = event as any; - const attr = evt[attrName]; - let valid: boolean; - - if (typeof attr === "function") { - valid = attr(...eventValues); - } else if (typeof attr === "boolean") { - valid = attr; - } else if (typeof attr === "string") { - const lowerAttr = attr.toLowerCase().trim(); - const expr = [...eventValues].join("").toLowerCase().trim(); - valid = lowerAttr === expr; - } else { - throw dsErr("InvalidValue", { attrName, key, el }); - } - - if (valid) { - cb(evt); - } - }; - callback = revisedCallback; - }); - - let lastSignalsMarshalled = ""; const eventName = kebabize(key).toLowerCase(); switch (eventName) { case "load": @@ -116,11 +108,15 @@ export const On: AttributePlugin = { }; case "signals-change": + onElementRemoved(el, () => { + lastSignalsMarshalled.delete(el.id); + }); return effect(() => { const onlyRemoteSignals = mods.has("remote"); const current = signals.JSON(false, onlyRemoteSignals); - if (lastSignalsMarshalled !== current) { - lastSignalsMarshalled = current; + const last = lastSignalsMarshalled.get(el.id) || ""; + if (last !== current) { + lastSignalsMarshalled.set(el.id, current); callback(); } }); diff --git a/library/src/plugins/official/logic/actions/setAll.ts b/library/src/plugins/official/logic/actions/setAll.ts index 482a1822c..543e8599d 100644 --- a/library/src/plugins/official/logic/actions/setAll.ts +++ b/library/src/plugins/official/logic/actions/setAll.ts @@ -7,10 +7,10 @@ import { ActionPlugin, PluginType } from "../../../../engine/types"; export const SetAll: ActionPlugin = { type: PluginType.Action, name: "setAll", - fn: (ctx, regexp, newValue) => { - const re = new RegExp(regexp); - ctx.signals.walk( - (name, signal) => re.test(name) && (signal.value = newValue), - ); + fn: ({ signals }, path: string, newValue) => { + signals.walk((name, signal) => { + if (!name.startsWith(path)) return; + signal.value = newValue; + }); }, }; diff --git a/library/src/plugins/official/logic/actions/toggleAll.ts b/library/src/plugins/official/logic/actions/toggleAll.ts index da14c6606..ffe0c3636 100644 --- a/library/src/plugins/official/logic/actions/toggleAll.ts +++ b/library/src/plugins/official/logic/actions/toggleAll.ts @@ -7,10 +7,12 @@ import { ActionPlugin, PluginType } from "../../../../engine/types"; export const ToggleAll: ActionPlugin = { type: PluginType.Action, name: "toggleAll", - fn: (ctx, regexp) => { - const re = new RegExp(regexp); + fn: (ctx, path: string) => { ctx.signals.walk( - (name, signal) => re.test(name) && (signal.value = !signal.value), + (name, signal) => { + if (!name.startsWith(path)) return; + signal.value = !signal.value; + }, ); }, }; diff --git a/library/src/utils/dom.ts b/library/src/utils/dom.ts index 0dfc0946c..16845a907 100644 --- a/library/src/utils/dom.ts +++ b/library/src/utils/dom.ts @@ -31,4 +31,19 @@ export function elUniqId(el: Element) { el = el.parentNode as Element; } return DATASTAR + hash; -} \ No newline at end of file +} + +export function onElementRemoved(element: Element, callback: () => void) { + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const removedNode of mutation.removedNodes) { + if (removedNode === element) { + observer.disconnect(); + callback(); + return; + } + } + } + }); + observer.observe(element.parentNode as Node, { childList: true }); +} diff --git a/sdk/README.md b/sdk/README.md index de22ce85d..09854598e 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -14,7 +14,7 @@ Provide an SDK in a language agnostic way, to that end ### Status - [x] Create a document (this) to allow any one to make a spec compliant SDK for any language or framework -- [x] Provide a [reference implementation](../code/go/sdk) in Go +- [x] Provide a [reference implementation](../sdk/go) in Go - [ ] Provide SDKs for - [ ] JS/TS - [x] PHP diff --git a/sdk/dotnet/src/Consts.fs b/sdk/dotnet/src/Consts.fs index 9e4dfcdd4..06ed26523 100644 --- a/sdk/dotnet/src/Consts.fs +++ b/sdk/dotnet/src/Consts.fs @@ -37,12 +37,12 @@ type EventType = module Consts = let [] DatastarKey = "datastar" - let [] Version = "0.21.0-beta1" - let [] VersionClientByteSize = 33244 - let [] VersionClientByteSizeGzip = 12223 + let [] Version = "0.21.0" + let [] VersionClientByteSize = 33596 + let [] VersionClientByteSizeGzip = 12349 /// Default: TimeSpan.FromMilliseconds 300 - let DefaultSettleDuration = TimeSpan.FromMilliseconds 300 + let DefaultFragmentsSettleDuration = TimeSpan.FromMilliseconds 300 /// Default: TimeSpan.FromMilliseconds 1000 let DefaultSseRetryDuration = TimeSpan.FromMilliseconds 1000 diff --git a/sdk/dotnet/src/ServerSentEvent.fs b/sdk/dotnet/src/ServerSentEvent.fs index 1a2b2736a..8b705f666 100644 --- a/sdk/dotnet/src/ServerSentEvent.fs +++ b/sdk/dotnet/src/ServerSentEvent.fs @@ -69,7 +69,7 @@ module MergeFragmentsOptions = let defaults = { Selector = ValueNone MergeMode = Consts.DefaultFragmentMergeMode - SettleDuration = Consts.DefaultSettleDuration + SettleDuration = Consts.DefaultFragmentsSettleDuration UseViewTransition = Consts.DefaultFragmentsUseViewTransitions EventId = ValueNone Retry = Consts.DefaultSseRetryDuration } @@ -81,7 +81,7 @@ type RemoveFragmentsOptions = Retry: TimeSpan } module RemoveFragmentsOptions = let defaults = - { SettleDuration = Consts.DefaultSettleDuration + { SettleDuration = Consts.DefaultFragmentsSettleDuration UseViewTransition = Consts.DefaultFragmentsUseViewTransitions EventId = ValueNone Retry = Consts.DefaultSseRetryDuration } diff --git a/sdk/dotnet/src/ServerSentEventGenerator.fs b/sdk/dotnet/src/ServerSentEventGenerator.fs index 3bc5d5752..67ce14a84 100644 --- a/sdk/dotnet/src/ServerSentEventGenerator.fs +++ b/sdk/dotnet/src/ServerSentEventGenerator.fs @@ -16,7 +16,7 @@ module ServerSentEventGenerator = DataLines = [| if (options.Selector |> ValueOption.isSome) then $"{Consts.DatastarDatalineSelector} {options.Selector |> ValueOption.get |> Selector.value}" if (options.MergeMode <> Consts.DefaultFragmentMergeMode) then $"{Consts.DatastarDatalineMergeMode} {options.MergeMode |> Consts.FragmentMergeMode.toString}" - if (options.SettleDuration <> Consts.DefaultSettleDuration) then $"{Consts.DatastarDatalineSettleDuration} {options.SettleDuration.Milliseconds}" + if (options.SettleDuration <> Consts.DefaultFragmentsSettleDuration) then $"{Consts.DatastarDatalineSettleDuration} {options.SettleDuration.Milliseconds}" if (options.UseViewTransition <> Consts.DefaultFragmentsUseViewTransitions) then $"{Consts.DatastarDatalineUseViewTransition} %A{options.UseViewTransition}" yield! (fragment |> Utility.splitLine |> Seq.map (fun fragmentLine -> $"{Consts.DatastarDatalineFragments} %s{fragmentLine}")) |] } @@ -29,7 +29,7 @@ module ServerSentEventGenerator = Retry = options.Retry DataLines = [| $"{Consts.DatastarDatalineSelector} {selector |> Selector.value}" - if (options.SettleDuration <> Consts.DefaultSettleDuration) then $"{Consts.DatastarDatalineSettleDuration} {options.SettleDuration.Milliseconds}" + if (options.SettleDuration <> Consts.DefaultFragmentsSettleDuration) then $"{Consts.DatastarDatalineSettleDuration} {options.SettleDuration.Milliseconds}" if (options.UseViewTransition <> Consts.DefaultFragmentsUseViewTransitions) then $"{Consts.DatastarDatalineUseViewTransition} %A{options.UseViewTransition}" |] } |> send env diff --git a/sdk/go/consts.go b/sdk/go/consts.go index 049576c70..c9d39706d 100644 --- a/sdk/go/consts.go +++ b/sdk/go/consts.go @@ -6,14 +6,14 @@ import "time" const ( DatastarKey = "datastar" - Version = "0.21.0-beta1" - VersionClientByteSize = 33244 - VersionClientByteSizeGzip = 12223 + Version = "0.21.0" + VersionClientByteSize = 33596 + VersionClientByteSizeGzip = 12349 //region Default durations - // The default duration for settling during merges. Allows for CSS transitions to complete. - DefaultSettleDuration = 300 * time.Millisecond + // The default duration for settling during fragment merges. Allows for CSS transitions to complete. + DefaultFragmentsSettleDuration = 300 * time.Millisecond // The default duration for retrying SSE on connection reset. This is part of the underlying retry mechanism of SSE. DefaultSseRetryDuration = 1000 * time.Millisecond diff --git a/sdk/go/fragments.go b/sdk/go/fragments.go index 12500e94a..9b49a601a 100644 --- a/sdk/go/fragments.go +++ b/sdk/go/fragments.go @@ -53,7 +53,7 @@ func (sse *ServerSentEventGenerator) MergeFragments(fragment string, opts ...Mer RetryDuration: DefaultSseRetryDuration, Selector: "", MergeMode: FragmentMergeModeMorph, - SettleDuration: DefaultSettleDuration, + SettleDuration: DefaultFragmentsSettleDuration, } for _, opt := range opts { opt(options) @@ -74,7 +74,7 @@ func (sse *ServerSentEventGenerator) MergeFragments(fragment string, opts ...Mer if options.MergeMode != FragmentMergeModeMorph { dataRows = append(dataRows, MergeModeDatalineLiteral+string(options.MergeMode)) } - if options.SettleDuration > 0 && options.SettleDuration != DefaultSettleDuration { + if options.SettleDuration > 0 && options.SettleDuration != DefaultFragmentsSettleDuration { settleDuration := strconv.Itoa(int(options.SettleDuration.Milliseconds())) dataRows = append(dataRows, SettleDurationDatalineLiteral+settleDuration) } @@ -141,7 +141,7 @@ func (sse *ServerSentEventGenerator) RemoveFragments(selector string, opts ...Re options := &RemoveFragmentsOptions{ EventID: "", RetryDuration: DefaultSseRetryDuration, - SettleDuration: DefaultSettleDuration, + SettleDuration: DefaultFragmentsSettleDuration, UseViewTransitions: nil, } for _, opt := range opts { @@ -149,7 +149,7 @@ func (sse *ServerSentEventGenerator) RemoveFragments(selector string, opts ...Re } dataRows := []string{SelectorDatalineLiteral + selector} - if options.SettleDuration > 0 && options.SettleDuration != DefaultSettleDuration { + if options.SettleDuration > 0 && options.SettleDuration != DefaultFragmentsSettleDuration { settleDuration := strconv.Itoa(int(options.SettleDuration.Milliseconds())) dataRows = append(dataRows, SettleDurationDatalineLiteral+settleDuration) } diff --git a/sdk/java/README.md b/sdk/java/README.md new file mode 100644 index 000000000..1d97a47c2 --- /dev/null +++ b/sdk/java/README.md @@ -0,0 +1,151 @@ +# Datastar Java SDK + +This package provides a Java SDK for working with [Datastar](https://data-star.dev/). + +## License + +This package is licensed for free under the MIT License. + +## Requirements + +This package requires Java 17 or later. + +## Installation + +Install using Maven by adding the following to your `pom.xml`: + +```xml + + com.starfederation + datastar + 1.0.0 + + + + + github + https://maven.pkg.github.com/starfederation/datastar + + +``` + +## Usage + +### Import Statements + +```java +import StarFederation.Datastar.enums.FragmentMergeMode; +import StarFederation.Datastar.events.MergeFragments; +import StarFederation.Datastar.events.MergeFragmentsOptions; +import StarFederation.Datastar.events.ExecuteScript; +import StarFederation.Datastar.events.MergeSignals; +import StarFederation.Datastar.events.MergeSignalsOptions; +import StarFederation.Datastar.events.RemoveSignals; +import StarFederation.Datastar.ServerSentEventGenerator; +``` + +### Set up Server-Sent Events Generator + +```java +AbstractResponseAdapter responseAdapter = new HttpServletResponseAdapter(response); +ServerSentEventGenerator sse = new ServerSentEventGenerator(responseAdapter); +``` + +### Explanation of Events + +```java +// This will create the event options based on the type. +MergeFragmentsOptions mergeFragmentsOptions = MergeFragmentsOptions.create(); + +// This will create the event based on the type and options. +MergeFragments mergeFragments = new MergeFragments(fragment, mergeFragmentsOptions); + +// This will set the SSE spec options. +mergeFragments.setEventId(UUID.randomUUID().toString()); +mergeFragments.setRetryDuration(1000); + +// This will send the event. +sse.MergeFragments(mergeFragments); +``` + +### Merging HTML Fragments into the DOM + +```java +String fragment = String.format("
bar
"); + +MergeFragmentsOptions mergeFragmentsOptions = MergeFragmentsOptions.create(); + +MergeFragments mergeFragments = new MergeFragments(fragment, mergeFragmentsOptions); +mergeFragments.setEventId(UUID.randomUUID().toString()); +mergeFragments.setRetryDuration(1000); + +sse.MergeFragments(mergeFragments); +``` + +### Removing HTML Fragments from the DOM + +```java +String selector = "#target"; + +RemoveFragmentsOptions removeFragmentsOptions = RemoveFragmentsOptions.create(); + +removeFragmentsOptions.withSettleDuration(20); +RemoveFragments removeFragments = new RemoveFragments(selector, removeFragmentsOptions); +removeFragments.setEventId(UUID.randomUUID().toString()); + +sse.RemoveFragments(removeFragments); +``` + +### Merging Signals + +```java +DataStore store = new DataStore(); +SignalReader.readSignals(adapter, store.getStore()); + +String input = store.get("input", String.class); +store.put("output", input); + +MergeSignalsOptions mergeSignalsOptions = MergeSignalsOptions.create(); +MergeSignals mergeSignals = new MergeSignals(store.toJson(), mergeSignalsOptions); +mergeSignals.setEventId(UUID.randomUUID().toString()); +mergeSignals.setRetryDuration(1000); + +sse.MergeSignals(mergeSignals); +``` + +### Removing Signals + +```java +List paths = Arrays.asList("user.name", "user.email"); +RemoveSignals removeSignals = new RemoveSignals(paths); +removeSignals.setEventId(UUID.randomUUID().toString()); + +sse.RemoveSignals(removeSignals); +``` + +### Executing Scripts + +```java +String script = "console.log('Hello from Datastar!')"; + +ExecuteScriptOptions executeScriptOptions = ExecuteScriptOptions.create() + .withAutoRemove(true) + .withAttributes("type module"); +ExecuteScript executeScript = new ExecuteScript(script, executeScriptOptions); +executeScript.setEventId(UUID.randomUUID().toString()); +executeScript.setRetryDuration(1000); + +sse.ExecuteScript(executeScript); +``` + +### Reading Signals + +```java +RequestAdapter adapter = new HttpServletRequestAdapter(request); + +DataStore store = new DataStore(); +SignalReader.readSignals(adapter, store.getStore()); + +String input = store.get("input", String.class); +store.put("output", input); +``` diff --git a/sdk/java/pom.xml b/sdk/java/pom.xml new file mode 100644 index 000000000..e8a8a3dd9 --- /dev/null +++ b/sdk/java/pom.xml @@ -0,0 +1,83 @@ + + 4.0.0 + + com.starfederation + datastar + 1.0.0 + jar + + Datastar Java SDK + A Java SDK for the Datastar platform + + + 6.1.0 + 2.18.2 + 5.10.0 + 17 + 17 + + + + + github + https://maven.pkg.github.com/rphumulock/datastar + + + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.databind.version} + + + + + jakarta.servlet + jakarta.servlet-api + ${jakarta.servlet.version} + provided + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.2.1 + + + package + + jar + + + + + + + diff --git a/sdk/java/src/main/java/StarFederation/Datastar/Consts.java b/sdk/java/src/main/java/StarFederation/Datastar/Consts.java new file mode 100644 index 000000000..ffa22270d --- /dev/null +++ b/sdk/java/src/main/java/StarFederation/Datastar/Consts.java @@ -0,0 +1,47 @@ +package starfederation.datastar; + +import starfederation.datastar.enums.FragmentMergeMode; + +/** + * This is auto-generated by Datastar. DO NOT EDIT. + */ +public final class Consts { + public static final String DATASTAR_KEY = "datastar"; + public static final String VERSION = "0.21.0"; + public static final int VERSION_CLIENT_BYTE_SIZE = 33596; + public static final int VERSION_CLIENT_BYTE_SIZE_GZIP = 12349; + + // The default duration for settling during fragment merges. Allows for CSS transitions to complete. + public static final int DEFAULT_FRAGMENTS_SETTLE_DURATION = 300; + + // The default duration for retrying SSE on connection reset. This is part of the underlying retry mechanism of SSE. + public static final int DEFAULT_SSE_RETRY_DURATION = 1000; + + // Should fragments be merged using the ViewTransition API? + public static final boolean DEFAULT_FRAGMENTS_USE_VIEW_TRANSITIONS = false; + + // Should a given set of signals merge if they are missing? + public static final boolean DEFAULT_MERGE_SIGNALS_ONLY_IF_MISSING = false; + + // Should script element remove itself after execution? + public static final boolean DEFAULT_EXECUTE_SCRIPT_AUTO_REMOVE = true; + + // The default attributes for ` + cdnText := `` }} {{ - usageSample := ` + usageSample := `
` }} @@ -73,7 +73,7 @@ templ Home() {
{ cdnText }
@@ -91,7 +91,7 @@ templ Home() {
{ usageSample }
@@ -116,7 +116,7 @@ templ Home() {

- We are by far the smallest and fastest full-featured framework, even with everything included. But no worries, we provide a “pay what for what you use” bundling system for when you want a custom build.'' + We are by far the smallest and fastest full-featured framework, even with everything included. But no worries, we provide a “pay for what you use” bundling system for when you want a custom build.

- // data-on-pageshow:window is to combat Safari's aggressive caching + // data-on-pageshow__window is to combat Safari's aggressive caching // https://stackoverflow.com/questions/8788802/prevent-safari-loading-from-cache-when-back-button-is-clicked { children... } diff --git a/site/smoketests/custom_events_test.go b/site/smoketests/custom_events_test.go index 70f49f884..09f7ae588 100644 --- a/site/smoketests/custom_events_test.go +++ b/site/smoketests/custom_events_test.go @@ -1,7 +1,9 @@ package smoketests import ( + "strconv" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -13,22 +15,22 @@ func TestExampleCustomEvents(t *testing.T) { assert.NotNil(t, page) t.Run("observe custom event", func(t *testing.T) { - evt := "myevent" - // setup listener - page.MustEval(`() => { - addEventListener('` + evt + `', function(event) { - window.__CUSTOM_EVENT = event; - }); - } - `) - - // wait until an event is captured in global scope - page.MustWait(`() => (window.__CUSTOM_EVENT !== undefined && window.__CUSTOM_EVENT !== undefined)`) - - // capture event details - result := page.MustEval(`() => window.__CUSTOM_EVENT.detail`).Str() - - assert.Contains(t, result, "eventTime") + evtCountEl := page.MustElement("#eventCount") + + count := func() int { + evtCountRaw := evtCountEl.MustText() + evtCount, err := strconv.Atoi(evtCountRaw) + assert.NoError(t, err) + return evtCount + } + + prev := count() + for i := 0; i < 2; i++ { + time.Sleep(1 * time.Second) + evtCountAgain := count() + assert.Greater(t, evtCountAgain, prev) + prev = evtCountAgain + } }) } diff --git a/site/smoketests/setup_test.go b/site/smoketests/setup_test.go index ec6fc76a6..8ef287de0 100644 --- a/site/smoketests/setup_test.go +++ b/site/smoketests/setup_test.go @@ -4,20 +4,20 @@ import ( "context" "fmt" "testing" + "time" "github.com/delaneyj/toolbelt" "github.com/go-rod/rod" "github.com/starfederation/datastar/site" - "github.com/ysmood/got" ) var ( baseURL string ) -// test context -type G struct { - got.G +type UnitTest struct { + t *testing.T + ctx context.Context browser *rod.Browser } @@ -40,18 +40,19 @@ func TestMain(m *testing.M) { ctx.Done() } -var setup = func() func(t *testing.T) G { - +var setup = func() func(t *testing.T) UnitTest { browser := rod.New().MustConnect() + return func(t *testing.T) UnitTest { + t.Parallel() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() - return func(t *testing.T) G { - t.Parallel() // run each test concurrently - return G{got.New(t), browser} + return UnitTest{t, ctx, browser} } }() -func (g G) page(url string) *rod.Page { - page := g.browser.MustIncognito().MustPage(fmt.Sprintf("%s/%s", baseURL, url)) - g.Cleanup(page.MustClose) +func (u UnitTest) page(url string) *rod.Page { + page := u.browser.MustIncognito().MustPage(fmt.Sprintf("%s/%s", baseURL, url)) + u.t.Cleanup(page.MustClose) return page } diff --git a/site/static/images/circular.png b/site/static/images/circular.png new file mode 100644 index 000000000..dba7ec732 --- /dev/null +++ b/site/static/images/circular.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94fa63363c6978f42ee9ca14d3e6953624bd3edd8f01aa200dca4a4d9ab61ea8 +size 40287 diff --git a/site/static/md/errors/InvalidValue.md b/site/static/md/errors/InvalidValue.md deleted file mode 100644 index 44a5e98d8..000000000 --- a/site/static/md/errors/InvalidValue.md +++ /dev/null @@ -1,3 +0,0 @@ -# Error: InvalidValue - -TODO: DELANEY! \ No newline at end of file diff --git a/site/static/md/examples/active_search.md b/site/static/md/examples/active_search.md index 3e97995c9..ddda6c48c 100644 --- a/site/static/md/examples/active_search.md +++ b/site/static/md/examples/active_search.md @@ -17,10 +17,10 @@ The interesting part is the input field: ```html ``` -The input issues a `GET` to `/active_search/data` with the input value bound to `search`. The `:debounce_1000ms` modifier ensures that the search is not issued on every keystroke, but only after the user has stopped typing for 1 second. This modifier will be covered in more detail in the [reference section](/reference). +The input issues a `GET` to `/active_search/data` with the input value bound to `search`. The `debounce.1000ms` modifier ensures that the search is not issued on every keystroke, but only after the user has stopped typing for 1 second. This modifier will be covered in more detail in the [reference section](/reference). diff --git a/site/static/md/examples/bind_keys.md b/site/static/md/examples/bind_keys.md index cc9b47d9a..5f67aa8bb 100644 --- a/site/static/md/examples/bind_keys.md +++ b/site/static/md/examples/bind_keys.md @@ -2,18 +2,14 @@ ## Demo -

Press Ctrl+K

-

Press Enter

+

Press Ctrl+K

+

Press Enter

## Explanation ```html -

- Press Ctrl+K -

-

- Press Enter -

+

Press Ctrl+K

+

Press Enter

``` Able to bind to any value on the `event`. Because of how `data-*` attributes are interpreted you'll need to use `kebab-case` for `camelCase` attributes. diff --git a/site/static/md/examples/bulk_update.md b/site/static/md/examples/bulk_update.md index dd9bdf83d..8b2d31738 100644 --- a/site/static/md/examples/bulk_update.md +++ b/site/static/md/examples/bulk_update.md @@ -24,7 +24,7 @@ transition: all 1.2s; ## Explanation -This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in PUT’s to two different endpoints: `activate` and `deactivate`: +This demo shows how to implement a common pattern where rows are selected and then bulk updated. This is accomplished by putting a form around a table, with checkboxes in the table, and then including the checked values in PUT’s to two different endpoints: `activate` and `deactivate`. Added to the page in this way: diff --git a/site/static/md/examples/classes.md b/site/static/md/examples/classes.md index 755192599..0909c3553 100644 --- a/site/static/md/examples/classes.md +++ b/site/static/md/examples/classes.md @@ -4,7 +4,7 @@
Count
@@ -18,7 +18,7 @@ ```html
Count
diff --git a/site/static/md/examples/custom_events.md b/site/static/md/examples/custom_events.md index cac47f823..11fe3f694 100644 --- a/site/static/md/examples/custom_events.md +++ b/site/static/md/examples/custom_events.md @@ -4,7 +4,7 @@
-
Event count: EventCount
+
Event count: EventCount
Last Event Details: EventTime