@digicreon/mujs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Digicreon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,514 @@
1
+ # µJS (muJS)
2
+
3
+ **Lightweight AJAX navigation library — accelerate your website without a JS framework.**
4
+
5
+ µJS intercepts clicks on links and form submissions to load pages via AJAX instead of full browser navigation. The fetched content replaces part (or all) of the current page, making navigation faster and smoother.
6
+
7
+ No build step required. No dependencies. No framework. Just a single `<script>` tag.
8
+
9
+ Inspired by [pjax](https://github.com/defunkt/jquery-pjax), [Turbo](https://turbo.hotwired.dev/) and [HTMX](https://htmx.org/), µJS aims to be simpler and lighter while covering the most common use cases.
10
+
11
+ - 🚀 **Fast** — Prefetch on hover, no full page reload, progress bar
12
+ - 🪶 **Lightweight** — Single file, ~3 KB gzipped, zero dependencies
13
+ - 🔌 **Drop-in** — Works with any backend (PHP, Python, Ruby, Go…), no server-side changes needed
14
+ - 🧩 **Patch mode** — Update multiple page fragments in a single request
15
+ - ✨ **Modern** — View Transitions, DOM morphing (via idiomorph), `fetch` API, event delegation
16
+
17
+
18
+ ## Table of contents
19
+
20
+ - [Installation](#installation)
21
+ - [Quick start](#quick-start)
22
+ - [Modes](#modes)
23
+ - [Patch mode](#patch-mode)
24
+ - [Forms](#forms)
25
+ - [Ghost mode](#ghost-mode)
26
+ - [Scroll restoration](#scroll-restoration)
27
+ - [Prefetch](#prefetch)
28
+ - [DOM morphing](#dom-morphing)
29
+ - [View Transitions](#view-transitions)
30
+ - [Progress bar](#progress-bar)
31
+ - [Events](#events)
32
+ - [Attributes reference](#attributes-reference)
33
+ - [Configuration reference](#configuration-reference)
34
+ - [Programmatic API](#programmatic-api)
35
+ - [Browser support](#browser-support)
36
+ - [License](#license)
37
+
38
+
39
+ ## Installation
40
+
41
+ ### Via `<script>` tag (recommended)
42
+
43
+ ```html
44
+ <script src="/path/to/mu.min.js"></script>
45
+ <script>mu.init();</script>
46
+ ```
47
+
48
+ ### Via CDN
49
+
50
+ ```html
51
+ <!-- unpkg -->
52
+ <script src="https://unpkg.com/mujs/dist/mu.min.js"></script>
53
+
54
+ <!-- jsDelivr -->
55
+ <script src="https://cdn.jsdelivr.net/npm/mujs/dist/mu.min.js"></script>
56
+ ```
57
+
58
+ ### Via npm
59
+
60
+ ```bash
61
+ npm install mujs
62
+ ```
63
+
64
+
65
+ ## Quick start
66
+
67
+ After calling `mu.init()`, all internal links (URLs starting with `/`) are automatically intercepted. Clicking a link fetches the page via AJAX and replaces the current `<body>` with the fetched `<body>`. The page title is updated automatically. Browser history (back/forward buttons) works as expected.
68
+
69
+ ```html
70
+ <!DOCTYPE html>
71
+ <html>
72
+ <head>
73
+ <title>My site</title>
74
+ <script src="/path/to/mu.min.js"></script>
75
+ <script>mu.init();</script>
76
+ </head>
77
+ <body>
78
+ <!-- These links are automatically handled by µJS -->
79
+ <nav>
80
+ <a href="/">Home</a>
81
+ <a href="/about">About</a>
82
+ <a href="/contact">Contact</a>
83
+ </nav>
84
+
85
+ <main id="content">
86
+ <p>Page content here.</p>
87
+ </main>
88
+
89
+ <!-- This link is NOT handled (external URL) -->
90
+ <a href="https://example.com">External link</a>
91
+
92
+ <!-- This link is NOT handled (explicitly disabled) -->
93
+ <a href="/file.pdf" mu-disabled>Download PDF</a>
94
+ </body>
95
+ </html>
96
+ ```
97
+
98
+ To replace only a fragment of the page instead of the whole body:
99
+
100
+ ```html
101
+ <a href="/about" mu-target="#content" mu-source="#content">About</a>
102
+ ```
103
+
104
+ This fetches `/about`, extracts the `#content` element from the response, and replaces the current `#content` with it.
105
+
106
+
107
+ ## Modes
108
+
109
+ The `mu-mode` attribute controls how fetched content is injected into the page. Default: `replace`.
110
+
111
+ | Mode | Description |
112
+ |---|---|
113
+ | `replace` | Replace the target node with the source node (default). |
114
+ | `update` | Replace the inner content of the target with the source's inner content. |
115
+ | `prepend` | Insert the source node at the beginning of the target. |
116
+ | `append` | Insert the source node at the end of the target. |
117
+ | `before` | Insert the source node before the target. |
118
+ | `after` | Insert the source node after the target. |
119
+ | `remove` | Remove the target node (source is ignored). |
120
+ | `none` | Do nothing to the DOM (events are still fired). |
121
+ | `patch` | Process multiple targeted fragments (see [Patch mode](#patch-mode)). |
122
+
123
+ Example:
124
+
125
+ ```html
126
+ <a href="/notifications" mu-mode="update" mu-target="#notifs" mu-source="#notifs">
127
+ Refresh notifications
128
+ </a>
129
+ ```
130
+
131
+
132
+ ## Patch mode
133
+
134
+ Patch mode allows a single request to update multiple parts of the page. The server returns HTML fragments, each annotated with a target and an optional mode.
135
+
136
+ ### Link triggering a patch
137
+
138
+ ```html
139
+ <a href="/api/comments/new" mu-mode="patch">Add comment</a>
140
+ ```
141
+
142
+ ### Server response
143
+
144
+ The server returns plain HTML. Each element with a `mu-patch-target` attribute is a patch fragment:
145
+
146
+ ```html
147
+ <!-- Replaces #comment-42 (default mode: replace) -->
148
+ <div class="comment" mu-patch-target="#comment-42">
149
+ Updated comment text
150
+ </div>
151
+
152
+ <!-- Appends a new comment to #comments -->
153
+ <div class="comment" mu-patch-target="#comments" mu-patch-mode="append">
154
+ New comment
155
+ </div>
156
+
157
+ <!-- Updates the page title -->
158
+ <title mu-patch-target="title">New page title</title>
159
+
160
+ <!-- Adds a stylesheet -->
161
+ <link rel="stylesheet" href="/css/gallery.css"
162
+ mu-patch-target="head" mu-patch-mode="append">
163
+
164
+ <!-- Removes an element -->
165
+ <div mu-patch-target="#old-banner" mu-patch-mode="remove"></div>
166
+ ```
167
+
168
+ Patch fragments are standard HTML elements — no special tags needed. The `mu-patch-*` attributes are preserved on injected nodes for debugging.
169
+
170
+ The `mu-patch-mode` attribute accepts the same values as `mu-mode` (except `patch` and `none`). Default is `replace`.
171
+
172
+ ### Patch and browser history
173
+
174
+ By default, patch mode does not modify browser history. To add the URL to history:
175
+
176
+ ```html
177
+ <a href="/products?cat=3" mu-mode="patch" mu-patch-ghost="false">Filter</a>
178
+ ```
179
+
180
+
181
+ ## Forms
182
+
183
+ µJS intercepts form submissions. HTML5 validation (`reportValidity()`) is checked before any request.
184
+
185
+ ### GET forms
186
+
187
+ Data is serialized as a query string. Behaves like a link.
188
+
189
+ ```html
190
+ <form action="/search" method="get" mu-target="#results" mu-source="#results">
191
+ <input type="text" name="q">
192
+ <button type="submit">Search</button>
193
+ </form>
194
+ ```
195
+
196
+ ### POST forms
197
+
198
+ Data is sent as `FormData`. Ghost mode is enabled by default (POST responses should not be replayed via the browser back button).
199
+
200
+ ```html
201
+ <form action="/comment/create" method="post">
202
+ <textarea name="body"></textarea>
203
+ <button type="submit">Send</button>
204
+ </form>
205
+ ```
206
+
207
+ ### POST form with patch response
208
+
209
+ ```html
210
+ <form action="/comment/create" method="post" mu-mode="patch">
211
+ <textarea name="body"></textarea>
212
+ <button type="submit">Send</button>
213
+ </form>
214
+ ```
215
+
216
+ Server response:
217
+
218
+ ```html
219
+ <div class="comment" mu-patch-target="#comments" mu-patch-mode="append">
220
+ <p>The new comment</p>
221
+ </div>
222
+
223
+ <form action="/comment/create" method="post" mu-patch-target="#comment-form">
224
+ <textarea name="body"></textarea>
225
+ <button type="submit">Send</button>
226
+ </form>
227
+ ```
228
+
229
+ The new comment is appended to the list, and the form is replaced with a blank version.
230
+
231
+ ### Custom validation
232
+
233
+ ```html
234
+ <form action="/save" method="post" mu-validate="myValidator">...</form>
235
+ <script>
236
+ function myValidator(form) {
237
+ return form.querySelector('#name').value.length > 0;
238
+ }
239
+ </script>
240
+ ```
241
+
242
+ ### Quit-page confirmation
243
+
244
+ Add `mu-confirm-quit` to a form. If any input is modified, the user is prompted before navigating away:
245
+
246
+ ```html
247
+ <form action="/save" method="post" mu-confirm-quit>
248
+ <input type="text" name="title">
249
+ <button type="submit">Save</button>
250
+ </form>
251
+ ```
252
+
253
+
254
+ ## Ghost mode
255
+
256
+ Ghost mode prevents a navigation from being added to browser history and disables automatic scroll-to-top.
257
+
258
+ ```html
259
+ <!-- Ghost mode on a single link -->
260
+ <a href="/panel" mu-ghost>Open panel</a>
261
+
262
+ <!-- Ghost mode globally -->
263
+ <script>mu.init({ ghost: true });</script>
264
+ ```
265
+
266
+ In patch mode, ghost is enabled by default. Use `mu-patch-ghost="false"` to add the URL to history.
267
+
268
+
269
+ ## Scroll restoration
270
+
271
+ When the user navigates with the browser's back/forward buttons, µJS automatically restores the scroll position to where it was before leaving the page. This works out of the box — no configuration needed.
272
+
273
+
274
+ ## Prefetch
275
+
276
+ When enabled (default), µJS fetches the target page when the user hovers over a link, before they click. This saves ~100-300ms of perceived loading time.
277
+
278
+ The prefetch cache stores one entry per URL and is consumed on click.
279
+
280
+ ```html
281
+ <!-- Disable prefetch on a specific link -->
282
+ <a href="/heavy-page" mu-prefetch="false">Heavy page</a>
283
+
284
+ <!-- Disable prefetch globally -->
285
+ <script>mu.init({ prefetch: false });</script>
286
+ ```
287
+
288
+
289
+ ## DOM morphing
290
+
291
+ When a morph library is available, µJS uses it for `replace` and `update` modes to preserve DOM state (input focus, scroll positions, video playback, CSS transitions, etc.).
292
+
293
+ µJS auto-detects [idiomorph](https://github.com/bigskysoftware/idiomorph). Just load it before µJS:
294
+
295
+ ```html
296
+ <script src="/path/to/idiomorph.js"></script>
297
+ <script src="/path/to/mu.min.js"></script>
298
+ <script>mu.init();</script>
299
+ ```
300
+
301
+ If idiomorph is not loaded, µJS falls back to direct DOM replacement. No error, no warning.
302
+
303
+ Disable morphing globally or per-element:
304
+
305
+ ```html
306
+ <!-- Globally -->
307
+ <script>mu.init({ morph: false });</script>
308
+
309
+ <!-- Per-element -->
310
+ <a href="/page" mu-morph="false">Link</a>
311
+ ```
312
+
313
+ To use a different morph library:
314
+
315
+ ```javascript
316
+ mu.init();
317
+ mu.setMorph(function(target, html, opts) {
318
+ myMorphLib.morph(target, html, opts);
319
+ });
320
+ ```
321
+
322
+
323
+ ## View Transitions
324
+
325
+ µJS uses the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) when supported by the browser, providing smooth animated transitions between page states.
326
+
327
+ Enabled by default. Falls back silently on unsupported browsers.
328
+
329
+ ```html
330
+ <!-- Disable globally -->
331
+ <script>mu.init({ transition: false });</script>
332
+
333
+ <!-- Disable per-element -->
334
+ <a href="/page" mu-transition="false">Link</a>
335
+ ```
336
+
337
+
338
+ ## Progress bar
339
+
340
+ A thin progress bar (3px, blue) is displayed at the top of the page during fetch requests. It requires no external stylesheet.
341
+
342
+ The bar element has `id="mu-progress"` and can be customized via CSS:
343
+
344
+ ```css
345
+ #mu-progress {
346
+ background: red !important;
347
+ height: 5px !important;
348
+ }
349
+ ```
350
+
351
+ Disable globally:
352
+
353
+ ```html
354
+ <script>mu.init({ progress: false });</script>
355
+ ```
356
+
357
+
358
+ ## Events
359
+
360
+ µJS dispatches `CustomEvent` events on `document`. All events carry a `detail` object with `lastUrl` and `previousUrl`.
361
+
362
+ | Event | Cancelable | Description |
363
+ |---|---|---|
364
+ | `mu:init` | No | Fired after initialization. |
365
+ | `mu:before-fetch` | Yes | Fired before fetching. `preventDefault()` aborts the load. |
366
+ | `mu:before-render` | Yes | Fired after fetch, before DOM injection. `detail.html` can be modified. |
367
+ | `mu:after-render` | No | Fired after DOM injection. |
368
+ | `mu:fetch-error` | No | Fired on fetch failure or HTTP error. |
369
+
370
+ ### Example: run code after each page load
371
+
372
+ ```javascript
373
+ document.addEventListener("mu:after-render", function(e) {
374
+ console.log("Loaded: " + e.detail.url);
375
+ myApp.initWidgets();
376
+ });
377
+ ```
378
+
379
+ ### Example: cancel a navigation
380
+
381
+ ```javascript
382
+ document.addEventListener("mu:before-fetch", function(e) {
383
+ if (e.detail.url === "/restricted") {
384
+ e.preventDefault();
385
+ }
386
+ });
387
+ ```
388
+
389
+ ### Example: modify HTML before rendering
390
+
391
+ ```javascript
392
+ document.addEventListener("mu:before-render", function(e) {
393
+ e.detail.html = e.detail.html.replace("foo", "bar");
394
+ });
395
+ ```
396
+
397
+ ### Example: handle errors
398
+
399
+ ```javascript
400
+ document.addEventListener("mu:fetch-error", function(e) {
401
+ if (e.detail.status === 404) {
402
+ alert("Page not found");
403
+ }
404
+ });
405
+ ```
406
+
407
+
408
+ ## Attributes reference
409
+
410
+ All attributes support both `mu-*` and `data-mu-*` syntax.
411
+
412
+ | Attribute | Description |
413
+ |---|---|
414
+ | `mu-disabled` | Disable µJS on this element. |
415
+ | `mu-mode` | Injection mode (`replace`, `update`, `prepend`, `append`, `before`, `after`, `remove`, `none`, `patch`). |
416
+ | `mu-target` | CSS selector for the target node in the current page. |
417
+ | `mu-source` | CSS selector for the source node in the fetched page. |
418
+ | `mu-url` | Override the URL to fetch (instead of `href` / `action`). |
419
+ | `mu-prefix` | URL prefix for the fetch request. |
420
+ | `mu-title` | Selector for the title node. Supports `selector/attribute` syntax. Empty string to disable. |
421
+ | `mu-ghost` | Skip browser history and scroll-to-top. |
422
+ | `mu-ghost-redirect` | Skip history for HTTP redirections. |
423
+ | `mu-scroll-to-top` | Force (`true`) or prevent (`false`) scrolling to top. |
424
+ | `mu-morph` | Disable morphing on this element (`false`). |
425
+ | `mu-transition` | Disable view transitions on this element (`false`). |
426
+ | `mu-prefetch` | Disable prefetch on hover for this link (`false`). |
427
+ | `mu-post` | Force POST method on a link. |
428
+ | `mu-confirm` | Show a confirmation dialog before loading. |
429
+ | `mu-confirm-quit` | *(Forms)* Prompt before leaving if the form has been modified. |
430
+ | `mu-validate` | *(Forms)* Name of a JS validation function. Must return `true`/`false`. |
431
+ | `mu-patch-target` | *(Patch fragments)* CSS selector of the target node. |
432
+ | `mu-patch-mode` | *(Patch fragments)* Injection mode for this fragment. |
433
+ | `mu-patch-ghost` | Set to `false` to add the URL to browser history in patch mode. |
434
+
435
+
436
+ ## Configuration reference
437
+
438
+ Pass an object to `mu.init()` to override defaults:
439
+
440
+ ```javascript
441
+ mu.init({
442
+ ghost: true,
443
+ processForms: false,
444
+ morph: false,
445
+ progress: true
446
+ });
447
+ ```
448
+
449
+ | Option | Type | Default | Description |
450
+ |---|---|---|---|
451
+ | `processLinks` | bool | `true` | Intercept `<a>` tags. |
452
+ | `processForms` | bool | `true` | Intercept `<form>` tags. |
453
+ | `ghost` | bool | `false` | Ghost mode for all navigations. |
454
+ | `ghostRedirect` | bool | `false` | Ghost mode for HTTP redirections. |
455
+ | `mode` | string | `"replace"` | Default injection mode. |
456
+ | `target` | string | `"body"` | Default target CSS selector. |
457
+ | `source` | string | `"body"` | Default source CSS selector. |
458
+ | `title` | string | `"title"` | Title selector (`"selector"` or `"selector/attribute"`). |
459
+ | `scrollToTop` | bool\|null | `null` | Scroll behavior. `null` = auto (scroll unless ghost). |
460
+ | `urlPrefix` | string\|null | `null` | Prefix added to fetched URLs. |
461
+ | `progress` | bool | `true` | Show progress bar during fetch. |
462
+ | `prefetch` | bool | `true` | Prefetch pages on link hover. |
463
+ | `morph` | bool | `true` | Enable DOM morphing (requires idiomorph or custom morph function). |
464
+ | `transition` | bool | `true` | Enable View Transitions API. |
465
+ | `confirmQuitText` | string | `"Are you sure you want to leave this page?"` | Quit-page confirmation message. |
466
+
467
+
468
+ ## Programmatic API
469
+
470
+ ```javascript
471
+ // Load a page programmatically
472
+ mu.load("/page", { ghost: true, target: "#content" });
473
+
474
+ // Get the last URL loaded by µJS
475
+ mu.getLastUrl(); // "/about" or null
476
+
477
+ // Get the previous URL
478
+ mu.getPreviousUrl(); // "/" or null
479
+
480
+ // Enable/disable quit-page confirmation
481
+ mu.setConfirmQuit(true);
482
+ mu.setConfirmQuit(false);
483
+
484
+ // Register a custom morph function
485
+ mu.setMorph(function(target, html, opts) {
486
+ myMorphLib.morph(target, html, opts);
487
+ });
488
+ ```
489
+
490
+
491
+ ## Browser support
492
+
493
+ µJS works in all modern browsers:
494
+
495
+ - Chrome / Edge 89+
496
+ - Firefox 87+
497
+ - Safari 15+
498
+
499
+ View Transitions require Chrome/Edge 111+. On unsupported browsers, transitions are skipped silently.
500
+
501
+ DOM morphing requires a separate library (idiomorph recommended). Without it, µJS falls back to direct DOM replacement.
502
+
503
+ µJS does **not** support Internet Explorer.
504
+
505
+
506
+ ## License
507
+
508
+ [MIT](LICENSE)
509
+
510
+ ---
511
+
512
+ **µJS** is developed by [Digicreon](https://github.com/Digicreon).
513
+ Website: [mujs.org](https://mujs.org)
514
+
package/dist/mu.min.js ADDED
@@ -0,0 +1,2 @@
1
+ /* µJS (muJS) v1.0 - mujs.org */
2
+ var mu=new function(){this._VERSION="1.0",this._defaults={processLinks:!0,processForms:!0,ghost:!1,ghostRedirect:!1,mode:"replace",target:"body",source:"body",title:"title",scrollToTop:null,urlPrefix:null,progress:!0,prefetch:!0,morph:!0,transition:!0,confirmQuitText:"Are you sure you want to leave this page?"},this._cfg={},this._lastUrl=null,this._previousUrl=null,this._abortCtrl=null,this._progressEl=null,this._prefetchCache=new Map,this._confirmQuit=!1,this._jsIncludes={},this._morph=null,this.init=function(t){if(mu._cfg=Object.assign({},mu._defaults,t||{}),mu._cfg._titleAttr=null,mu._cfg.title){var e=mu._cfg.title.lastIndexOf("/");-1!==e&&(mu._cfg._titleAttr=mu._cfg.title.substring(e+1),mu._cfg.title=mu._cfg.title.substring(0,e))}mu._morph||void 0===window.Idiomorph||"function"!=typeof window.Idiomorph.morph||(mu._morph=function(t,e,r){window.Idiomorph.morph(t,e,r)}),document.addEventListener("click",mu._onClick),document.addEventListener("submit",mu._onSubmit),document.addEventListener("mouseover",mu._onMouseOver),document.addEventListener("input",mu._onInput),window.addEventListener("popstate",mu._onPopState),window.addEventListener("beforeunload",mu._onBeforeUnload),window.history.replaceState({mu:!0,url:location.pathname+location.search},""),mu._emit("mu:init",{url:location.pathname+location.search})},this.load=function(t,e){var r=Object.assign({},mu._cfg,e||{});mu._loadExec(t,r)},this.getLastUrl=function(){return mu._lastUrl},this.getPreviousUrl=function(){return mu._previousUrl},this.setConfirmQuit=function(t){mu._confirmQuit=!!t},this.setMorph=function(t){mu._morph=t},this._attr=function(t,e){return t.hasAttribute("mu-"+e)?t.getAttribute("mu-"+e):t.hasAttribute("data-mu-"+e)?t.getAttribute("data-mu-"+e):null},this._attrBool=function(t,e,r){var o=mu._attr(t,e);return null===o?r:""===o||"true"===o||"false"!==o&&r},this._shouldProcess=function(t){if("true"===mu._attr(t,"disabled")||""===mu._attr(t,"disabled"))return!1;if("false"===t.getAttribute("mu")||"false"===t.getAttribute("data-mu"))return!1;if(t.hasAttribute("target"))return!1;if("A"===t.tagName&&t.hasAttribute("onclick"))return!1;if("FORM"===t.tagName&&t.hasAttribute("onsubmit"))return!1;if("true"===t.getAttribute("mu")||"true"===t.getAttribute("data-mu"))return!0;var e=t.getAttribute("href")||t.getAttribute("action")||"";return!(!e.startsWith("/")||e.startsWith("//"))},this._elemCfg=function(t){var e,r=Object.assign({},mu._cfg);if(null!==(e=mu._attr(t,"mode"))&&(r.mode=e),null!==(e=mu._attr(t,"target"))&&(r.target=e),null!==(e=mu._attr(t,"source"))&&(r.source=e),null!==(e=mu._attr(t,"title"))){r.title=e,r._titleAttr=null;var o=e.lastIndexOf("/");-1!==o&&(r._titleAttr=e.substring(o+1),r.title=e.substring(0,o))}return null!==(e=mu._attr(t,"url"))&&(r._url=e),null!==(e=mu._attr(t,"prefix"))&&(r.urlPrefix=e),r.ghost=mu._attrBool(t,"ghost",r.ghost),r.ghostRedirect=mu._attrBool(t,"ghost-redirect",r.ghostRedirect),r.scrollToTop=mu._attrBool(t,"scroll-to-top",r.scrollToTop),r.morph=mu._attrBool(t,"morph",r.morph),r.transition=mu._attrBool(t,"transition",r.transition),r.post=mu._attrBool(t,"post",!1),r.confirm=mu._attr(t,"confirm"),r.patchGhost=mu._attrBool(t,"patch-ghost",!0),r},this._onClick=function(t){var e=t.target.closest("a");if(e&&mu._cfg.processLinks&&mu._shouldProcess(e)&&!(t.ctrlKey||t.metaKey||t.shiftKey||t.altKey)){t.preventDefault();var r=mu._elemCfg(e),o=r._url||e.getAttribute("href");if(r._sourceElement=e,!r.confirm||window.confirm(r.confirm)){if(mu._confirmQuit){if(!window.confirm(r.confirmQuitText))return;mu.setConfirmQuit(!1)}mu._loadExec(o,r)}}},this._onSubmit=function(t){var e=t.target.closest("form");if(e&&mu._cfg.processForms&&mu._shouldProcess(e)&&e.reportValidity()){var r=mu._attr(e,"validate");if(!r||"function"!=typeof window[r]||window[r](e)){var o=(e.getAttribute("method")||"get").toLowerCase(),u=mu._elemCfg(e),i=u._url||e.getAttribute("action");if(u._sourceElement=e,"post"===o)t.preventDefault(),u.postData=new FormData(e),e.hasAttribute("mu-ghost")||e.hasAttribute("data-mu-ghost")||(u.ghost=!0),null===u.scrollToTop&&"patch"!==u.mode&&(u.scrollToTop=!0);else{t.preventDefault();var n=new FormData(e);i=i+"?"+new URLSearchParams(n).toString()}mu.setConfirmQuit(!1),mu._loadExec(i,u)}}},this._onInput=function(t){t.target.closest("form[mu-confirm-quit], form[data-mu-confirm-quit]")&&mu.setConfirmQuit(!0)},this._onMouseOver=function(t){if(mu._cfg.prefetch){var e=t.target.closest("a");if(e&&mu._shouldProcess(e)&&!1!==mu._attrBool(e,"prefetch",!0)){var r=mu._attr(e,"url")||e.getAttribute("href");if(r&&!mu._prefetchCache.has(r)){mu._prefetchCache.set(r,null);var o=mu._cfg.urlPrefix?mu._cfg.urlPrefix+r:r;fetch(o,{headers:{"X-Requested-With":"mujs","X-Mu-Prefetch":"1"},priority:"low"}).then(function(t){return t.ok?t.text():null}).then(function(t){t?mu._prefetchCache.set(r,t):mu._prefetchCache.delete(r)}).catch(function(){mu._prefetchCache.delete(r)})}}}},this._onPopState=function(t){var e=t.state;if(e&&e.mu){var r=Object.assign({},mu._cfg);r.ghost=!0,r.scrollToTop=!1,r._restoreScroll=void 0!==e.scrollX?{x:e.scrollX,y:e.scrollY}:null,mu._loadExec(e.url,r)}else window.location.href=document.location},this._onBeforeUnload=function(t){mu._confirmQuit&&(t.preventDefault(),t.returnValue=mu._cfg.confirmQuitText)},this._loadExec=async function(t,e){var r=e.urlPrefix?e.urlPrefix+t:t;if(mu._saveScroll(),mu._emit("mu:before-fetch",{url:t,fetchUrl:r,config:e,sourceElement:e._sourceElement||null})){mu._abortCtrl&&mu._abortCtrl.abort(),mu._abortCtrl=new AbortController,mu._showProgress();try{var o=null,u=null,i=t;if(mu._prefetchCache.has(t)&&null!==mu._prefetchCache.get(t))o=mu._prefetchCache.get(t),mu._prefetchCache.delete(t);else{var n={signal:mu._abortCtrl.signal,headers:{"X-Requested-With":"mujs","X-Mu-Mode":e.mode}};if(e.postData?(n.method="POST",n.body=e.postData):e.post&&(n.method="POST"),!(u=await fetch(r,n)).ok)return void mu._emit("mu:fetch-error",{url:t,fetchUrl:r,status:u.status,response:u});u.redirected&&(i=new URL(u.url).pathname+new URL(u.url).search),o=await u.text()}var s,a={url:t,finalUrl:i,html:o,config:e};if(!mu._emit("mu:before-render",a))return;if(s="patch"===e.mode?function(){mu._renderPatch(a.html,e)}:function(){mu._renderPage(a.html,e)},e.transition&&document.startViewTransition?document.startViewTransition(s):s(),"patch"===e.mode)e.patchGhost||(mu._previousUrl=mu._lastUrl,mu._lastUrl=i,window.history.pushState({mu:!0,url:i},"",i));else e.ghost||u&&u.redirected&&e.ghostRedirect||e.postData?u&&u.redirected&&!e.ghostRedirect?(mu._previousUrl=mu._lastUrl,mu._lastUrl=i,window.history.pushState({mu:!0,url:i},"",i)):(mu._previousUrl=mu._lastUrl,mu._lastUrl=i):(mu._previousUrl=mu._lastUrl,mu._lastUrl=i,window.history.pushState({mu:!0,url:i},"",i));if("patch"!==e.mode)if(e._restoreScroll)window.scrollTo(e._restoreScroll.x,e._restoreScroll.y);else{var l=e.ghost||u&&u.redirected&&e.ghostRedirect;if(null!==e.scrollToTop?e.scrollToTop:!l){var m=t.indexOf("#");if(-1!==m){var c=document.getElementById(t.substring(m+1));c&&c.scrollIntoView({behavior:"smooth"})}else window.scrollTo(0,0)}}mu.setConfirmQuit(!1),mu._emit("mu:after-render",{url:t,finalUrl:i,mode:e.mode})}catch(e){if("AbortError"===e.name)return;mu._emit("mu:fetch-error",{url:t,fetchUrl:r,error:e})}finally{mu._hideProgress()}}},this._renderPage=function(t,e){var r=(new DOMParser).parseFromString(t,"text/html"),o=null;e.source&&(o=r.querySelector(e.source)),o||(o=r.body);var u=document.querySelector(e.target);if(u){mu._applyMode(e.mode,u,o,e),mu._updateTitle(r,e),mu._mergeHead(r);var i=document.querySelector(e.target)||document.body;mu._runScripts(i)}else console.warn("[µJS] Target element '"+e.target+"' not found.")},this._renderPatch=function(t,e){for(var r=(new DOMParser).parseFromString(t,"text/html").querySelectorAll("[mu-patch-target], [data-mu-patch-target]"),o=0;o<r.length;o++){var u=r[o],i=u.getAttribute("mu-patch-target")||u.getAttribute("data-mu-patch-target"),n=u.getAttribute("mu-patch-mode")||u.getAttribute("data-mu-patch-mode")||"replace",s=document.querySelector(i);s?(mu._applyMode(n,s,u,e),"remove"!==n&&mu._runScripts(u)):console.warn("[µJS] Patch target '"+i+"' not found.")}},this._applyMode=function(t,e,r,o){var u=o.morph&&mu._morph;switch(t){case"update":u?mu._morph(e,r.innerHTML,{morphStyle:"innerHTML"}):e.innerHTML=r.innerHTML;break;case"prepend":e.prepend(r);break;case"append":e.append(r);break;case"before":e.before(r);break;case"after":e.after(r);break;case"remove":e.remove();break;case"none":break;default:"BODY"===e.tagName&&"BODY"===r.tagName?u?mu._morph(e,r.innerHTML,{morphStyle:"innerHTML"}):e.innerHTML=r.innerHTML:u?mu._morph(e,r.outerHTML,{morphStyle:"outerHTML"}):e.replaceWith(r)}},this._updateTitle=function(t,e){if(e.title){var r=t.querySelector(e.title);if(r){var o=e._titleAttr?r.getAttribute(e._titleAttr):r.textContent;o&&(document.title=o)}}},this._mergeHead=function(t){for(var e="link[rel='stylesheet'], style, meta[name], meta[property]",r=document.head.querySelectorAll(e),o=t.head.querySelectorAll(e),u=new Map,i=0;i<r.length;i++)u.set(mu._elKey(r[i]),r[i]);var n=new Set;for(i=0;i<o.length;i++){var s=mu._elKey(o[i]);n.add(s),u.has(s)||document.head.appendChild(o[i].cloneNode(!0))}u.forEach(function(t,e){n.has(e)||t.remove()})},this._elKey=function(t){return"LINK"===t.tagName?"link:"+t.getAttribute("href"):"STYLE"===t.tagName?"style:"+t.textContent.substring(0,100):"META"===t.tagName?"meta:"+(t.getAttribute("name")||t.getAttribute("property")):t.outerHTML},this._runScripts=function(t){for(var e=t.querySelectorAll("script"),r=0;r<e.length;r++){var o=e[r];if("true"!==mu._attr(o,"disabled")&&""!==mu._attr(o,"disabled")){if(o.hasAttribute("src")){var u=o.getAttribute("src");if(mu._jsIncludes[u])continue;mu._jsIncludes[u]=!0}for(var i=document.createElement("script"),n=0;n<o.attributes.length;n++)i.setAttribute(o.attributes[n].name,o.attributes[n].value);i.textContent=o.textContent,o.parentNode.replaceChild(i,o)}}},this._showProgress=function(){if(mu._cfg.progress){if(!mu._progressEl){mu._progressEl=document.createElement("div"),mu._progressEl.id="mu-progress";var t=mu._progressEl.style;t.position="fixed",t.top="0",t.left="0",t.height="3px",t.background="#29d",t.zIndex="99999",t.transition="width .3s ease",t.width="0"}document.body.appendChild(mu._progressEl),mu._progressEl.offsetWidth,mu._progressEl.style.width="70%"}},this._hideProgress=function(){mu._progressEl&&(mu._progressEl.style.width="100%",setTimeout(function(){mu._progressEl&&(mu._progressEl.style.transition="none",mu._progressEl.style.width="0",mu._progressEl.offsetWidth,mu._progressEl.style.transition="width .3s ease",mu._progressEl.remove())},200))},this._saveScroll=function(){var t=window.history.state;t&&t.mu&&(t.scrollX=window.scrollX,t.scrollY=window.scrollY,window.history.replaceState(t,""))},this._emit=function(t,e){(e=e||{}).lastUrl=mu._lastUrl,e.previousUrl=mu._previousUrl;var r=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:e});return document.dispatchEvent(r)}};
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@digicreon/mujs",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight AJAX navigation library",
5
+ "main": "dist/mu.min.js",
6
+ "files": [
7
+ "dist/mu.min.js"
8
+ ],
9
+ "keywords": [
10
+ "ajax",
11
+ "navigation",
12
+ "pjax",
13
+ "turbo",
14
+ "htmx",
15
+ "spa",
16
+ "lightweight"
17
+ ],
18
+ "author": "Digicreon",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/Digicreon/muJS"
23
+ },
24
+ "homepage": "https://mujs.org"
25
+ }