@lokascript/htmx-adapter 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,206 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.1.1] - 2026-09-04
11
+
12
+ ### Fixed
13
+
14
+ - **`@hyperfixi/mcp-server` no longer installs two copies of the contract
15
+ packages.** 3.1.0 pinned `@lokascript/domains ^2.11.1`, whose framework /
16
+ semantic / intent were hard 2.x dependencies — a clean install got 3.1.0 at
17
+ the top level and 2.11.1 nested under domains, and every `DomainRegistry` /
18
+ schema singleton forked across that boundary. mcp-server, framework and
19
+ server-bridge now range on `@lokascript/domains ^3.0.0`, which peers on
20
+ framework 3.x instead.
21
+
22
+ ### Added
23
+
24
+ - **`scripts/check-domains-peer-major.cjs`** — the cross-repo guard for that
25
+ class: the domains version the lockfile pins must PEER (not depend) on
26
+ framework / semantic / intent at a range the version this repo publishes
27
+ satisfies. Runs in `lint-typecheck`, first thing in `publish.yml` after the
28
+ version bump, and from the pre-commit hook. `examples/release-smoke/run.mjs`
29
+ additionally asserts one installed copy of each contract package, and
30
+ Dependabot now gives `@lokascript/domains` its own PR group so a domains
31
+ release is visible on its own instead of riding the weekly tooling PR.
32
+
33
+ ## [3.1.0] - 2026-09-04
34
+
35
+ ### Added
36
+
37
+ - **`hyperscript.use(frontEnd)`** — register the multilingual front-end
38
+ `compile()` consults for a non-English program (and `toLSE`/`fromLSE` for
39
+ a semantic node and a renderer). The contract is `FrontEnd` in
40
+ `@hyperfixi/core` (`parseToAST`, optional `parse`/`render`);
41
+ `@lokascript/semantic` satisfies it through
42
+ `createBridgeFrontEnd(new SemanticGrammarBridge())` from
43
+ `@hyperfixi/core/multilingual`. The full browser bundle registers it at
44
+ boot. The library entry registers nothing and, through 3.x, builds the same
45
+ bridge lazily on the first non-English compile — so nothing changes for a
46
+ consumer with `@lokascript/semantic` installed. **4.0 will remove that
47
+ default:** a non-English `compile()` with no front-end registered will
48
+ return the same result the core-parser-only path returns today, and
49
+ `@lokascript/semantic` becomes an optional peer. Engine-migration plan
50
+ Arc 1, steps 2 and 3.
51
+
52
+ ### Fixed
53
+
54
+ - **Language server: valid non-English code no longer gets a `parse-error`.**
55
+ In `lokascript`/`hyperscript-i18n` mode the server ran core's English
56
+ parser on every region regardless of what the semantic front-end said, so
57
+ `al hacer clic alternar .activo` (accepted at 0.89 confidence) was published
58
+ as `Unexpected token: hacer`. Core's errors are now suppressed for a region
59
+ the front-end accepted in a non-English language. The integration suite
60
+ that claimed to cover this had been discarding every notification; it now
61
+ records `publishDiagnostics` and asserts on it.
62
+
63
+ - **Language server: settings actually reach the server.** Neither VS Code
64
+ extension set `synchronize.configurationSection`, so the client pushed
65
+ `settings: null` and every `mode`/`language`/`maxDiagnostics` value was
66
+ ignored; `initializationOptions` was ignored too. The server now reads
67
+ `initializationOptions`, a pushed `lokascript`/`hyperscript` section, or
68
+ pulls `workspace/configuration` when the push is empty — and merges partial
69
+ objects over the defaults instead of replacing them (a bare
70
+ `{ maxDiagnostics }` used to leave `language` undefined and error on every
71
+ document). Both extensions push their section, and the LokaScript one now
72
+ contributes `lokascript.mode`.
73
+
74
+ - **Both VS Code extension server bundles build and run.** The standalone
75
+ hyperscript extension's `bundle:server` had failed since #1016 (no shim for
76
+ `@hyperfixi/core/multilingual`); the LokaScript extension externalized
77
+ `@hyperfixi/core` without shipping it, so every install ran with core
78
+ absent ("diagnostics and hover degraded"). Core is bundled in now, and CI
79
+ runs both bundle scripts.
80
+
81
+ - **`@lokascript/language-server` declares what it imports.**
82
+ `@lokascript/semantic` was an optional peer but a static import (the
83
+ server crashed at startup without it); `@lokascript/framework` was
84
+ undeclared and got inlined together with `@lokascript/intent` (261 KB) —
85
+ and without `--clean`, `dist/` shipped every previous build's chunk (three
86
+ dead ones, 63% of the tarball). Both are regular dependencies now, the
87
+ build cleans, the 20-byte `--dts` output is gone, and `npm test` rebuilds
88
+ the server it spawns.
89
+
90
+ - **Hyperscript-compat mode stops flagging portable code.** `as Int`, `Float`,
91
+ `JSON`, `Date`, `Set`, `Map` are upstream conversions (the extension list
92
+ is now `Math`/`Values`, the ones core registers and upstream lacks);
93
+ `my.x`/`your.x` evaluate upstream (only `its.` is flagged); and the
94
+ extension-command scan no longer fires inside `y.replace(`, `arr.push(`,
95
+ string literals, `:process`, `--` comments, or the upstream trailing
96
+ `… unless …` modifier.
97
+
98
+ - **Language-server false positives and misses.** A `.hs` file containing a
99
+ query literal like `<li/>` was classified as HTML and lost every feature;
100
+ possessives after `)`/`]`/`>` and apostrophes in `--` comments raised
101
+ `unmatched-quote`; the outline reported the `on` in `toggle .x on me` as a
102
+ handler and `.init` as an init block; `install Foo(args)` produced
103
+ overlapping rename edits VS Code rejects; `on draggable:start` became a
104
+ variable named `:start`; and keyword boundaries were ASCII `\b`, so
105
+ definitions and symbols were silently absent in the ten non-Latin
106
+ languages. Completion-context and document-symbol logic moved out of the
107
+ entry script into `completion-context.ts` / `document-symbols.ts` so the
108
+ unit suite tests the shipped code rather than the hand-copied twins it
109
+ used to carry.
110
+
111
+ - **MCP `get_diagnostics`, `get_completions` and `get_document_symbols` read
112
+ the AST.** The bridge guarded those paths on helper names core never
113
+ exported, so all three had only ever taken their token-based fallback.
114
+ They now read the interchange: the core parser's own errors become
115
+ diagnostics, completions inside a command offer that command's argument
116
+ shapes, symbols carry their commands as children, and a document with
117
+ several `end`-terminated handlers keeps all of them.
118
+
119
+ - **`hyperscript.process()` honours the event grammar.** The API-side DOM
120
+ processor carried its own listener installer for `on …` attributes and
121
+ silently dropped what the runtime's installer handles: `on
122
+ click[event.shiftKey]` fired on a plain click, `on mouseenter or click` never
123
+ fired on mouseenter, `on click from document` never fired. Every browser
124
+ bundle already went through the runtime; `process()` now does too, and
125
+ `config.logAll` moved into the runtime so both paths log. Pinned by
126
+ `api/dom-processor.test.ts`, which runs each case on both paths.
127
+
128
+ - **The browser bundles' attribute processor now runs the element lifecycle,
129
+ and its lazy stub only takes the shapes it can run faithfully.** Measured
130
+ side by side with `hyperscript.process()`: the bundle path never dispatched
131
+ `hyperscript:before:init` / `hyperscript:after:init` and never set
132
+ `data-hyperscript-powered` — both lived only in the API processor, so a
133
+ morph engine reading the marker saw nothing on a bundle-processed page, and
134
+ canceling `before:init` was impossible there. It also compiled every
135
+ attribute as English; it now detects the element's language (`data-lang`,
136
+ closest `lang`, the document's) the way `process()` always did. And the
137
+ `lazyParsing` stub read only the event NAME from the header, so for the
138
+ first event it ignored a filter (`on click[event.shiftKey]` fired on a
139
+ plain click), listened for the first name of an `or` list only, and never
140
+ saw `from <target>` at all; a header with a filter, `(args)`, `or`, `from`,
141
+ `elsewhere`, `queue`, `debounced`, `throttled` or `in` is now processed
142
+ eagerly. `dom/processor-parity.test.ts` runs every row on all three paths
143
+ (API, eager, lazy) and is the gate the DOM-processor collapse landed under.
144
+
145
+ - **`hyperscript.process()` and the browser bundles share one DOM
146
+ processor.** `process()` and `cleanup()` now run on the attribute
147
+ processor every bundle uses, which receives its compiler and runtime from
148
+ the API by injection instead of importing it (the `dom -> api` layering
149
+ edge is gone). What changes for `process()` callers, all of it upstream
150
+ parity: an element is initialized once however many times it is passed or
151
+ scanned (it used to re-install on every call); `<script
152
+ type="text/hyperscript">` tags inside the tree are processed, with or
153
+ without `for=`, before the elements that may `install` what they define;
154
+ each element gets a non-bubbling `load` event once its program has run,
155
+ and an unparseable attribute is reported through `console.error`, the
156
+ `hyperfixi:compile-error` event and `config.onCompileError` (it was logged
157
+ at debug level only). And `cleanup(container)` now un-marks every processed
158
+ descendant — it stripped the root's `data-hyperscript-powered` only — and
159
+ drops a lazy stub still waiting on an element, so cleanup-then-process
160
+ re-initializes the tree once. A second scan in `lazyParsing` mode no longer
161
+ registers a second stub.
162
+ The API-side processor module is deleted; the multilingual bundle's own
163
+ copy of the language walk now calls the shared one, narrowed to the
164
+ languages that bundle ships; and a subtree the `MutationObserver` picks up
165
+ takes the same entry as `process()`.
166
+
167
+ - **`set *<css-property>` writes inline style in every spelling upstream
168
+ accepts.** `set *opacity to 0.5`, `set *opacity of me to 0.5` and
169
+ `set *background-color of me to "red"` were silent no-ops and
170
+ `set the *opacity of me to 0.5` threw; only the possessive `set my *opacity`
171
+ worked. The tokenizer classes `*opacity` as a selector (for `measure`), and
172
+ `set`'s parser now re-types it into the identifier property the runtime's
173
+ style rungs already handle, so one path serves all of them — bare, `of
174
+ <target>`, `the … of <target>`, `<target>'s`. Ten rows pinned against
175
+ `_hyperscript` 0.9.93.
176
+
177
+ - **`require('@hyperfixi/core')` returned `{}`, and the subpath `require`s
178
+ threw — since `"type": "module"`, on 3.0.0 too.** Every `exports.*.require`
179
+ and `main` pointed at a `.js` file built as CommonJS, which Node reads as
180
+ ESM under that flag. The CJS outputs are `.cjs` now (main entry, the 21
181
+ subpaths, the parser modules); the `import` conditions are unchanged. The
182
+ bare-Node check in CI now `require()`s the entries as well as importing
183
+ them, which is the check that would have caught this. Found by the
184
+ end-to-end probe that landed Arc 1's build half.
185
+
186
+ ### Changed
187
+
188
+ - **`@hyperfixi/core`'s library entry no longer bundles the multilingual
189
+ front-end.** `dist/index.mjs`/`index.js` had `@lokascript/semantic`,
190
+ `@lokascript/intent` and `@lokascript/framework` inlined whole (3.33 MB, zero
191
+ dynamic imports left); they are external now (1.04 MB) and load on the first
192
+ non-English `compileAsync`, the way the source always said they would. No
193
+ API change: semantic and intent stay `dependencies`, framework stays an
194
+ optional peer — and is now genuinely one. A consumer that imported both core
195
+ and semantic no longer holds two copies of semantic. `dist/index.min.js`
196
+ (UMD, not in `exports`) stays self-contained. Gated by
197
+ `scripts/check-node-import.mjs` on the sourcemap. Engine-migration plan Arc 1
198
+ step 2, build half.
199
+
200
+ - **`@lokascript/semantic` drops its `asyncSchema`.** Core deleted the `async`
201
+ command in 3.0.0 (#1102); the semantic front-end never emitted the action
202
+ anyway — its `stripAsyncModifier` pass removes the keyword (in all 24
203
+ languages) and parses the following command, so `async fetch /api as json`
204
+ is a `fetch`. The schema, its registration, the `'async'` `ActionType`
205
+ member and the AST-builder mapper row are gone. Kept on purpose: every
206
+ profile's `keywords.async` (the stripper matches on it) and the
207
+ `'then' | 'and' | 'async'` chain type (a different concept). The IR-level
208
+ `async all/race` node lives in `@lokascript/intent` and is untouched.
209
+
10
210
  ## [3.0.0] - 2026-09-03
11
211
 
12
212
  Full notes: [GitHub Releases](https://github.com/codetalcott/hyperfixi/releases/tag/v3.0.0).
@@ -14,9 +214,11 @@ Full notes: [GitHub Releases](https://github.com/codetalcott/hyperfixi/releases/
14
214
  One major, two themes. `@lokascript/i18n`'s grammar transformer is gone,
15
215
  because `@lokascript/semantic` overtook it on every row of the translation
16
216
  corpus (#973–#1001). And the engine-migration plan
17
- (`docs-internal/ENGINE_MIGRATION_PLAN.md`) is complete: its last arc deleted
217
+ (`docs-internal/ENGINE_MIGRATION_PLAN.md`) closed its last arc, which deleted
18
218
  the exported dead code it had been carrying and collapsed the browser bundle
19
- lineup to two names (#1099–#1105).
219
+ lineup to two names (#1099–#1105). One arc is still open: Arc 1's steps 2
220
+ and 3 — `@hyperfixi/core`'s library entry still bundles the semantic
221
+ front-end (recorded in the plan's History, 2026-09-03).
20
222
 
21
223
  ### ⚠ BREAKING
22
224
 
@@ -514,7 +716,8 @@ _Synchronized version release. See git history for details._
514
716
  - npm access token stored in GitHub Secrets
515
717
  - 2FA recommended for npm organization
516
718
 
517
- [Unreleased]: https://github.com/codetalcott/hyperfixi/compare/v2.10.0...HEAD
719
+ [Unreleased]: https://github.com/codetalcott/hyperfixi/compare/v3.1.0...HEAD
720
+ [3.1.0]: https://github.com/codetalcott/hyperfixi/compare/v3.0.0...v3.1.0
518
721
  [2.10.0]: https://github.com/codetalcott/hyperfixi/compare/v2.9.0...v2.10.0
519
722
  [2.9.0]: https://github.com/codetalcott/hyperfixi/compare/v2.8.0...v2.9.0
520
723
  [2.8.0]: https://github.com/codetalcott/hyperfixi/compare/v2.5.1...v2.8.0
package/README.md CHANGED
@@ -39,7 +39,7 @@ All three consumers share one vocabulary source: the generated modules under
39
39
 
40
40
  htmx v4 has no hook to override attribute-name resolution — core reads `hx-get`
41
41
  literally. We maintain a proposed upstream seam plus a working ~30-line
42
- reference patch against beta5 and a ready-to-file Discussion draft
42
+ reference patch against 4.0.0 and a ready-to-file Discussion draft
43
43
  ([docs/UPSTREAM_HOOK_PROPOSAL.md](docs/UPSTREAM_HOOK_PROPOSAL.md)); against a
44
44
  patched build, `installResolverMode(htmx)` localizes with **zero DOM
45
45
  mutation** (proven in the e2e suite). Until the seam exists upstream, the
@@ -51,19 +51,27 @@ attribute is copied to its canonical name on the same element
51
51
  _before_ the htmx `<script>` so the sweep listener registers first
52
52
  (loka-js's "orchestrator before libraries" rule).
53
53
  2. **Swapped-in content** — a registered htmx v4 extension
54
- (`lokascript-i18n`) canonicalizes each subtree in
55
- `htmx_before_process_node`. (A best-effort `defineExtension`/`onEvent`
56
- fallback covers htmx v1/v2.)
54
+ (`lokascript-i18n`) canonicalizes each subtree in `htmx_before_process`
55
+ (`htmx_before_process_node` is kept as an inert alias). (A best-effort
56
+ `defineExtension`/`onEvent` fallback covers htmx v1/v2.)
57
57
 
58
58
  Guarantees, mirroring loka-js where the mechanism allows:
59
59
 
60
60
  - **Authored attributes are never removed or rewritten** — devtools shows what
61
- the author wrote. Two documented exceptions: an author-written canonical
61
+ the author wrote. Documented exceptions: an author-written canonical
62
62
  `hx-trigger` whose _value_ uses localized event names (`hx-trigger="clic"`)
63
63
  is translated in place (idempotently), since there is no separate canonical
64
- target; and canonical-named `hx-on:*` attrs are removed in opt-in executor
65
- mode (see below), since htmx would otherwise JS-eval their hyperscript
66
- bodies.
64
+ target; and in opt-in executor mode a canonical-named `hx-on:*` attr may be
65
+ removed so htmx never JS-evals its hyperscript body. On htmx **v4 with the
66
+ extension accepted** that removal does not happen: the extension cancels
67
+ the node's `htmx:before:on:init` instead and the claimed attr stays
68
+ authored-verbatim (removal survives only as a per-node fallback when the
69
+ element also carries an hx-on form htmx must still bind — the legacy
70
+ composite `hx-on="…"`, the `config.prefix` spelling, a custom
71
+ `metaCharacter`). On htmx **v2**, or when v4 **rejected** the registration
72
+ (`htmx.config.extensions` allowlist), or with no htmx at all, the attr is
73
+ removed at claim time — the only guard that needs no hook. Adapter-created
74
+ canonical siblings are not authored and are always cleaned up.
67
75
  - **An existing canonical attribute always wins** — `hx-get` is never
68
76
  overwritten by `hx-obtener`.
69
77
  - **No vocab loaded → no-op.** Stock htmx pages pay nothing.
@@ -141,15 +149,35 @@ reliable detection):
141
149
  unchanged.
142
150
  - **Localized-named attrs** (`hx-en:clic`) stay verbatim in the DOM and get
143
151
  no canonical sibling — htmx never recognized them anyway.
144
- - **Canonical-named attrs** (`hx-on:click`) are **removed** after claiming —
145
- the second (and last) documented exception to the never-mutate rule: left
146
- in place, htmx would eval the hyperscript body as JS, giving a console
147
- error plus a double-execution attempt on every fire.
152
+ - **Canonical-named attrs** (`hx-on:click`) must be kept away from htmx's own
153
+ binder — left bound, htmx would eval the hyperscript body as JS, giving a
154
+ console error plus a double-execution attempt on every fire. A claim
155
+ _records_ the attribute; what keeps htmx off it depends on the runtime:
156
+ - **htmx v4, extension accepted** (the `registerWith` result, not the API's
157
+ shape): no mutation. The extension's `htmx:before:on:init` hook consults
158
+ the record per node and cancels htmx's binding; the authored attribute
159
+ stays in the DOM, and `htmx.process(elt, true)` after editing it runs the
160
+ new body. If the node also carries an hx-on form htmx must bind and the
161
+ adapter never claims (composite `hx-on="event -> code"`, the
162
+ `config.prefix` spelling, a custom `metaCharacter`), per-node cancellation
163
+ would kill that too, so the hook removes the claimed canonical attrs and
164
+ lets htmx bind the rest.
165
+ - **htmx v2** (2.0.10 binds `hx-on` before it fires `beforeProcessNode`, so
166
+ there is no pre-bind seam), **a rejected v4 registration** (the adapter
167
+ warns, naming `htmx.config.extensions`), or **no htmx**: the attr is
168
+ removed at claim time — the documented exception to the never-mutate
169
+ rule, and the only guard that does not depend on a hook being installed.
170
+ - Wiring `createExtension()` into `htmx.registerExtension` yourself? Call
171
+ `setNeutralizeOnClaim(false)` after it succeeds to get the v4 behaviour.
148
172
  - The `hx-on::after-swap` shorthand maps to the `htmx:` event namespace and
149
173
  works unchanged (the listener hears htmx's real CustomEvents).
150
174
  - Re-sweeps never stack duplicate listeners (claims are keyed per element by
151
175
  resolved event name), and an executor configured _after_ the initial sweep
152
- triggers a healing re-sweep.
176
+ triggers a healing re-sweep. The initial sweep waits for `DOMContentLoaded`
177
+ to have fired (a `load` listener covers a script injected in between), and
178
+ the browser entry registers the extension before it re-detects
179
+ `_hyperscript`, so a late `_hyperscript`, a `defer`ed adapter, or a
180
+ module script all see the same behaviour as the recommended order.
153
181
 
154
182
  A side benefit: executor mode never uses `eval`, so hyperscript bodies work on
155
183
  CSP-restricted pages where htmx's native `hx-on` JS eval cannot.
@@ -169,7 +197,7 @@ only the suffix is localized. The `_=` attribute is
169
197
  ## Tests
170
198
 
171
199
  ```bash
172
- npm test --prefix packages/htmx-adapter # vitest, jsdom (60 tests)
200
+ npm test --prefix packages/htmx-adapter # vitest, jsdom
173
201
  npm run test:browser --prefix packages/htmx-adapter # Playwright e2e (build dist first)
174
202
  ```
175
203
 
@@ -177,11 +205,11 @@ The unit suite includes a reuse guard that loads every generated
177
205
  `packages/core/vocab/htmx/{lang}.js` module against this adapter's registry, so
178
206
  generator drift fails here rather than in a browser.
179
207
 
180
- The Playwright suite drives **real vendored libraries** — htmx `4.0.0-beta5`,
208
+ The Playwright suite drives **real vendored libraries** — htmx `4.0.0`,
181
209
  htmx `2.0.10`, `_hyperscript` `0.9.93` (`test/browser/vendor/`) — verifying the
182
210
  end-to-end truths mocks can't: the v4 extension hook name and firing
183
211
  granularity (`htmx_before_process`, per processed root — validated against the
184
- beta5 source), request/swap from a localized button, both script orders,
212
+ 4.0.0 source), request/swap from a localized button, both script orders,
185
213
  localized attributes inside swapped-in content, executor-mode `hx-on` bodies
186
214
  running through real `_hyperscript` with `me` bound, and the htmx 2.x
187
215
  `defineExtension` fallback.
@@ -1,2 +1,2 @@
1
- "use strict";var HtmxI18n=(()=>{var H=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var Z=(e,t)=>{for(var n in t)H(e,n,{get:t[n],enumerable:!0})},ee=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of J(t))!Q.call(e,r)&&r!==n&&H(e,r,{get:()=>t[r],enumerable:!(o=Y(t,r))||o.enumerable});return e};var te=e=>ee(H({},"__esModule",{value:!0}),e);var ae={};Z(ae,{EXTENSION_NAME:()=>b,canonicalizeElement:()=>L,canonicalizeTree:()=>g,installAutoSweep:()=>$,installResolverMode:()=>G,langOf:()=>p,normLang:()=>d,register:()=>h,registerWith:()=>R,setBodyExecutor:()=>_,setBodyTranslator:()=>T,translateTriggerValue:()=>A});function d(e){return e?e.split(/[-_]/)[0].toLowerCase():"en"}function p(e){let t=e.getAttribute?.("data-hyperfixi-lang");if(t)return d(t);let n=e.closest?.("[data-hyperfixi-lang]");if(n)return d(n.getAttribute("data-hyperfixi-lang"));let o=e.closest?.("[lang]");return o?d(o.getAttribute("lang")):"en"}var y=new Map,M=new Set,O=new Set;function h(e,t){let n=d(e);y.set(n,t?.hyperfixi??{}),M.delete(n);for(let o of O)o()}function x(e){return y.get(e)}function I(){return y.size>0}function P(){return[...y.keys()]}function E(e){return O.add(e),()=>{O.delete(e)}}function F(e){M.has(e)||(M.add(e),typeof console<"u"&&console.warn(`[htmx-i18n] No vocab registered for lang="${e}". Elements in this language scope keep their localized attribute names and htmx will not see them. Load the ${e} vocab module (packages/core/vocab/htmx/${e}.js) before htmx processes the page.`))}var m=null,w=null,S=new Set,N=new WeakMap;function _(e){m=e;for(let t of S)t()}function T(e){w=e}function V(){return m!==null}function U(e){return S.add(e),()=>{S.delete(e)}}function ne(e,t){return e.startsWith(":")?`htmx${e}`:t[e]??e}function D(e,t,n,o){if(!m)return!1;let r=t.indexOf(":");if(r<=0)return!1;let i=ne(t.slice(r+1),o),s=N.get(e);if(s?.has(i))return t.startsWith("hx-on:")&&e.hasAttribute(t)?(e.removeAttribute(t),!0):!1;let l=e.getAttribute(t)??"",u=null;return e.addEventListener(i,c=>{if(m){u===null&&(u=n!=="en"&&w?w(l,n):l);try{m(u,e,c)}catch(a){typeof console<"u"&&console.error(`[htmx-i18n] hx-on body execution failed (${t} \u2192 ${i})`,a)}}}),t.startsWith("hx-on:")&&e.removeAttribute(t),s?s.add(i):N.set(e,new Set([i])),!0}function j(e){let t=e;if(!m&&typeof t._hyperscript=="function"){let n=t._hyperscript;_((o,r,i)=>typeof n.evaluate=="function"?n.evaluate(o,{me:r,event:i}):n(o))}if(!w&&typeof t.HyperscriptI18n?.preprocess=="function"){let n=t.HyperscriptI18n.preprocess;T((o,r)=>n(o,r))}}var C=!1;function X(){return C}function oe(e,t){let n=p(e);if(n==="en")return null;let o=x(n)?.attrs;if(!o)return null;for(let[r,i]of Object.entries(o))if(i===t&&e.hasAttribute?.(r))return r;return null}function q(){let e=new Set;for(let t of P()){let n=x(t)?.attrs??{};for(let[o,r]of Object.entries(n))r!=="hx-on"&&e.add(`[${o}]`)}return[...e]}function G(e){if(!e?.config)throw new Error("[htmx-i18n] installResolverMode: htmx.config not found");let t=e.config;t.attributeResolver=oe,t.additionalAttributeSelectors=q();let n=E(()=>{t.additionalAttributeSelectors=q()});return C=!0,()=>{n(),t.attributeResolver=null,t.additionalAttributeSelectors=[],C=!1}}var K=/^(?:hx|sse|ws)-/;function A(e,t){return e.split(",").map(n=>{let o=n.trim();if(!o)return o;let r=o.split(/\s+/),i=r[0].match(/^([^[\s]+)(\[.*)?$/);if(i){let s=t[i[1]];s&&(r[0]=s+(i[2]??""))}return r.join(" ")}).join(", ")}function L(e){if(!e.attributes||e.attributes.length===0)return!1;let t=!1;for(let c of Array.from(e.attributes))if(K.test(c.name)){t=!0;break}if(!t)return!1;let n=V(),o=p(e);if(o==="en"&&!n)return!1;let r=x(o);if(o!=="en"&&!r&&(F(o),!n))return!1;let i=r?.attrs??{},s=r?.events??{},l=!1;for(let c of Array.from(e.attributes)){let a=c.name;if(!K.test(a))continue;if(n&&a.startsWith("hx-on:")){D(e,a,o,s)&&(l=!0);continue}let f=i[a],v;if(!f){let B=a.indexOf(":");if(B>0&&(v=i[a.slice(0,B)],v)){let W=a.slice(B+1);f=`${v}:${s[W]??W}`}}if(!f)continue;if(n&&v==="hx-on"){D(e,a,o,s)&&(l=!0);continue}if(f===a||e.hasAttribute(f))continue;let k=c.value;f==="hx-trigger"&&(k=A(k,s)),e.setAttribute(f,k),l=!0}let u=e.getAttribute("hx-trigger");if(u!==null&&Object.keys(s).length>0){let c=A(u,s);c!==u&&(e.setAttribute("hx-trigger",c),l=!0)}return l}function g(e){if(!e||X()||!I()&&!V())return 0;let t=0;if(e instanceof Element&&L(e)&&t++,typeof e.querySelectorAll=="function")for(let n of Array.from(e.querySelectorAll("*")))L(n)&&t++;return t}var b="lokascript-i18n";function re(){return{htmx_before_process(e){g(e)},htmx_before_process_node(e){g(e)},onEvent(e,t){if(e!=="htmx:beforeProcessNode")return;let o=t?.detail?.elt??(t?.target instanceof Element?t.target:null);o&&g(o)}}}function R(e){if(!e)return null;let t=re();return typeof e.registerExtension=="function"?(e.registerExtension(b,t),"v4"):typeof e.defineExtension=="function"?(e.defineExtension(b,t),"v2"):null}function $(e=document){let t=()=>{g(e.body??e.documentElement)},n=null;if(e.readyState==="loading"){let i=()=>t();e.addEventListener("DOMContentLoaded",i,{once:!0}),n=()=>e.removeEventListener("DOMContentLoaded",i)}else t();let o=E(()=>{e.readyState!=="loading"&&t()}),r=U(()=>{e.readyState!=="loading"&&t()});return()=>{n?.(),o(),r()}}var z="__hyperfixi_i18n";function ie(){if(typeof window>"u")return;let e=window,t=e[z];if(t){let n=t.register.bind(t);e[z]={register:(o,r)=>{n(o,r),h(o,r)}};return}e[z]={register:h}}function se(){if(typeof window>"u"||typeof document>"u")return;let e=window;j(window),document.addEventListener("DOMContentLoaded",()=>j(window),{once:!0}),R(e.htmx)||document.addEventListener("DOMContentLoaded",()=>{!R(e.htmx)&&typeof console<"u"&&console.warn(`[htmx-i18n] window.htmx not found \u2014 the "${b}" extension was not registered. Dynamically swapped content will not be canonicalized (the initial document sweep still ran). Load htmx on this page.`)},{once:!0}),$(document)}ie();se();return te(ae);})();
1
+ "use strict";var HtmxI18n=(()=>{var B=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var le=Object.getOwnPropertyNames;var ce=Object.prototype.hasOwnProperty;var ue=(e,t)=>{for(var n in t)B(e,n,{get:t[n],enumerable:!0})},fe=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of le(t))!ce.call(e,o)&&o!==n&&B(e,o,{get:()=>t[o],enumerable:!(r=ae(t,o))||r.enumerable});return e};var de=e=>fe(B({},"__esModule",{value:!0}),e);var Ae={};ue(Ae,{EXTENSION_NAME:()=>m,canonicalizeElement:()=>H,canonicalizeTree:()=>p,installAutoSweep:()=>U,installResolverMode:()=>te,langOf:()=>b,normLang:()=>g,register:()=>O,registerWith:()=>M,setBodyExecutor:()=>$,setBodyTranslator:()=>N,setNeutralizeOnClaim:()=>v,translateTriggerValue:()=>R});function g(e){return e?e.split(/[-_]/)[0].toLowerCase():"en"}function b(e){let t=e.getAttribute?.("data-hyperfixi-lang");if(t)return g(t);let n=e.closest?.("[data-hyperfixi-lang]");if(n)return g(n.getAttribute("data-hyperfixi-lang"));let r=e.closest?.("[lang]");return r?g(r.getAttribute("lang")):"en"}var A=new Map,_=new Set,T=new Set;function O(e,t){let n=g(e);A.set(n,t?.hyperfixi??{}),_.delete(n);for(let r of T)r()}function E(e){return A.get(e)}function G(){return A.size>0}function K(){return[...A.keys()]}function C(e){return T.add(e),()=>{T.delete(e)}}function Y(e){_.has(e)||(_.add(e),typeof console<"u"&&console.warn(`[htmx-i18n] No vocab registered for lang="${e}". Elements in this language scope keep their localized attribute names and htmx will not see them. Load the ${e} vocab module (packages/core/vocab/htmx/${e}.js) before htmx processes the page.`))}var ge=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),h=null,S=null,z=new Set,V=new WeakMap,L=!0;function v(e){L=e}function $(e){h=e;for(let t of z)t()}function N(e){S=e}function j(){return h!==null}function J(e){return z.add(e),()=>{z.delete(e)}}function W(e){let t=new Set,n=V.get(e);if(n)for(let r of n.values())t.add(r.attrName);return t}function Q(e){let t=0;for(let n of W(e))n.startsWith("hx-on:")&&e.hasAttribute(n)&&(e.removeAttribute(n),t++);return t}function pe(e,t){return e.startsWith(":")?`htmx${e}`:ge(t,e)?t[e]:e}function D(e,t,n,r,o={}){if(!h)return!1;let i=t.indexOf(":");if(i<=0)return!1;let s=pe(t.slice(i+1),r),f=t.startsWith("hx-on:"),u=e.getAttribute(t)??"",c=V.get(e),a=c?.get(s);if(a)return a.attrName===t?(a.body!==u&&(a.body=u,a.translated=null),f&&L&&e.hasAttribute(t)?(e.removeAttribute(t),!0):!1):f&&(o.adapterCreated||L)&&e.hasAttribute(t)?(e.removeAttribute(t),!0):!1;let l={attrName:t,lang:n,body:u,translated:null};return e.addEventListener(s,d=>{if(h){l.translated===null&&(l.translated=l.lang!=="en"&&S?S(l.body,l.lang):l.body);try{h(l.translated,e,d)}catch(y){typeof console<"u"&&console.error(`[htmx-i18n] hx-on body execution failed (${t} \u2192 ${s})`,y)}}}),c||(c=new Map,V.set(e,c)),c.set(s,l),f&&L&&e.removeAttribute(t),!0}function P(e){let t=e;if(!h&&typeof t._hyperscript=="function"){let n=t._hyperscript;$((r,o,i)=>typeof n.evaluate=="function"?n.evaluate(r,{me:o,event:i}):n(r))}if(!S&&typeof t.HyperscriptI18n?.preprocess=="function"){let n=t.HyperscriptI18n.preprocess;N((r,o)=>n(r,o))}}var I=!1;function ee(){return I}function me(e,t){let n=b(e);if(n==="en")return null;let r=E(n)?.attrs;if(!r)return null;for(let[o,i]of Object.entries(r))if(i===t&&e.hasAttribute?.(o))return o;return null}function Z(){let e=new Set;for(let t of K()){let n=E(t)?.attrs??{};for(let[r,o]of Object.entries(n))o!=="hx-on"&&e.add(`[${r}]`)}return[...e]}function te(e){if(!e?.config)throw new Error("[htmx-i18n] installResolverMode: htmx.config not found");let t=e.config;t.attributeResolver=me,t.additionalAttributeSelectors=Z();let n=C(()=>{t.additionalAttributeSelectors=Z()});return I=!0,()=>{n(),t.attributeResolver=null,t.additionalAttributeSelectors=[],I=!1}}var k=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),ne=/^(?:hx|sse|ws)-/,xe=/,(?![^\[]*\])(?![^(]*\))(?![^<]*\/>)(?=(?:[^"']|"[^"]*"|'[^']*')*$)/,oe=e=>e.split(xe),ie=oe;function se(e){ie=e??oe}function re(e){return e==="hx-trigger"||e.startsWith("hx-trigger:")}function R(e,t){return ie(e).map(n=>n.replace(/^(\s*)([^[\s]+)/,(r,o,i)=>k(t,i)?o+t[i]:r)).join(",")}var F=new WeakMap;function be(e,t){let n=F.get(e);n||(n=new Set,F.set(e,n)),n.add(t)}function he(e,t){return F.get(e)?.has(t)??!1}function H(e){if(!e.attributes||e.attributes.length===0)return!1;let t=!1;for(let c of Array.from(e.attributes))if(ne.test(c.name)){t=!0;break}if(!t)return!1;let n=j(),r=b(e);if(r==="en"&&!n)return!1;let o=E(r);if(r!=="en"&&!o&&(Y(r),!n))return!1;let i=o?.attrs??{},s=o?.events??{},f=!1;for(let c in s){f=!0;break}let u=!1;for(let c of Array.from(e.attributes)){let a=c.name;if(!ne.test(a))continue;if(n&&a.startsWith("hx-on:")){D(e,a,r,s,{adapterCreated:he(e,a)})&&(u=!0);continue}if(re(a)){if(f){let x=R(c.value,s);x!==c.value&&(e.setAttribute(a,x),u=!0)}continue}let l=k(i,a)?i[a]:void 0,d;if(!l){let x=a.indexOf(":");if(x>0){let X=a.slice(0,x);if(d=k(i,X)?i[X]:void 0,d){let w=a.slice(x+1);l=d==="hx-on"?`${d}:${k(s,w)?s[w]:w}`:`${d}:${w}`}}}if(!l)continue;if(n&&d==="hx-on"){D(e,a,r,s)&&(u=!0);continue}if(l===a||e.hasAttribute(l))continue;let y=c.value;re(l)&&(y=R(y,s)),e.setAttribute(l,y),be(e,l),u=!0}return u}function p(e){if(!e||ee()||!G()&&!j())return 0;let t=0;if(e instanceof Element&&H(e)&&t++,typeof e.querySelectorAll=="function")for(let n of Array.from(e.querySelectorAll("*")))H(n)&&t++;return t}var m="lokascript-i18n";function ve(e,t,n){for(let r of t)if(e===r||e.startsWith(r)&&e.charAt(r.length)===n)return!0;return!1}function ye(e){let t=e?.config,n=t&&"prefix"in t?t.prefix??"":"data-hx-",r=t?.metaCharacter||":",o=n?["hx-on",n+"on"]:["hx-on"];return{init(i){let s=i?.HCON?.split;typeof s=="function"&&se(s.bind(i.HCON))},htmx_before_process(i){p(i)},htmx_before_process_node(i){p(i)},htmx_before_on_init(i){let s=W(i);if(s.size===0)return;let f=!1;for(let u of Array.from(i.attributes)){let c=u.name;if(!s.has(c)&&ve(c,o,r)){f=!0;break}}if(!f)return!1;Q(i)},onEvent(i,s){if(i!=="htmx:beforeProcessNode")return;let u=s?.detail?.elt??(s?.target instanceof Element?s.target:null);u&&p(u)}}}function M(e){if(!e)return null;let t=ye(e);return typeof e.registerExtension=="function"?e.registerExtension(m,t)===!1?(typeof console<"u"&&console.warn(`[htmx-i18n] htmx.registerExtension("${m}") returned false \u2014 the extension is not registered. If htmx.config.extensions (the <meta name="htmx-config"> allowlist) is set, add "${m}" to it. Swapped-in content will not be canonicalized; executor-mode claims fall back to removing canonical hx-on:* attrs.`),v(!0),null):(v(!1),"v4"):typeof e.defineExtension=="function"?(e.defineExtension(m,t),v(!0),"v2"):null}function U(e=document){let t=()=>{p(e.body??e.documentElement)},n=e.readyState==="complete",r=null;if(n)t();else{let s=()=>{n||(n=!0,r?.(),t())},f=e.defaultView;e.addEventListener("DOMContentLoaded",s),f?.addEventListener("load",s),r=()=>{e.removeEventListener("DOMContentLoaded",s),f?.removeEventListener("load",s)}}let o=C(()=>{n&&t()}),i=J(()=>{n&&t()});return()=>{r?.(),o(),i()}}var q="__hyperfixi_i18n";function Ee(){if(typeof window>"u")return;let e=window,t=e[q];if(t){let n=t.register.bind(t);e[q]={register:(r,o)=>{n(r,o),O(r,o)}};return}e[q]={register:O}}function we(){if(typeof window>"u"||typeof document>"u")return;let e=window;P(window),M(e.htmx)||document.addEventListener("DOMContentLoaded",()=>{!M(e.htmx)&&!e.htmx&&typeof console<"u"&&console.warn(`[htmx-i18n] window.htmx not found \u2014 the "${m}" extension was not registered. Dynamically swapped content will not be canonicalized (the initial document sweep still ran). Load htmx on this page.`)},{once:!0}),document.addEventListener("DOMContentLoaded",()=>P(window),{once:!0}),U(document)}Ee();we();return de(Ae);})();
2
2
  //# sourceMappingURL=htmx-i18n.global.js.map