docs(htmx_repo): add v2.0.8 changelog entry

This commit is contained in:
2026-01-05 11:24:23 -05:00
parent a291096d94
commit 889f2058c4
16 changed files with 2295 additions and 0 deletions

583
CHANGELOG.md Normal file
View File

@@ -0,0 +1,583 @@
# Changelog
## [2.0.8] - 2025-10-24
* [Updated](https://github.com/bigskysoftware/htmx/commit/b9336a96fbdcf28550699971dc2218a90c7a4e01) `parseHTML` to use to use the (unfortunately named) [`Document.parseHTMLUnsafe()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/parseHTMLUnsafe_static)
method for better Web Components support
* [Added](https://github.com/bigskysoftware/htmx/commit/83a1449a89b1fcd1c1655039ede02d74d61e4800) `pushURL` option to the `htmx.ajax()` API
* [Fixed](https://github.com/bigskysoftware/htmx/commit/cd045c3e0eb31776a80e3a4b4c74e37d0631c1f1) `hx-sync` and `htmx:abort` within the Shadow Dom [Issue 3419](https://github.com/bigskysoftware/htmx/issues/3419)
* [Fixed](https://github.com/bigskysoftware/htmx/commit/04d6c7249b7fd7b8518ddca92e7a70fdcc651b34) a long-standing bug in history support with respect to relative paths [Issue 3449](https://github.com/bigskysoftware/htmx/issues/3449)
* Once again, this is a release mainly done by @MichaelWest22's heroic work, thank you!
## [2.0.7] - 2025-09-08
* Fix not preventing link when inside htmx enabled element (fixes https://github.com/bigskysoftware/htmx/issues/3395)
* Implement `reportValidity()` for reporting proper form validation errors behind config flag (fixes https://github.com/bigskysoftware/htmx/issues/2372)
* Update indicator style to have `visibility:hidden` for screen readers (fixes https://github.com/bigskysoftware/htmx/issues/3354)
* Bugfix: swap="outerHTML" on <div> with style attribute leaves htmx-swapping class behind (see https://github.com/bigskysoftware/htmx/pull/3341)
## [2.0.6] - 2025-06-27
* [Fix](https://github.com/bigskysoftware/htmx/pull/3357) a [regression](https://github.com/bigskysoftware/htmx/issues/3356)
with htmx-powered links that contain other elements in them issuing full page refreshes
## [2.0.5] - 2025-06-20
* 100% test coverage! (Thank you @MichaelWest22!)
* The default recommended CDN is now jsDelivr
* The `inherit` keyword is now supported by `hx-include`, `hx-indicator` and `hx-disabled-elt` to allow you to inherit
the value from a parent and extend it.
* `hx-on` listeners are now added before processing nodes so events during processing can be captured
* Using `<button hx-verb="/endpoint" type="reset">` will now reset the associated form (after submitting to `/endpoint`)
* Using `<button formmethod="dialog">` will no longer submit its associated form
* Local history cache now uses `sessionStorage` rather than `localStorage` so cross-tab contamination doesn't occur
* History restoration now follows the standard swapping code paths
* Many other smaller bug and documentation fixes
## [2.0.4] - 2024-12-13
* Calling `htmx.ajax` with no target or source now defaults to body (previously did nothing)
* Nested [shadow root](https://github.com/bigskysoftware/htmx/commit/5ab508f6523a37890932176f7dc54be9f7a281ff) fix
* The `htmx:trigger` event now properly fires on the synthetic `load` event
* The synthetic `load` event will not be re-called when an element is reinitialized via `htmx.process()`
* Boosted `<form>` tags that issue a `GET` with no or empty `action` attributes will properly replace all existing query
parameters
* Events that are triggered on htmx-powered elements located outside a form, but that refer to a form via the`form`
attribute, now properly cancel the submission of the referred-to form
* Extension Updates
* `preload` extension was
[completely reworked](https://github.com/bigskysoftware/htmx-extensions/commit/fb68dfb48063505d2d7420d717c24ac9a8dae244)
by @marisst to be compatible with `hx-boost`, not cause side effect, etc. Thank you!
* `response-targets` was updated to not use deprecated methods
* A [small fix](https://github.com/bigskysoftware/htmx-extensions/commit/e87e1c3d0bf728b4e43861c7459f3f937883eb99) to
`ws` to avoid an error when closing in some cases
* The `head-support` extension was updated to work with the `sse` extension
## [2.0.3] - 2024-10-03
* Added support for the experimental `moveBefore()` functionality in [Chrome Canary](https://www.google.com/chrome/canary/),
see the [demo page](/examples/move-before) for more information.
* Fixed `revealed` event when a resize reveals an element
* Enabled `hx-preserve` in oob-swaps
* Better degredation of `hx-boost` on forms with query parameters in their `action`
* Improved shadowRoot support
* Many smaller bug fixes
* Moved the core extension documentation back to <https://htmx.org/extensions>
## [2.0.2] - 2024-08-12
* no longer boost forms of type `dialog`
* properly trigger the `htmx:trigger` event when an event is delayed or throttled
* file upload is now fixed
* empty templates that are not used for oob swaps are no longer removed from the DOM
* request indicators are not removed when a full page redirect or refresh occurs
* elements that have been disabled for a request are properly re-enabled before snapshotting for history
* you can now trigger events on other elements using the `HX-Trigger` response header
* The `.d.ts` file should now work properly
## [2.0.1] - 2024-07-12
* Make the `/dist/htmx.esm.js` file the `main` file in `package.json` to make installing htmx smoother
* Update `htmx.d.ts` & include it in the distribution
* A fix to avoid removing text-only templates on htmx cleanup
* A fix for outerHTML swapping of the `body` tag
* Many docs fixes
## [2.0.0] - 2024-06-17
* Removed extensions and moved to their own repos linked off of <https://extensions.htmx.org>
* The website now supports dark mode! (Thanks [@pokonski](https://github.com/pokonski)!)
* The older, deprecated [SSE & WS](https://v1.htmx.org/docs/#websockets-and-sse) attributes were removed
* Better support for [Web Components & Shadow DOM](https://htmx.org/examples/web-components/)
* HTTP `DELETE` requests now use parameters, rather than form encoded bodies, for their payload (This is in accordance w/ the spec.)
* Module support was split into different files:
* We now provide specific files in `/dist` for the various JavaScript module styles:
* ESM Modules: `/dist/htmx.esm.js`
* AMD Modules: `/dist/htmx.amd.js`
* CJS Modules: `/dist/htmx.cjs.js`
* The `/dist/htmx.js` file continues to be browser-loadable
* The `hx-on` attribute, with its special syntax, has been removed in favor of the less-hacky `hx-on:` syntax.
* See the [Upgrade Guide](https://htmx.org/migration-guide-htmx-1/) for more details on upgrade steps
* The `selectAndSwap()` internal API method was replaced with the public (and much better) [`swap()`](/api/#swap) method
## [1.9.12] - 2024-04-17
* [IE Fixes](https://github.com/bigskysoftware/htmx/commit/e64238dba3113c2eabe26b1e9e9ba7fe29ba3010)
## [1.9.11] - 2024-03-15
* Fix for new issue w/ web sockets & SSE on iOS 17.4 (thanks apple!)
* Fix for double script execution issue when using template parsing
* Fix TypeScript types file
* Fix SSE Ext: reinstantiate EventSource listeners upon reconnection logic (#2272)
## [1.9.10] - 2023-12-21
* `hx-on*` attributes now support the form `hx-on-`, with a trailing dash, to better support template systems (such as EJS)
that do not like double colons in HTML attributes.
* Added an `htmx.config.triggerSpecsCache` configuration property that can be set to an object to cache the trigger spec parsing
* Added a `path-params.js` extension for populating request paths with variable values
* Many smaller bug fixes & improvements
## [1.9.9] - 2023-11-21
* Allow CSS selectors with whitespace in attributes like `hx-target` by using parens or curly-braces
* Properly allow users to override the `Content-Type` request header
* Added the `select` option to `htmx.ajax()`
* Fixed a race condition in readystate detection that lead to htmx not being initialized in some scenarios with 3rd
party script loaders
* Fixed a bug that caused relative resources to resolve against the wrong base URL when a new URL is pushed
* Fixed a UI issue that could cause indicators to briefly flash
## [1.9.8] - 2023-11-06
* Fixed a few npm & build related issues
## [1.9.7] - 2023-11-03
* Fixed a bug where a button associated with a form that is swapped out of the DOM caused errors
* The `hx-target-error` attribute was added to the `response-targets` extension, allowing you to capture all 400 & 500
responses with a single attribute
* `hx-on` now properly supports multiple listeners
* The `hx-confirm` prompt is now passed into custom confirmation handlers
* `next` and `previous` are now valid _extended CSS_ symbols in htmx
* The `htmx:beforeHistoryUpdate` event was added
* Properly ignore the `dialog` formmethod on buttons when resolving the HTTP method to use
* Added a `htmx.config.scrollIntoViewOnBoost` option that may be set to `false` to disable scrolling the top of the
body into view for boosted elements
## [1.9.6] - 2023-09-22
* IE support has been restored (thank you @telroshan!)
* Introduced the `hx-disabled-elt` attribute to allow specifying elements to disable during a request
* You can now explicitly decide to ignore `title` tags found in new content via the `ignoreTitle` option in `hx-swap` and the `htmx.config.ignoreTitle` configuration variable.
* `hx-swap` modifiers may be used without explicitly specifying the swap mechanism
* Arrays are now supported in the `client-side-templates` extension
* XSLT support in the `client-side-templates` extension
* Support `preventDefault()` in extension event handling
* Allow the `HX-Refresh` header to apply even after an `HX-Redirect` has occurred
* the `formaction` and `formmethod` attributes on buttons are now properly respected
* `hx-on` can now handle events with dots in their name
* `htmx.ajax()` now always returns a Promise
* Handle leading `style` tag parsing more effectively
## [1.9.5] - 2023-08-25
* Web sockets now properly pass the target id in the HEADERS struct
* A very rare loading state bug was fixed (see https://github.com/bigskysoftware/htmx/commit/93bd81b6d003bb7bc445f10192bdb8089fa3495d)
* `hx-on` will not evaluate if `allowEval` is set to false
* You can disable the interpretation of script tags with the new `htmx.config.allowScriptTags` config variable
* You can now disable htmx-based requests to non-origin hosts via the `htmx.config.selfRequestsOnly` config variable
* The [Security](https://htmx.org/docs#security) section has been expanded to help developers better understand how to
properly secure their htmx-based applications.
## [1.9.4] - 2023-07-25
* This is a bug-fix release for the most part, w/a heavy dose of @telroshan
* The `HX-Trigger` response header now supports comma separated event names
* Submit buttons that use the `form` attribute now work correctly
* The `changed` modifier now uses the triggering element, rather than the element the `hx-trigger` is defined on
* `hx-disable` is now handled dynamically so it can be added and removed
* IE11 compatibility restored! (maybe, hard to test)
* Fixed bug with `hx-on` event handler cleanup
* Many smaller bug fixes, typo fixes and general improvements
## [1.9.3] - 2023-07-14
* The `hx-on` attribute has been deprecated (sorry) in favor of `hx-on:<event name>` attributes. See [`hx-on`](/attributes/hx-on) for more information.
* We now have functioning CI using GitHub actions!
* You can now configure if a type of HTTP request uses the body for parameters or not. In particular, the `DELETE` _should_ use
query parameters, according to the spec. htmx has used the body, instead. To avoid breaking code we are keeping this undefined
behavior for now, but allowing people to fix it for their use cases by updating the `htmx.config.methodsThatUseUrlParams` config
option. Thank you to Alex and Vincent for their feedback and work on this issue!
* The `this` symbol is now available in event filter expressions, and refers to the element the `hx-trigger` is on
* Fix bug where the `htmx:afterSettle` event was raised multiple times with oob swaps occurred
* A large number of accessibility fixes were made in the docs (Thank you Denis & crew!)
* Fixed bug w/ WebSocket extension initialization caused by "naked" `hx-trigger` feature
* The `HX-Reselect` HTTP response header has been added to change the selection from the returned content
* Many other smaller bug fixes
## [1.9.2] - 2023-04-28
* Fixed bug w/ `hx-on` not properly de-initializing
## [1.9.1] - 2023-04-27
* Fixed a bug with the new naked triggers that prevented boosted elements with explicit `hx-trigger`'s from functioning
properly
* Added code to play well with other libraries that also use the `window.onpopstate` Daily reminder: <https://htmx.org/img/memes/javascripthistory.png>
## [1.9.0] - 2023-04-11
* Support for generalized inline event handling via the new [`hx-on`](/attributes/hx-on) attribute, which addresses
the shortcoming of limited [`onevent` properties](https://developer.mozilla.org/en-US/docs/Web/Events/Event_handlers#using_onevent_properties) attributes in HTML.
* Support for [view transitions](/docs#view-transitions), based on the experimental [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API)
currently available in Chrome 111+ and coming to other browsers soon.
* Support for "naked" [`hx-trigger`](/attributes/hx-trigger) attributes, where an `hx-trigger` is present on an element
that does not have an `hx-get`, etc. defined on it. Instead, it will trigger the new `htmx:triggered` event, which can
be responded to via your [preferred scripting solution](/docs#scripting).
* A memory leak fix by [@croxton](https://github.com/bigskysoftware/htmx/commit/8cd3a480a7388877628ce8b9b8e50cd5df48bb81)
* The htmx website has been migrated from 11ty to [zola](https://www.getzola.org/) by [@danieljsummers](https://github.com/danieljsummers), cutting
way down on the number of "development" javascript dependencies
* Many other smaller bug fixes
## [1.8.6] - 2023-03-02
* ESM support!
* Sass has been vanquished from the htmx.org website, which should set us up for some good progress going forward
* Fixed a bug where the `changed` modifier on `keyup` did not work properly if an input was tabbed into
* Many other smaller bug fixes and doc fixes
## [1.8.5] - 2023-01-17
* Support a new optional cache-busting configuration option, `getCacheBusterParam`, to allow browsers to disambiguate
between `GET` requests from htmx and from the raw browser
* Support new `hx-history='false'` attribute, to prevent sensitive data from being stored in the history cache. (Thank you @croxton!)
* Extensive new event-oriented features are available in the [Web Socket](/extensions/web-sockets/) extension (Thank you @Renerick!)
* A bug fix for when a form contains multiple empty input values with the same name (Thank you @bluekeyes!)
* A bug fix around inputs that throw exceptions when calling `setSelectionRange()` (Thank you @gone!)
* A bug fix to pass through the proper event for the `htmx:configRequest` event
* A bug fix/improvement for the `preload` extension
* Many other small bug fixes
## [1.8.4] - 2022-11-05
* Fix the _exact same_ regression in `revealed` logic as in 1.8.2
## [1.8.3] - 2022-11-04
* A new [`htmx:confirm` event](/events#htmx:confirm) was added that allows for asynchronous confirmation dialogs to
be integrated into htmx requests
* The new [head-support](/extensions/head-support) extension allows for more elaborate head tag merging than standard htmx
supports. This functionality may be integrated into htmx 2.0, depending on feedback.
* The new [multi-swap](/extensions/multi-swap) provides more elaborate swapping of multiple elements on a screen using
a custom swap strategy
* Many doc fixes (thank you to everyone who contributed!)
## [1.8.2] - 2022-10-12
* Fix regression in `revealed` logic
## [1.8.1] - 2022-10-11
* We now keep a count of outstanding requests for an indicator, so more than one overlapping request can share the same
indicator without issues
* We now track the attribute state of an element and re-initialize it if `htmx.process()` is called on the element and
the attributes have changed
* [Idiomorph](https://github.com/bigskysoftware/idiomorph) is now available for all your morph-swapping needs
* The `unset` directive now works properly for `hx-vals` and `hx-vars`
* The title of the page is now properly set on a history cache miss
* The new [`hx-validate`](https://htmx.org/attributes/hx-validate) attribute will force elements to validate before a request, even if
they are not within a form being submitted
* Many smaller bug and docs fixes
## [1.8.0] - 2022-7-12
* **NOTE**: This release involved some changes to touchy code (e.g. history support) so please test thoroughly and let
us know if you see any issues
* Boosted forms now will automatically push URLs into history as with links. The [response URL](https://caniuse.com/mdn-api_xmlhttprequest_responseurl)
detection API support is good enough that we feel comfortable making this the default now.
* If you do not want this behavior you can add `hx-push-url='false'` to your boosted forms
* The [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url) attribute was introduced, allowing you to replace
the current URL in history (to complement `hx-push-url`)
* Bug fix - if htmx is included in a page more than once, we do not process elements multiple times
* Bug fix - When localStorage is not available we do not attempt to save history in it
* [Bug fix](https://github.com/bigskysoftware/htmx/issues/908) - `hx-boost` respects the `enctype` attribute
* `m` is now a valid timing modifier (e.g. `hx-trigger="every 2m"`)
* `next` and `previous` are now valid extended query selector modifiers, e.g. `hx-target="next div"` will target the
next div from the current element
* Bug fix - `hx-boost` will boost anchor tags with a `_self` target
* The `load` event now properly supports event filters
* The websocket extension has had many improvements: (A huge thank you to Denis Palashevskii, our newest committer on the project!)
* Implement proper `hx-trigger` support
* Expose trigger handling API to extensions
* Implement safe message sending with sending queue
* Fix `ws-send` attributes connecting in new elements
* Fix OOB swapping of multiple elements in response
* The `HX-Location` response header now implements client-side redirects entirely within htmx
* The `HX-Reswap` response header allows you to change the swap behavior of htmx
* The new [`hx-select-oob`](https://htmx.org/attributes/hx-select-oob) attribute selects one or more elements from a server response to swap in via an out of band swap
* The new [`hx-replace-url`](https://htmx.org/attributes/hx-replace-url) attribute can be used to replace the current URL in the location
bar (very similar to `hx-push-url` but no new history entry is created). The corresponding `HX-Replace-Url` response header can be used as well.
* htmx now properly handles anchors in both boosted links, as well as in `hx-get`, etc. attributes
## [1.7.0] - 2022-02-22
* The new [`hx-sync`](https://htmx.org/attributes/hx-sync) attribute allows you to synchronize multiple element requests on a single
element using various strategies (e.g. replace)
* You can also now abort an element making a request by sending it the `htmx:abort` event
* [Server Sent Events](/extensions/server-sent-events) and [Web Sockets](/extensions/web-sockets) are now available as
extensions, in addition to the normal core support. In htmx 2.0, the current `hx-sse` and `hx-ws` attributes will be
moved entirely out to these new extensions. By moving these features to extensions we will be able to add functionality
to both of them without compromising the core file size of htmx. You are encouraged to move over to the new
extensions, but `hx-sse` and `hx-ws` will continue to work indefinitely in htmx 1.x.
* You can now mask out [attribute inheritance](/docs#inheritance) via the [`hx-disinherit`](https://htmx.org/attributes/hx-disinherit) attribute.
* The `HX-Push` header can now have the `false` value, which will prevent a history snapshot from occurring.
* Many new extensions, with a big thanks to all the contributors!
* A new [`alpine-morph`](/extensions/alpine-morph) allows you to use Alpine's swapping engine, which preserves Alpine
* A [restored](/extensions/restored) extension was added that will trigger a `restore` event on all elements in the DOM
on history restoration.
* A [loading-states](/extensions/loading-states) extension was added that allows you to easily manage loading states
while a request is in flight, including disabling elements, and adding and removing CSS classes.
* The `this` symbol now resolves properly for the [`hx-include`](https://htmx.org/attributes/hx-include) and [`hx-indicator`](https://htmx.org/attributes/hx-indicator)
attributes
* When an object is included via the [`hx-vals`](https://htmx.org/attributes/hx-vals) attribute, it will be converted to JSON (rather
than rendering as the string `[Object object]"`)
* You can now pass a swap style in to the `htmx.ajax()` function call.
* Poll events now contain a `target` attribute, allowing you to filter a poll on the element that is polling.
* Two new Out Of Band-related events were added: `htmx:oobBeforeSwap` & `htmx:oobAfterSwap`
## [1.6.1] - 2021-11-22
* A new `HX-Retarget` header allows you to change the default target of returned content
* The `htmx:beforeSwap` event now includes another configurable property: `detail.isError` which can
be used to indicate if a given response should be treated as an error or not
* The `htmx:afterRequest` event has two new detail properties: `success` and `failed`, allowing you to write
trigger filters in htmx or hyperscript:
```applescript
on htmx:afterRequest[failed]
set #myCheckbox's checked to true
```
* Fixed the `from:` option in [`hx-trigger`](https://htmx.org/attributes/hx-trigger) to support `closest <CSS selector>`
and `find <CSS selector>` forms
* Don't boost anchor tags with an explicit `target` set
* Don't cancel all events on boosted elements, only the events that naturally trigger them (click for anchors, submit
for forms)
* Persist revealed state in the DOM so that on history navigation, revealed elements are not re-requested
* Process all [`hx-ext`](https://htmx.org/attributes/hx-ext) attributes, even if no other htmx attribute is on the element
* Snapshot the current URL on load so that history support works properly after a page refresh occurs
* Many, many documentation updates (thank you to all the contributors!)
## [1.6.0] - 2021-10-01
* Completely reworked `<script>` tag support that now supports the `<script src="...'/>` form
* You can now use the value `unset` to clear a property that would normally be inherited (e.g. hx-confirm)
* The `htmx-added` class is added to new content before a swap and removed after the settle phase, which allows you
more flexibility in writing CSS transitions for added content (rather than relying on the target, as with `htmx-settling`)
* The `htmx:beforeSwap` event has been updated to allow you to [configure swapping](https://htmx.org/docs/#modifying_swapping_behavior_with_events)
behavior
* Improved `<title>` extraction support
* You can listen to events on the `window` object using the `from:` modifier in `hx-trigger`
* The `root` option of the `intersect` event was fixed
* Boosted forms respect the `enctype` declaration
* The `HX-Boosted` header will be sent on requests from boosted elements
* Promises are not returned from the main ajax function unless it is an api call (i.e. `htmx.ajax`)
## [1.5.0] - 2021-7-12
* Support tracking of button clicked during a form submission
* Conditional polling via the [hx-trigger](https://htmx.org/attributes/hx-trigger) attribute
* `document` is now a valid pseudo-selector on the [hx-trigger](https://htmx.org/attributes/hx-trigger) `from:` argument, allowing you
to listen for events on the document.
* Added the [hx-request](https://htmx.org/attributes/hx-request) attribute, allowing you to configure the following aspects of the request
* `timeout` - the timeout of the request
* `credentials` - if the request will send credentials
* `noHeaders` - strips all headers from the request
* Along with the above attribute, you can configure the default values for each of these via the corresponding `htmx.config`
properties (e.g. `htmx.config.timeout`)
* Both the `scroll` and `show` options on [hx-swap](https://htmx.org/attributes/hx-swap) now support extended syntax for selecting the
element to scroll or to show, including the pseudo-selectors `window:top` and `window:bottom`.
## [1.4.1] - 2021-6-1
* typo fix
## [1.4.0] - 2021-5-25
* Added the `queue` option to the [hx-trigger](https://htmx.org/attributes/hx-trigger) attribute, allowing you to specify how events
should be queued when they are received with a request in flight
* The `htmx.config.useTemplateFragments` option was added, allowing you to use HTML template tags for parsing content
from the server. This allows you to use Out of Band content when returning things like table rows, but it is not
IE11 compatible.
* The `defaultSettleDelay` was dropped to 20ms from 100ms
* Introduced a new synthetic event, [intersect](https://htmx.org/docs#pecial-events) that allows you to trigger when an item is scrolled into view
as specified by the `IntersectionObserver` API
* Fixed timing issue that caused exceptions in the `reveal` logic when scrolling at incredible speeds - <https://github.com/bigskysoftware/htmx/issues/463>
* Fixed bug causing SVG titles to be incorrectly used as page title - <https://github.com/bigskysoftware/htmx/issues/459>
* Boosted forms that issue a GET will now push the URL by default - <https://github.com/bigskysoftware/htmx/issues/485>
* Better dispatch of request events when an element is removed from the DOM
* Fixed a bug causing `hx-prompt` to fail
* The `htmx.config.withCredentials` option was added, to send credentials with ajax requests (default is `false`)
* The `throttle` option on `hx-trigger` does not delay the initial request any longer
* The `meta` key is ignored on boosted links
* `<script>` tags are now evaluated in the global scope
* `hx-swap` now supports the `none` option
* Safari text selection bug - <https://github.com/bigskysoftware/htmx/issues/438>
## [1.3.3] - 2021-4-5
* Added the [`hx-disabled`](https://htmx.org/docs#security) attribute to allow htmx to be turned off for parts of the DOM
* SSE now uses a full-jitter exponential backoff algorithm on reconnection, using the `htmx.config.wsReconnectDelay`
setting
## [1.3.2] - 2021-3-9
* Bug fixes
## [1.3.1] - 2021-3-9
* IE11 fixes
## [1.3.0] - 2021-3-6
* Support a `target` modifier on `hx-trigger` to filter based on the element targeted by an event. This allows
lazy binding to that target selector.
* Events are no longer consumed by the first element that might handle them, unless the `consume` keyword is
added to the `hx-trigger` specification
* Added the `htmx:beforeSend` event, fired just before an ajax request begins
* SSE swaps are properly settled
* Fixed bug that was improperly cancelling all clicks on anchors
* `htmx.ajax()` now returns a promise
## [1.2.1] - 2021-2-19
* Fixed an issue with the history cache, where the cache was getting blown out after the first navigation backwards
* Added the `htmx.config.refreshOnHistoryMiss` option, allowing users to trigger a full page refresh on history cache miss
rather than issuing an AJAX request
## [1.2.0] - 2021-2-13
### New Features
* `hx-vars` has been deprecated in favor of `hx-vals`
* `hx-vals` now supports a `javascript:` prefix to achieve the behavior that `hx-vars` provided
* The new `hx-headers` attribute allows you to add headers to a request via an attribute. Like `hx-vals` it supports
JSON or javascript via the `javascript:` prefix
* `hx-include` will now include all inputs under an element, even if that element is not a form tag
* The [preload extension](https://htmx.org/extensions/preload/) now offers a `preload-images="true"` attribute that will aggressively load images in preloaded content
* On requests driven by a history cache miss, the new `HX-History-Restore-Request` header is included so that the server
can differentiate between history requests and normal requests
### Improvements & Bug fixes
* Improved handling of precedence of input values to favor the enclosing form (see [here](https://github.com/bigskysoftware/htmx/commit/a10e43d619dc340aa324d37772c06a69a2f47ec9))
* Moved event filtering logic *after* `preventDefault` so filtering still allows events to be properly handled
* No longer trigger after swap events on elements that have been removed via an `outerHTML` swap
* Properly remove event handlers added to other elements when an element is removed from the DOM
* Handle the `scroll:` modifier in `hx-swap` properly when an `outerHTML` swap occurs
* Lots of docs fixes
## [1.1.0] - 2021-1-6
* Newly added [preload extension](https://htmx.org/extensions/preload/) allows you to preload resources for lower
latency requests!
* Support the `ignore:` modifier for extensions
* Updated form variable order inclusion to include the enclosing form *last* so that, in the presence of multiple
values, the most relevant value is the most likely to be selected by the server
* Support for the [`htmx.ajax()`](https://dev.htmx.org/api/#ajax) javascript function, to issue an htmx-style ajax
request from javascript
* Removed the following htmx request headers for better cache behavior: `HX-Event-Target`, `HX-Active-Element`,
`HX-Active-Element-Name`, `HX-Active-Element-Value`
* Added the [`hx-preserve`](https://dev.htmx.org/attributes/hx-preserve) attribute, which allows
you to preserve elements across requests (for example, to keep a video element playing properly)
* The [path-deps](https://dev.htmx.org/extensions/path-deps/#refresh) now surfaces a small api
for refreshing path dependencies manually in javascript
* Now support the `from:` clause on [`hx-trigger`](https://dev.htmx.org/attributes/hx-trigger) to
allow an element to respond to events on other elements.
* Added the `htmx:beforeProcessNode` event, renamed the (previously undocumented) `htmx:processedNode` to `htmx:afterProcessNode`
* Added `closest` syntax support for the [`hx-indicator`](https://dev.htmx.org/attributes/hx-indicator) attribute
* Added `on load` support for the newest version of [hyperscript](https://hyperscript.org)
* Added the `htmx.config.allowEval` configuration value, for CSP compatibility
* Bug fixes & improvements
## [1.0.2] - 2020-12-12
* Extend all API methods to take a string selector as well as an element
* Out of band swap elements need not be top level now
* [`hx-swap-oob`](https://htmx.org/attributes/hx-swap-oob) now can accept a CSS selector to retarget with
## [1.0.1] - 2020-12-04
* AJAX file upload now correctly fires events, allowing for [a proper progress bar](https://htmx.org/examples/file-upload)
* htmx api functions that expect an element now can accept a string selector instead:
```js
htmx.on('#form', 'htmx:xhr:progress', function(evt) {
htmx.find('#progress').setAttribute('value', evt.detail.loaded/evt.detail.total * 100)
});
```
* htmx now properly handles the `multiple` attribute on `<select>` elements
## [1.0.0] - 2020-11-24
* Bumped the release version :)
## [0.4.1] - 2020-11-23
* Fixed bug with title tag support when title tag contained HTML entities
* Pass properties for the `loadstart`, `loadend`, `progress`, `abort` events through properly to the htmx equivalents
## [0.4.0] - 2020-11-16
* Now support the `HX-Redirect` and `HX-Refresh` response headers for redirecting client side and triggering a page refresh, respectively
* `hx-vars` now overrides input values
* `<title>` tags in responses will be used to update page titles
* All uses of `eval()` have been removed in favor of `Function`
* [`hx-vals`](https://htmx.org/attributes/hx-vals) is available as a safe alternative to `hx-vars`. It uses `JSON.parse()` rather than evaluation, if you wish to safely pass user-provided values through to htmx.
## [0.3.0] - 2020-10-27
* `hx-trigger` parsing has been rewritten and now supports [trigger filters](https://htmx.org/docs/#trigger-filters) to filter
events based on arbitrary javascript expressions
* htmx now supports two additional response headers `HX-Trigger-After-Swap` and `HX-Trigger-After-Settle` allowing
an event to be triggered after a given life cycle event (instead of before the swap)
* The `requestConfig` is now passed out to events surrounding the AJAX life cycle
* htmx now evaluates `<script>` tags as javascript when no language is defined on them
* A new [`event-header`](https://htmx.org/extensions/event-header) extension, which will include a serialized JSON representation of the triggering event in requests
## [0.2.0] - 2020-9-30
* AJAX file upload [support](https://htmx.org/docs#files)
* The HTML validation API is [respected](https://htmx.org/docs#validation)
## [0.1.0] - 2020-9-18
* *BREAKING CHANGE*: The SSE attribute [`hx-sse`](https://htmx.org/attributes/hx-sse/) and the Web Sockets attribute [`hx-ws`](https://htmx.org/attributes/hx-ws) have changed syntax to now use colon separators: `hx-sse='connect:/chat swap:message'`
* The SSE attribute [`hx-sse`](https://htmx.org/attributes/hx-sse/) allows for swapping content directly on an event, in addition to triggering an htmx element,
with the new `swap:<event name>` syntax.
* [`hx-target`](https://htmx.org/attributes/hx-target) now supports a `find` syntax to find elements below the element by a CSS selector
* htmx plays better with deferred loading and many package managers
* All htmx events are dispatched in both camelCase as well as kebab-case, for better compatibility with AlpineJS and other frameworks. (e.g. `htmx:afterOnLoad` will also be triggered as
`htmx:after-on-load`)
* [hypeerscript](https://hyperscript.org) is now initialized independently of htmx
## [0.0.8] - 2020-7-8
* The `view` modifier on `hx-swap` has been renamed to `show`: `hx-swap='innerHTML show:top'`
## [0.0.7] - 2020-6-30
* The [`hx-swap`](https://htmx.org/attributes/hx-swap) attribute now supports two new modifiers:
* `scroll` - allows you to scroll the target to the `top` or `bottom`
* `view` - allows you to scroll the `top` or `bottom` of the target into view
* The [`hx-push-url`](https://htmx.org/attributes/hx-push-url) attribute now can optionally take a URL to push, in addition to `true` and `false`
* Added the [`hx-vars`](https://htmx.org/attributes/hx-vars) attribute that allows you to dynamically add to the parameters that will be submitted with a request
## [0.0.6] - 2020-6-20
* Custom request/response headers no longer start with the `X-` prefix, which is no longer recommended
* empty verb attributes are now allowed and follow the anchor tag semantics (e.g. `<div hx-get></div>`)
* nunjuks inline rendering is now supported in the `client-side-templates` extension
* the new `ajax-header` extension includes the `X-Requested-With` header
* bad JSON is now handled more gracefully
* `hx-swap="none"` will cause no swap to take place <https://github.com/bigskysoftware/htmx/issues/89>
* `hx-trigger` now supports a `throttle` modifier <https://github.com/bigskysoftware/htmx/issues/88>
* the focused element is preserved if possible after a replacement
* perf improvements for large DOM trees with sparse `hx-` annotations
## [0.0.4] - 2020-5-24
* Extension mechanism added
* SSE support added
* WebSocket support added
## [0.0.3] - 2020-5-17
* Renamed to htmx
* A bug fix for the `hx-prompt` attribute
* A bug fix for multiple `hx-swap-oob` attributes
* Moved the default CSS indicator injection into its own sheet to avoid breaking
* Added the `htmx.config.includeIndicatorStyles` configuration option so people can opt out of injecting the indicator CSS
## [0.0.1] - 2020-5-15
* Initial release (originally named kutty)

140
components/stepper.templ Normal file
View File

@@ -0,0 +1,140 @@
package components
import "strconv"
// StepInfo represents a single step in the stepper
type StepInfo struct {
Number int
Label string
}
// OnboardingStepper renders a 3-step progress indicator
// currentStep: 1-indexed current step number
// steps: slice of step labels (e.g., []string{"Welcome", "Learn", "Get Started"})
templ OnboardingStepper(currentStep int, steps []string) {
@stepperStyles()
<div class="onboarding-stepper">
for i, label := range steps {
@stepperItem(i+1, label, currentStep)
if i < len(steps)-1 {
@stepperLine(i+1 < currentStep)
}
}
</div>
}
// stepperItem renders a single step circle with label
templ stepperItem(stepNum int, label string, currentStep int) {
<div class={ "stepper-item", templ.KV("active", stepNum == currentStep), templ.KV("completed", stepNum < currentStep) } data-step={ strconv.Itoa(stepNum) }>
<div class="stepper-number">
if stepNum < currentStep {
<wa-icon name="check" style="font-size: 14px;"></wa-icon>
} else {
{ strconv.Itoa(stepNum) }
}
</div>
<span class="stepper-label">{ label }</span>
</div>
}
// stepperLine renders the connecting line between steps
templ stepperLine(completed bool) {
<div class={ "stepper-line", templ.KV("completed", completed) }></div>
}
// ProgressDots renders a compact dot-based progress indicator
templ ProgressDots(current int, total int) {
<div class="progress-dots">
for i := 1; i <= total; i++ {
<div class={ "progress-dot", templ.KV("active", i == current), templ.KV("completed", i < current) }></div>
}
</div>
}
templ stepperStyles() {
<style>
.onboarding-stepper {
display: flex;
justify-content: center;
align-items: center;
gap: var(--wa-space-s);
padding: var(--wa-space-m) 0;
}
.stepper-item {
display: flex;
align-items: center;
gap: var(--wa-space-xs);
}
.stepper-number {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--wa-font-size-s);
font-weight: 600;
background: var(--wa-color-neutral-200);
color: var(--wa-color-neutral-600);
transition: all 0.3s;
}
.stepper-item.active .stepper-number {
background: var(--wa-color-primary);
color: white;
box-shadow: 0 0 0 4px var(--wa-color-primary-subtle);
}
.stepper-item.completed .stepper-number {
background: var(--wa-color-success);
color: white;
}
.stepper-label {
font-size: var(--wa-font-size-xs);
color: var(--wa-color-neutral-500);
display: none;
}
@media (min-width: 480px) {
.stepper-label {
display: block;
}
}
.stepper-item.active .stepper-label {
color: var(--wa-color-primary);
font-weight: 500;
}
.stepper-line {
width: 40px;
height: 2px;
background: var(--wa-color-neutral-200);
transition: background 0.3s;
}
.stepper-line.completed {
background: var(--wa-color-success);
}
/* Progress dots */
.progress-dots {
display: flex;
justify-content: center;
gap: var(--wa-space-xs);
}
.progress-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--wa-color-neutral-200);
transition: all 0.3s;
}
.progress-dot.active {
background: var(--wa-color-primary);
transform: scale(1.25);
}
.progress-dot.completed {
background: var(--wa-color-success);
}
/* Responsive adjustments */
@media (max-height: 600px) {
.onboarding-stepper {
padding: var(--wa-space-s) 0;
}
}
</style>
}

304
components/stepper_templ.go Normal file
View File

@@ -0,0 +1,304 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package components
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import "strconv"
// StepInfo represents a single step in the stepper
type StepInfo struct {
Number int
Label string
}
// OnboardingStepper renders a 3-step progress indicator
// currentStep: 1-indexed current step number
// steps: slice of step labels (e.g., []string{"Welcome", "Learn", "Get Started"})
func OnboardingStepper(currentStep int, steps []string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = stepperStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"onboarding-stepper\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i, label := range steps {
templ_7745c5c3_Err = stepperItem(i+1, label, currentStep).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if i < len(steps)-1 {
templ_7745c5c3_Err = stepperLine(i+1 < currentStep).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// stepperItem renders a single step circle with label
func stepperItem(stepNum int, label string, currentStep int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
if templ_7745c5c3_Var2 == nil {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var3 = []any{"stepper-item", templ.KV("active", stepNum == currentStep), templ.KV("completed", stepNum < currentStep)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var3).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/stepper.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" data-step=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(stepNum))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/stepper.templ`, Line: 28, Col: 154}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\"><div class=\"stepper-number\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if stepNum < currentStep {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<wa-icon name=\"check\" style=\"font-size: 14px;\"></wa-icon>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(stepNum))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/stepper.templ`, Line: 33, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div><span class=\"stepper-label\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(label)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/stepper.templ`, Line: 36, Col: 37}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</span></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// stepperLine renders the connecting line between steps
func stepperLine(completed bool) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
var templ_7745c5c3_Var9 = []any{"stepper-line", templ.KV("completed", completed)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/stepper.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// ProgressDots renders a compact dot-based progress indicator
func ProgressDots(current int, total int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
if templ_7745c5c3_Var11 == nil {
templ_7745c5c3_Var11 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"progress-dots\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i := 1; i <= total; i++ {
var templ_7745c5c3_Var12 = []any{"progress-dot", templ.KV("active", i == current), templ.KV("completed", i < current)}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var12).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `components/stepper.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
func stepperStyles() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
if templ_7745c5c3_Var14 == nil {
templ_7745c5c3_Var14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<style>\n\t\t.onboarding-stepper {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tgap: var(--wa-space-s);\n\t\t\tpadding: var(--wa-space-m) 0;\n\t\t}\n\t\t.stepper-item {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tgap: var(--wa-space-xs);\n\t\t}\n\t\t.stepper-number {\n\t\t\twidth: 32px;\n\t\t\theight: 32px;\n\t\t\tborder-radius: 50%;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tfont-size: var(--wa-font-size-s);\n\t\t\tfont-weight: 600;\n\t\t\tbackground: var(--wa-color-neutral-200);\n\t\t\tcolor: var(--wa-color-neutral-600);\n\t\t\ttransition: all 0.3s;\n\t\t}\n\t\t.stepper-item.active .stepper-number {\n\t\t\tbackground: var(--wa-color-primary);\n\t\t\tcolor: white;\n\t\t\tbox-shadow: 0 0 0 4px var(--wa-color-primary-subtle);\n\t\t}\n\t\t.stepper-item.completed .stepper-number {\n\t\t\tbackground: var(--wa-color-success);\n\t\t\tcolor: white;\n\t\t}\n\t\t.stepper-label {\n\t\t\tfont-size: var(--wa-font-size-xs);\n\t\t\tcolor: var(--wa-color-neutral-500);\n\t\t\tdisplay: none;\n\t\t}\n\t\t@media (min-width: 480px) {\n\t\t\t.stepper-label {\n\t\t\t\tdisplay: block;\n\t\t\t}\n\t\t}\n\t\t.stepper-item.active .stepper-label {\n\t\t\tcolor: var(--wa-color-primary);\n\t\t\tfont-weight: 500;\n\t\t}\n\t\t.stepper-line {\n\t\t\twidth: 40px;\n\t\t\theight: 2px;\n\t\t\tbackground: var(--wa-color-neutral-200);\n\t\t\ttransition: background 0.3s;\n\t\t}\n\t\t.stepper-line.completed {\n\t\t\tbackground: var(--wa-color-success);\n\t\t}\n\t\t/* Progress dots */\n\t\t.progress-dots {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: center;\n\t\t\tgap: var(--wa-space-xs);\n\t\t}\n\t\t.progress-dot {\n\t\t\twidth: 8px;\n\t\t\theight: 8px;\n\t\t\tborder-radius: 50%;\n\t\t\tbackground: var(--wa-color-neutral-200);\n\t\t\ttransition: all 0.3s;\n\t\t}\n\t\t.progress-dot.active {\n\t\t\tbackground: var(--wa-color-primary);\n\t\t\ttransform: scale(1.25);\n\t\t}\n\t\t.progress-dot.completed {\n\t\t\tbackground: var(--wa-color-success);\n\t\t}\n\t\t/* Responsive adjustments */\n\t\t@media (max-height: 600px) {\n\t\t\t.onboarding-stepper {\n\t\t\t\tpadding: var(--wa-space-s) 0;\n\t\t\t}\n\t\t}\n\t</style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module nebula
go 1.25.5
require github.com/a-h/templ v0.3.977 // indirect

2
go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg=
github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=

40
handlers/routes.go Normal file
View File

@@ -0,0 +1,40 @@
package handlers
import (
"net/http"
"strconv"
"nebula/views"
)
// RegisterRoutes sets up all HTTP routes for the application
func RegisterRoutes(mux *http.ServeMux) {
// Welcome page routes
mux.HandleFunc("GET /", handleWelcome)
mux.HandleFunc("GET /welcome", handleWelcome)
mux.HandleFunc("GET /welcome/step/{step}", handleWelcomeStep)
}
// handleWelcome renders the full welcome page at step 1
func handleWelcome(w http.ResponseWriter, r *http.Request) {
views.WelcomePage(1).Render(r.Context(), w)
}
// handleWelcomeStep handles HTMX partial updates for step navigation
func handleWelcomeStep(w http.ResponseWriter, r *http.Request) {
stepStr := r.PathValue("step")
step, err := strconv.Atoi(stepStr)
if err != nil || step < 1 || step > 3 {
step = 1
}
// Check if this is an HTMX request
if r.Header.Get("HX-Request") == "true" {
// Return only the step content fragment
views.WelcomeStepContent(step).Render(r.Context(), w)
return
}
// Full page render for non-HTMX requests
views.WelcomePage(step).Render(r.Context(), w)
}

1
htmx_repo Submodule

Submodule htmx_repo added at 749d5f2f4c

28
layouts/base.templ Normal file
View File

@@ -0,0 +1,28 @@
package layouts
// Base provides the HTML document structure for all pages
templ Base(title string) {
<!DOCTYPE html>
<html lang="en" class="wa-cloak">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>{ title } - Sonr Motr Wallet</title>
<script src="https://cdn.sonr.org/wa/autoloader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"></script>
<style>
:root {
--wa-color-primary: #17c2ff;
}
html, body {
min-height: 100%;
padding: 0;
margin: 0;
}
</style>
</head>
<body>
{ children... }
</body>
</html>
}

62
layouts/base_templ.go Normal file
View File

@@ -0,0 +1,62 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package layouts
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// Base provides the HTML document structure for all pages
func Base(title string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" class=\"wa-cloak\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `layouts/base.templ`, Line: 10, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Sonr Motr Wallet</title><script src=\"https://cdn.sonr.org/wa/autoloader.js\"></script><script src=\"https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js\"></script><style>\n\t\t\t\t:root {\n\t\t\t\t\t--wa-color-primary: #17c2ff;\n\t\t\t\t}\n\t\t\t\thtml, body {\n\t\t\t\t\tmin-height: 100%;\n\t\t\t\t\tpadding: 0;\n\t\t\t\t\tmargin: 0;\n\t\t\t\t}\n\t\t\t</style></head><body>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

43
layouts/centered.templ Normal file
View File

@@ -0,0 +1,43 @@
package layouts
// CenteredCard layout for auth pages (login, register, welcome, authorize)
// Optimized for popup/webview viewports: 360×540 to 480×680
templ CenteredCard(title string) {
@Base(title) {
@centeredStyles()
<wa-page>
<main class="main-centered">
<wa-card>
{ children... }
</wa-card>
</main>
</wa-page>
}
}
// centeredStyles contains the CSS for the centered card layout
templ centeredStyles() {
<style>
.main-centered {
display: flex;
justify-content: center;
align-items: center;
min-height: 100%;
padding: var(--wa-space-l);
box-sizing: border-box;
}
.main-centered wa-card {
width: 100%;
max-width: 48ch;
}
/* Viewport constraints for popup/webview (360-480px width) */
@media (max-width: 400px) {
.main-centered {
padding: var(--wa-space-m);
}
.main-centered wa-card {
max-width: 100%;
}
}
</style>
}

102
layouts/centered_templ.go Normal file
View File

@@ -0,0 +1,102 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package layouts
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
// CenteredCard layout for auth pages (login, register, welcome, authorize)
// Optimized for popup/webview viewports: 360×540 to 480×680
func CenteredCard(title string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = centeredStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, " <wa-page><main class=\"main-centered\"><wa-card>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</wa-card></main></wa-page>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = Base(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// centeredStyles contains the CSS for the centered card layout
func centeredStyles() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<style>\n\t\t.main-centered {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tmin-height: 100%;\n\t\t\tpadding: var(--wa-space-l);\n\t\t\tbox-sizing: border-box;\n\t\t}\n\t\t.main-centered wa-card {\n\t\t\twidth: 100%;\n\t\t\tmax-width: 48ch;\n\t\t}\n\t\t/* Viewport constraints for popup/webview (360-480px width) */\n\t\t@media (max-width: 400px) {\n\t\t\t.main-centered {\n\t\t\t\tpadding: var(--wa-space-m);\n\t\t\t}\n\t\t\t.main-centered wa-card {\n\t\t\t\tmax-width: 100%;\n\t\t\t}\n\t\t}\n\t</style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate

18
main.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
"net/http"
"nebula/handlers"
)
func main() {
mux := http.NewServeMux()
handlers.RegisterRoutes(mux)
addr := ":8080"
fmt.Printf("Starting server at http://localhost%s\n", addr)
log.Fatal(http.ListenAndServe(addr, mux))
}

64
migration-guide.md Normal file
View File

@@ -0,0 +1,64 @@
+++
title = "htmx 1.x &rarr; htmx 2.x Migration Guide"
+++
The purpose of this guide is to provide instructions for migrations from htmx 1.x to htmx 2.x.
We place a very high value on backwards compatibility, so in most cases this migrations should require very little, if any, work.
* If you are using htmx in a module setting, we now provide module-type specific files for all three of the major
JavaScript module types
* ESM Modules: `/dist/htmx.esm.js`
* AMD Modules: `/dist/htmx.amd.js`
* CJS Modules: `/dist/htmx.cjs.js`
* The `/dist/htmx.js` file continues to be browser-loadable
* All extensions have been removed from the core htmx distribution and are distributed separately. While many 1.x
extensions will continue to work with htmx 2, you
must upgrade the SSE extension to the 2.x version, and it is recommended that you upgrade all of them to the 2.x
versions.
* If you are still using the legacy `hx-ws` and `hx-sse` attributes, please upgrade to the extension versions
* Default Changes
* If you want to retain the 1.0 behavior of "smooth scrolling" by default, revert `htmx.config.scrollBehavior` to `'smooth'`
* If you want `DELETE` requests to use a form-encoded body rather than parameters, revert
`htmx.config.methodsThatUseUrlParams` to `["get"]` (it's a little crazy, but `DELETE`, according to the spec, should
use request parameters like `GET`.)
* If you want to make cross-domain requests with htmx, revert `htmx.config.selfRequestsOnly` to `false`
* Convert any `hx-on` attributes to their `hx-on:` equivalent:
```html
<button hx-get="/info" hx-on="htmx:beforeRequest: alert('Making a request!')
htmx:afterRequest: alert('Done making a request!')">
Get Info!
</button>
```
becomes:
```html
<button hx-get="/info" hx-on:htmx:before-request="alert('Making a request!')"
hx-on:htmx:after-request="alert('Done making a request!')">
Get Info!
</button>
Note that you must use the kebab-case of the event name due to the fact that attributes are case-insensitive in HTML.
```
* The `htmx.makeFragment()` method now **always** returns a `DocumentFragment` rather than either an `Element` or `DocumentFragment`
* If you are an extension author and your extension was using `selectAndSwap` method from internal API, it was removed and replaced with `swap` method,
which is available from both internal and public htmx APIs
To do a swap using new method, you need to simply use
```js
let content = "<div>Hello world</div>"; // this is HTML that will be swapped into target
let target = api.getTarget(child);
let swapSpec = api.getSwapSpecification(child);
api.swap(target, content, swapSpec);
```
`swap` method documentation is available on [JS API Reference](/api/#swap)
IE is no longer supported in htmx 2.0, but [htmx 1.x](https://v1.htmx.org) continues to support IE and will be supported
for the foreseeable future.
## Upgrade Music
This is the official htmx 1.x -> 2.x upgrade music:
<iframe width="640" height="360" src="https://www.youtube.com/embed/YDkD-N5goMg" title="PYLOT - Upgrades (Visualizer)"
frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

65
v2-release-post.md Normal file
View File

@@ -0,0 +1,65 @@
+++
title = "htmx 2.0.0 has been released!"
date = 2024-06-17
[taxonomies]
tag = ["posts", "announcements"]
+++
## htmx 2.0.0 Release
I'm very happy to announce the release of htmx 2.0. This release ends support for Internet Explorer and tightens up some
defaults, but does not change most of the core functionality or the core API of the library.
Note that we are not marking 2.0 as [`latest`](https://docs.npmjs.com/cli/v10/commands/npm-dist-tag#purpose) in NPM
because we do not want to force-upgrade users who are relying on non-versioned CDN URLs for htmx. Instead, 1.x will
remain `latest` and the 2.0 line will remain `next` until Jan 1, 2025. The website, however, will reference 2.0.
### Major Changes
* All extensions have been moved out of the core repository to [their own repo](https://github.com/bigskysoftware/htmx-extensions/)
and website: <https://extensions.htmx.org>. They are now all versioned individually and can be developed outside of
the normal (slow) htmx release cadence.
* Most 1.x extensions will work with 2.x, however the SSE extension did have a break and must be upgraded.
* The older extensions remain in the `/dist/ext` directory so as to not break the URLs of CDNs like unpkg, but please
move to the new extension URLs going forward
* We removed the deprecated `hx-sse` and `hx-ws` attributes in favor of the extensions, which were available and
recommended in 1.x.
* HTTP `DELETE` requests now use parameters, rather than form encoded bodies, for their payload (This is in accordance w/ the spec.)
* We now provide specific files in `/dist` for the various JavaScript module styles:
* ESM Modules: `/dist/htmx.esm.js`
* AMD Modules: `/dist/htmx.amd.js`
* CJS Modules: `/dist/htmx.cjs.js`
* The `/dist/htmx.js` file continues to be browser-loadable
* The `hx-on` attribute, with its special syntax, has been removed in favor of the less-hacky `hx-on:` syntax.
### Minor Changes
* We made some default changes:
* `htmx.config.scrollBehavior` was changed to `'instant'` from `'smooth'`
* As mentioned previously, `DELETE` requests now use query parameters, rather than a form-encoded body. This can
be reverted by setting `htmx.methodsThatUseUrlParams` to the value `['get']`,
* `htmx.config.selfRequestsOnly` now defaults to `true` rather than `false`
### Features
Not much, really:
* The `selectAndSwap()` internal API method was replaced with the public (and much better) [`swap()`](/api/#swap) method
* Web Component support has been [improved dramatically](@/examples/web-components.md)
* And the biggest feature of this release: [the website](https://htmx.org) now supports dark mode! (Thanks [@pokonski](https://github.com/pokonski)!)
A complete upgrade guide can be found here:
[htmx 1.x -> 2.x Migration Guide](@/migration-guide-htmx-1.md)
If you require IE compatibility, the [1.x](https://v1.htmx.org) will continue to be supported for the foreseeable future.
### Installing
htmx 2.0 can be installed via a package manager referencing version `2.0.0`, or can be linked via a CDN:
```html
<script src="https://unpkg.com/htmx.org@2.0.0/dist/htmx.min.js"></script>
```
or <a href="https://unpkg.com/htmx.org@2.0.0/dist/htmx.min.js" download>Downloaded</a>

343
views/welcome.templ Normal file
View File

@@ -0,0 +1,343 @@
package views
import (
"nebula/components"
"nebula/layouts"
)
var welcomeSteps = []string{"Welcome", "Learn", "Get Started"}
// WelcomePage renders the full onboarding page with the specified step
templ WelcomePage(currentStep int) {
@layouts.CenteredCard("Welcome") {
<div slot="header">
@components.OnboardingStepper(currentStep, welcomeSteps)
</div>
<div id="step-content">
@WelcomeStepContent(currentStep)
</div>
<footer slot="footer">
@WelcomeFooter()
</footer>
}
}
// WelcomeStepContent renders the content for a specific step (used for HTMX partials)
templ WelcomeStepContent(step int) {
switch step {
case 1:
@WelcomeStep1()
case 2:
@WelcomeStep2()
case 3:
@WelcomeStep3()
}
}
// WelcomeStep1 - Initial welcome screen with features
templ WelcomeStep1() {
@welcomeStyles()
<div class="wa-stack wa-gap-l">
<div class="hero-icon">
<wa-icon name="wallet" family="duotone"></wa-icon>
</div>
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h1 class="wa-heading-xl">Welcome to Sonr</h1>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Your self-sovereign identity wallet powered by WebAssembly
</p>
</div>
<wa-divider></wa-divider>
<div class="wa-stack wa-gap-s">
@FeatureItem("shield-check", "security", "Passwordless Security", "Authenticate with biometrics or hardware keys - no passwords to remember or steal")
@FeatureItem("bolt", "speed", "WASM-Powered", "Wallet runs entirely in your browser - fast, secure, and always available")
@FeatureItem("user-shield", "privacy", "You Own Your Data", "Self-sovereign identity means your keys never leave your device")
</div>
<wa-button
variant="brand"
size="large"
style="width: 100%;"
hx-get="/welcome/step/2"
hx-target="#step-content"
hx-swap="innerHTML"
>
Learn More
<wa-icon slot="end" name="arrow-right"></wa-icon>
</wa-button>
</div>
}
// WelcomeStep2 - How Motr Works explanation
templ WelcomeStep2() {
@welcomeStyles()
<div class="wa-stack wa-gap-l">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">How Motr Works</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
A next-generation wallet built on WebAuthn and WASM
</p>
</div>
<wa-divider></wa-divider>
<div class="wa-stack wa-gap-m">
@InfoCallout("microchip", "WebAssembly Runtime", "Your wallet logic runs as compiled Go code in a secure WASM sandbox, directly in your browser.")
@InfoCallout("fingerprint", "Passkey Authentication", "Use Face ID, Touch ID, or hardware security keys. Your biometrics stay on your device.")
@InfoCallout("network-wired", "Decentralized Identity", "Connect to any app with OpenID Connect - you control what data to share.")
@InfoCallout("key", "Multi-Chain Support", "One wallet for Sonr, Ethereum, Cosmos, Bitcoin and more via IBC.")
</div>
<div class="wa-cluster wa-gap-s" style="justify-content: space-between;">
<wa-button
variant="neutral"
appearance="outlined"
hx-get="/welcome/step/1"
hx-target="#step-content"
hx-swap="innerHTML"
>
<wa-icon slot="start" name="arrow-left"></wa-icon>
Back
</wa-button>
<wa-button
variant="brand"
hx-get="/welcome/step/3"
hx-target="#step-content"
hx-swap="innerHTML"
>
Get Started
<wa-icon slot="end" name="arrow-right"></wa-icon>
</wa-button>
</div>
</div>
}
// WelcomeStep3 - Action selection (Create/Sign In)
templ WelcomeStep3() {
@welcomeStyles()
<div class="wa-stack wa-gap-l">
<div class="wa-stack wa-gap-xs" style="text-align: center;">
<h2 class="wa-heading-l">Ready to Begin?</h2>
<p class="wa-caption-m" style="color: var(--wa-color-neutral-600);">
Create a new wallet or sign in to an existing one
</p>
</div>
<wa-divider></wa-divider>
<div class="wa-grid" style="--min-column-size: 180px; gap: var(--wa-space-m);">
<a href="/register" class="action-card">
<wa-icon name="user-plus" family="duotone"></wa-icon>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-m">Create Wallet</span>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
New to Sonr? Set up your wallet in minutes
</span>
</div>
</a>
<a href="/login" class="action-card">
<wa-icon name="right-to-bracket" family="duotone"></wa-icon>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-m">Sign In</span>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
Access your existing wallet
</span>
</div>
</a>
</div>
<wa-divider>or</wa-divider>
<wa-button
variant="neutral"
appearance="outlined"
style="width: 100%;"
hx-get="/welcome/qr-scanner"
hx-target="#step-content"
hx-swap="innerHTML"
>
<wa-icon slot="start" name="qrcode"></wa-icon>
Scan QR Code
</wa-button>
<wa-callout variant="brand" appearance="filled">
<wa-icon slot="icon" name="circle-info"></wa-icon>
<span class="wa-caption-s">
Already have a passkey registered on another device? Use QR code to sync your wallet.
</span>
</wa-callout>
<wa-button
variant="neutral"
appearance="plain"
style="width: 100%;"
hx-get="/welcome/step/2"
hx-target="#step-content"
hx-swap="innerHTML"
>
<wa-icon slot="start" name="arrow-left"></wa-icon>
Back to How It Works
</wa-button>
</div>
}
// WelcomeFooter renders the network status and links footer
templ WelcomeFooter() {
@welcomeStyles()
<div class="wa-stack wa-gap-m wa-align-items-center">
<div class="network-status">
<div class="status-dot"></div>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-600);">
Sonr Network: <strong style="color: var(--wa-color-success);">Operational</strong>
</span>
</div>
<div class="wa-cluster wa-justify-content-center wa-gap-m">
<wa-button appearance="plain" size="small" onclick="window.open('https://sonr.io', '_blank')">
Learn about Sonr
</wa-button>
<span style="color: var(--wa-color-neutral-300);">|</span>
<wa-button appearance="plain" size="small" onclick="window.open('https://docs.sonr.io', '_blank')">
Documentation
</wa-button>
</div>
</div>
}
// FeatureItem renders a feature highlight with icon
templ FeatureItem(icon string, variant string, title string, description string) {
<div class="feature-item">
<div class={ "feature-icon", variant }>
<wa-icon name={ icon }></wa-icon>
</div>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-xs">{ title }</span>
<span class="wa-caption-s" style="color: var(--wa-color-neutral-500);">
{ description }
</span>
</div>
</div>
}
// InfoCallout renders an informational callout box
templ InfoCallout(icon string, title string, description string) {
<wa-callout variant="neutral">
<wa-icon slot="icon" name={ icon }></wa-icon>
<div class="wa-stack wa-gap-2xs">
<span class="wa-heading-xs">{ title }</span>
<span class="wa-caption-s">{ description }</span>
</div>
</wa-callout>
}
// welcomeStyles contains the CSS specific to the welcome page
templ welcomeStyles() {
<style>
.hero-icon {
width: 80px;
height: 80px;
border-radius: var(--wa-radius-l);
background: linear-gradient(135deg, var(--wa-color-primary), #0090ff);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--wa-space-l);
box-shadow: 0 8px 32px rgba(23, 194, 255, 0.3);
}
.hero-icon wa-icon {
font-size: 40px;
color: white;
}
.feature-item {
display: flex;
align-items: flex-start;
gap: var(--wa-space-m);
padding: var(--wa-space-m);
background: var(--wa-color-surface-alt);
border-radius: var(--wa-radius-m);
}
.feature-icon {
width: 40px;
height: 40px;
border-radius: var(--wa-radius-s);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.feature-icon.security {
background: var(--wa-color-success-subtle);
color: var(--wa-color-success);
}
.feature-icon.speed {
background: var(--wa-color-primary-subtle);
color: var(--wa-color-primary);
}
.feature-icon.privacy {
background: var(--wa-color-warning-subtle);
color: var(--wa-color-warning);
}
.action-card {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--wa-space-m);
padding: var(--wa-space-xl);
background: var(--wa-color-surface-alt);
border-radius: var(--wa-radius-l);
border: 2px solid transparent;
cursor: pointer;
transition: all 0.2s;
text-align: center;
text-decoration: none;
color: inherit;
}
.action-card:hover {
border-color: var(--wa-color-primary);
background: var(--wa-color-primary-subtle);
}
.action-card wa-icon {
font-size: 2.5rem;
color: var(--wa-color-primary);
}
.network-status {
display: flex;
align-items: center;
justify-content: center;
gap: var(--wa-space-xs);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--wa-color-success);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Viewport constraints for popup/webview */
@media (max-width: 400px) {
.hero-icon {
width: 64px;
height: 64px;
}
.hero-icon wa-icon {
font-size: 32px;
}
.feature-item {
padding: var(--wa-space-s);
}
.feature-icon {
width: 32px;
height: 32px;
}
.action-card {
padding: var(--wa-space-m);
}
.action-card wa-icon {
font-size: 2rem;
}
}
@media (max-height: 600px) {
.hero-icon {
width: 56px;
height: 56px;
margin-bottom: var(--wa-space-m);
}
.hero-icon wa-icon {
font-size: 28px;
}
}
</style>
}

495
views/welcome_templ.go Normal file
View File

@@ -0,0 +1,495 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
package views
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"nebula/components"
"nebula/layouts"
)
var welcomeSteps = []string{"Welcome", "Learn", "Get Started"}
// WelcomePage renders the full onboarding page with the specified step
func WelcomePage(currentStep int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div slot=\"header\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = components.OnboardingStepper(currentStep, welcomeSteps).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div><div id=\"step-content\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = WelcomeStepContent(currentStep).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div><footer slot=\"footer\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = WelcomeFooter().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</footer>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
templ_7745c5c3_Err = layouts.CenteredCard("Welcome").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// WelcomeStepContent renders the content for a specific step (used for HTMX partials)
func WelcomeStepContent(step int) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
if templ_7745c5c3_Var3 == nil {
templ_7745c5c3_Var3 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
switch step {
case 1:
templ_7745c5c3_Err = WelcomeStep1().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 2:
templ_7745c5c3_Err = WelcomeStep2().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
case 3:
templ_7745c5c3_Err = WelcomeStep3().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
return nil
})
}
// WelcomeStep1 - Initial welcome screen with features
func WelcomeStep1() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
if templ_7745c5c3_Var4 == nil {
templ_7745c5c3_Var4 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = welcomeStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"wa-stack wa-gap-l\"><div class=\"hero-icon\"><wa-icon name=\"wallet\" family=\"duotone\"></wa-icon></div><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h1 class=\"wa-heading-xl\">Welcome to Sonr</h1><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Your self-sovereign identity wallet powered by WebAssembly</p></div><wa-divider></wa-divider><div class=\"wa-stack wa-gap-s\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = FeatureItem("shield-check", "security", "Passwordless Security", "Authenticate with biometrics or hardware keys - no passwords to remember or steal").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = FeatureItem("bolt", "speed", "WASM-Powered", "Wallet runs entirely in your browser - fast, secure, and always available").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = FeatureItem("user-shield", "privacy", "You Own Your Data", "Self-sovereign identity means your keys never leave your device").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div><wa-button variant=\"brand\" size=\"large\" style=\"width: 100%;\" hx-get=\"/welcome/step/2\" hx-target=\"#step-content\" hx-swap=\"innerHTML\">Learn More <wa-icon slot=\"end\" name=\"arrow-right\"></wa-icon></wa-button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// WelcomeStep2 - How Motr Works explanation
func WelcomeStep2() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var5 := templ.GetChildren(ctx)
if templ_7745c5c3_Var5 == nil {
templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = welcomeStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"wa-stack wa-gap-l\"><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h2 class=\"wa-heading-l\">How Motr Works</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">A next-generation wallet built on WebAuthn and WASM</p></div><wa-divider></wa-divider><div class=\"wa-stack wa-gap-m\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = InfoCallout("microchip", "WebAssembly Runtime", "Your wallet logic runs as compiled Go code in a secure WASM sandbox, directly in your browser.").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = InfoCallout("fingerprint", "Passkey Authentication", "Use Face ID, Touch ID, or hardware security keys. Your biometrics stay on your device.").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = InfoCallout("network-wired", "Decentralized Identity", "Connect to any app with OpenID Connect - you control what data to share.").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = InfoCallout("key", "Multi-Chain Support", "One wallet for Sonr, Ethereum, Cosmos, Bitcoin and more via IBC.").Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div><div class=\"wa-cluster wa-gap-s\" style=\"justify-content: space-between;\"><wa-button variant=\"neutral\" appearance=\"outlined\" hx-get=\"/welcome/step/1\" hx-target=\"#step-content\" hx-swap=\"innerHTML\"><wa-icon slot=\"start\" name=\"arrow-left\"></wa-icon> Back</wa-button> <wa-button variant=\"brand\" hx-get=\"/welcome/step/3\" hx-target=\"#step-content\" hx-swap=\"innerHTML\">Get Started <wa-icon slot=\"end\" name=\"arrow-right\"></wa-icon></wa-button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// WelcomeStep3 - Action selection (Create/Sign In)
func WelcomeStep3() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = welcomeStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<div class=\"wa-stack wa-gap-l\"><div class=\"wa-stack wa-gap-xs\" style=\"text-align: center;\"><h2 class=\"wa-heading-l\">Ready to Begin?</h2><p class=\"wa-caption-m\" style=\"color: var(--wa-color-neutral-600);\">Create a new wallet or sign in to an existing one</p></div><wa-divider></wa-divider><div class=\"wa-grid\" style=\"--min-column-size: 180px; gap: var(--wa-space-m);\"><a href=\"/register\" class=\"action-card\"><wa-icon name=\"user-plus\" family=\"duotone\"></wa-icon><div class=\"wa-stack wa-gap-2xs\"><span class=\"wa-heading-m\">Create Wallet</span> <span class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">New to Sonr? Set up your wallet in minutes</span></div></a> <a href=\"/login\" class=\"action-card\"><wa-icon name=\"right-to-bracket\" family=\"duotone\"></wa-icon><div class=\"wa-stack wa-gap-2xs\"><span class=\"wa-heading-m\">Sign In</span> <span class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">Access your existing wallet</span></div></a></div><wa-divider>or</wa-divider> <wa-button variant=\"neutral\" appearance=\"outlined\" style=\"width: 100%;\" hx-get=\"/welcome/qr-scanner\" hx-target=\"#step-content\" hx-swap=\"innerHTML\"><wa-icon slot=\"start\" name=\"qrcode\"></wa-icon> Scan QR Code</wa-button> <wa-callout variant=\"brand\" appearance=\"filled\"><wa-icon slot=\"icon\" name=\"circle-info\"></wa-icon> <span class=\"wa-caption-s\">Already have a passkey registered on another device? Use QR code to sync your wallet.</span></wa-callout> <wa-button variant=\"neutral\" appearance=\"plain\" style=\"width: 100%;\" hx-get=\"/welcome/step/2\" hx-target=\"#step-content\" hx-swap=\"innerHTML\"><wa-icon slot=\"start\" name=\"arrow-left\"></wa-icon> Back to How It Works</wa-button></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// WelcomeFooter renders the network status and links footer
func WelcomeFooter() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var7 := templ.GetChildren(ctx)
if templ_7745c5c3_Var7 == nil {
templ_7745c5c3_Var7 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = welcomeStyles().Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"wa-stack wa-gap-m wa-align-items-center\"><div class=\"network-status\"><div class=\"status-dot\"></div><span class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-600);\">Sonr Network: <strong style=\"color: var(--wa-color-success);\">Operational</strong></span></div><div class=\"wa-cluster wa-justify-content-center wa-gap-m\"><wa-button appearance=\"plain\" size=\"small\" onclick=\"window.open('https://sonr.io', '_blank')\">Learn about Sonr</wa-button> <span style=\"color: var(--wa-color-neutral-300);\">|</span> <wa-button appearance=\"plain\" size=\"small\" onclick=\"window.open('https://docs.sonr.io', '_blank')\">Documentation</wa-button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// FeatureItem renders a feature highlight with icon
func FeatureItem(icon string, variant string, title string, description string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"feature-item\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 = []any{"feature-icon", variant}
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<div class=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/welcome.templ`, Line: 1, Col: 0}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"><wa-icon name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/welcome.templ`, Line: 200, Col: 23}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"></wa-icon></div><div class=\"wa-stack wa-gap-2xs\"><span class=\"wa-heading-xs\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/welcome.templ`, Line: 203, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</span> <span class=\"wa-caption-s\" style=\"color: var(--wa-color-neutral-500);\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/welcome.templ`, Line: 205, Col: 17}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</span></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// InfoCallout renders an informational callout box
func InfoCallout(icon string, title string, description string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var14 := templ.GetChildren(ctx)
if templ_7745c5c3_Var14 == nil {
templ_7745c5c3_Var14 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<wa-callout variant=\"neutral\"><wa-icon slot=\"icon\" name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(icon)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/welcome.templ`, Line: 214, Col: 34}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\"></wa-icon><div class=\"wa-stack wa-gap-2xs\"><span class=\"wa-heading-xs\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/welcome.templ`, Line: 216, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</span> <span class=\"wa-caption-s\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(description)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/welcome.templ`, Line: 217, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</span></div></wa-callout>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// welcomeStyles contains the CSS specific to the welcome page
func welcomeStyles() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var18 := templ.GetChildren(ctx)
if templ_7745c5c3_Var18 == nil {
templ_7745c5c3_Var18 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<style>\n\t\t.hero-icon {\n\t\t\twidth: 80px;\n\t\t\theight: 80px;\n\t\t\tborder-radius: var(--wa-radius-l);\n\t\t\tbackground: linear-gradient(135deg, var(--wa-color-primary), #0090ff);\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tmargin: 0 auto var(--wa-space-l);\n\t\t\tbox-shadow: 0 8px 32px rgba(23, 194, 255, 0.3);\n\t\t}\n\t\t.hero-icon wa-icon {\n\t\t\tfont-size: 40px;\n\t\t\tcolor: white;\n\t\t}\n\t\t.feature-item {\n\t\t\tdisplay: flex;\n\t\t\talign-items: flex-start;\n\t\t\tgap: var(--wa-space-m);\n\t\t\tpadding: var(--wa-space-m);\n\t\t\tbackground: var(--wa-color-surface-alt);\n\t\t\tborder-radius: var(--wa-radius-m);\n\t\t}\n\t\t.feature-icon {\n\t\t\twidth: 40px;\n\t\t\theight: 40px;\n\t\t\tborder-radius: var(--wa-radius-s);\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tflex-shrink: 0;\n\t\t}\n\t\t.feature-icon.security {\n\t\t\tbackground: var(--wa-color-success-subtle);\n\t\t\tcolor: var(--wa-color-success);\n\t\t}\n\t\t.feature-icon.speed {\n\t\t\tbackground: var(--wa-color-primary-subtle);\n\t\t\tcolor: var(--wa-color-primary);\n\t\t}\n\t\t.feature-icon.privacy {\n\t\t\tbackground: var(--wa-color-warning-subtle);\n\t\t\tcolor: var(--wa-color-warning);\n\t\t}\n\t\t.action-card {\n\t\t\tdisplay: flex;\n\t\t\tflex-direction: column;\n\t\t\talign-items: center;\n\t\t\tgap: var(--wa-space-m);\n\t\t\tpadding: var(--wa-space-xl);\n\t\t\tbackground: var(--wa-color-surface-alt);\n\t\t\tborder-radius: var(--wa-radius-l);\n\t\t\tborder: 2px solid transparent;\n\t\t\tcursor: pointer;\n\t\t\ttransition: all 0.2s;\n\t\t\ttext-align: center;\n\t\t\ttext-decoration: none;\n\t\t\tcolor: inherit;\n\t\t}\n\t\t.action-card:hover {\n\t\t\tborder-color: var(--wa-color-primary);\n\t\t\tbackground: var(--wa-color-primary-subtle);\n\t\t}\n\t\t.action-card wa-icon {\n\t\t\tfont-size: 2.5rem;\n\t\t\tcolor: var(--wa-color-primary);\n\t\t}\n\t\t.network-status {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tgap: var(--wa-space-xs);\n\t\t}\n\t\t.status-dot {\n\t\t\twidth: 8px;\n\t\t\theight: 8px;\n\t\t\tborder-radius: 50%;\n\t\t\tbackground: var(--wa-color-success);\n\t\t\tanimation: pulse 2s infinite;\n\t\t}\n\t\t@keyframes pulse {\n\t\t\t0%, 100% { opacity: 1; }\n\t\t\t50% { opacity: 0.5; }\n\t\t}\n\t\t/* Viewport constraints for popup/webview */\n\t\t@media (max-width: 400px) {\n\t\t\t.hero-icon {\n\t\t\t\twidth: 64px;\n\t\t\t\theight: 64px;\n\t\t\t}\n\t\t\t.hero-icon wa-icon {\n\t\t\t\tfont-size: 32px;\n\t\t\t}\n\t\t\t.feature-item {\n\t\t\t\tpadding: var(--wa-space-s);\n\t\t\t}\n\t\t\t.feature-icon {\n\t\t\t\twidth: 32px;\n\t\t\t\theight: 32px;\n\t\t\t}\n\t\t\t.action-card {\n\t\t\t\tpadding: var(--wa-space-m);\n\t\t\t}\n\t\t\t.action-card wa-icon {\n\t\t\t\tfont-size: 2rem;\n\t\t\t}\n\t\t}\n\t\t@media (max-height: 600px) {\n\t\t\t.hero-icon {\n\t\t\t\twidth: 56px;\n\t\t\t\theight: 56px;\n\t\t\t\tmargin-bottom: var(--wa-space-m);\n\t\t\t}\n\t\t\t.hero-icon wa-icon {\n\t\t\t\tfont-size: 28px;\n\t\t\t}\n\t\t}\n\t</style>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate