@very-simple/ajax 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -10,10 +10,33 @@ type MergeStrategy = 'replace' | 'morph' | 'before' | 'after' | 'prepend' | 'app
10
10
  interface Config {
11
11
  watchHistory?: boolean;
12
12
  merge?: MergeStrategy;
13
+ morphHeads?: boolean;
14
+ }
15
+ type EventMap = {
16
+ visit: {
17
+ url: string;
18
+ prevUrl: string;
19
+ };
20
+ 'before-visit': {
21
+ url: string;
22
+ prevUrl?: string;
23
+ };
24
+ 'before-render': {
25
+ url: string;
26
+ prevUrl?: string;
27
+ newDocument: Document;
28
+ };
29
+ };
30
+ type DomEventMap = {
31
+ [K in keyof EventMap as `simple-ajax:${K & string}`]: CustomEvent<EventMap[K]>;
32
+ };
33
+ declare global {
34
+ interface DocumentEventMap extends DomEventMap {
35
+ }
13
36
  }
14
37
 
15
38
  declare const _default: {
16
- start: ({ watchHistory, merge, }?: Config) => void;
39
+ start: ({ watchHistory, merge, morphHeads, }?: Config) => void;
17
40
  visit: (url: string, { action, emitEvents, isBackForward, morphHeads, merge: mergeStrategy, autoFocus, }?: VisitOptions) => Promise<void>;
18
41
  };
19
42
 
package/dist/index.js CHANGED
@@ -1,17 +1,17 @@
1
1
  // src/config.ts
2
2
  var globalConfig = {
3
3
  watchHistory: true,
4
+ morphHeads: true,
4
5
  merge: "replace"
5
6
  };
6
7
 
7
8
  // src/load.ts
8
9
  var parser = new DOMParser();
9
- var progress = 0;
10
10
  var currentLoadController;
11
11
  var load = async (url, regionIds) => {
12
12
  let progressDelayTimeout;
13
13
  try {
14
- currentLoadController?.abort();
14
+ currentLoadController?.abort("cancel");
15
15
  currentLoadController = new AbortController();
16
16
  setProgress(0);
17
17
  progressDelayTimeout = window.setTimeout(() => toggleLoading(true), 500);
@@ -23,18 +23,27 @@ var load = async (url, regionIds) => {
23
23
  signal: currentLoadController.signal
24
24
  });
25
25
  const reader = response.body?.getReader();
26
- const length = parseInt(response.headers.get("Content-Length") ?? "0");
26
+ const contentLengthHeader = response.headers.get("Content-Length");
27
+ const length = parseInt(contentLengthHeader ?? "0");
27
28
  let receivedBytes = 0;
28
29
  const stream = new ReadableStream({
29
30
  start(controller) {
30
31
  const read = async () => {
31
32
  const { done, value } = await reader.read();
32
33
  if (done) {
34
+ if (contentLengthHeader && receivedBytes > length) {
35
+ console.warn(
36
+ `Server sent more data (${receivedBytes} bytes) than specified in Content-Length header (${length} bytes).`
37
+ );
38
+ }
33
39
  controller.close();
34
40
  return;
35
41
  }
36
42
  receivedBytes += value.length;
37
- setProgress(receivedBytes / length);
43
+ if (contentLengthHeader) {
44
+ const progress = Math.min(receivedBytes / length, 1);
45
+ setProgress(progress);
46
+ }
38
47
  controller.enqueue(value);
39
48
  read();
40
49
  };
@@ -47,8 +56,7 @@ var load = async (url, regionIds) => {
47
56
  return parser.parseFromString(html, "text/html");
48
57
  } catch (e) {
49
58
  setProgress(0);
50
- const isAbortError = e instanceof DOMException && e.name === "AbortError";
51
- if (isAbortError || currentLoadController?.signal.aborted) {
59
+ if (e !== "cancel") {
52
60
  window.location.reload();
53
61
  }
54
62
  } finally {
@@ -56,13 +64,10 @@ var load = async (url, regionIds) => {
56
64
  toggleLoading(false);
57
65
  }
58
66
  };
59
- var setProgress = (value) => {
60
- progress = value;
61
- document.documentElement.style.setProperty(
62
- "--simple-progress",
63
- `${value * 100}%`
64
- );
65
- };
67
+ var setProgress = (value) => document.documentElement.style.setProperty(
68
+ "--simple-progress",
69
+ `${Math.round(value * 1e4) / 100}%`
70
+ );
66
71
  var toggleLoading = (state) => document.documentElement.toggleAttribute("data-simple-loading", state);
67
72
 
68
73
  // src/merge.ts
@@ -100,22 +105,27 @@ var merge = (region, newRegion, strategy) => {
100
105
  var morph = (container, newContainer) => {
101
106
  const manualReplacements = [];
102
107
  let autoFocusEls = [];
108
+ let currentNodeKeepAttributes = [];
103
109
  Idiomorph.morph(container, newContainer, {
104
110
  callbacks: {
105
- afterNodeMorphed(_, newNode) {
106
- if (newNode instanceof HTMLElement && (newNode.autofocus || newNode.dataset.simpleAutofocus)) {
107
- autoFocusEls.push(newNode);
108
- }
109
- },
110
111
  beforeNodeMorphed(oldNode, newNode) {
111
112
  if (!(oldNode instanceof HTMLElement)) return true;
112
113
  if (!(newNode instanceof HTMLElement)) return true;
114
+ currentNodeKeepAttributes = oldNode.dataset.simpleKeepAttributes?.split(" ") ?? [];
113
115
  if (oldNode.hasAttribute("data-simple-permanent") && oldNode.id === newNode.id)
114
116
  return false;
115
117
  const oldComponent = oldNode.dataset.simpleComponent;
116
118
  const newComponent = newNode.dataset.simpleComponent;
117
119
  if (!oldComponent || oldComponent === newComponent) return true;
118
120
  manualReplacements.push([oldNode, newNode]);
121
+ },
122
+ beforeAttributeUpdated: (name) => {
123
+ return !currentNodeKeepAttributes.includes(name);
124
+ },
125
+ afterNodeMorphed(_, newNode) {
126
+ if (newNode instanceof HTMLElement && (newNode.autofocus || newNode.dataset.simpleAutofocus)) {
127
+ autoFocusEls.push(newNode);
128
+ }
119
129
  }
120
130
  }
121
131
  });
@@ -131,26 +141,31 @@ var emit = async (type, payload) => {
131
141
  const event = new CustomEvent(`simple-ajax:${type}`, { detail: payload });
132
142
  document.dispatchEvent(event);
133
143
  };
144
+ var currentUrl = window.location.pathname;
145
+ var prevUrl;
134
146
  var visit = async (url, {
135
147
  action = "none",
136
148
  emitEvents = true,
137
149
  isBackForward = false,
138
- morphHeads = false,
150
+ morphHeads = globalConfig.morphHeads,
139
151
  merge: mergeStrategy = globalConfig.merge,
140
152
  autoFocus = true
141
153
  } = {}) => {
142
- if (emitEvents) emit("before-visit", { url });
154
+ prevUrl = currentUrl;
155
+ currentUrl = url;
156
+ if (emitEvents) emit("before-visit", { url, prevUrl });
143
157
  let newDocument;
144
158
  if (isBackForward) {
145
159
  newDocument = cache.get(url)?.cloneNode(true);
146
- } else {
147
- const cacheId = window.location.pathname + window.location.search;
148
- cache.set(cacheId, document.cloneNode(true));
149
160
  }
150
161
  const regions = findRegions(document);
151
162
  newDocument ??= await load(url, Array.from(regions.keys()));
152
163
  if (!newDocument) return;
153
- if (emitEvents) emit("before-render", { url, newDocument });
164
+ if (emitEvents) emit("before-render", { url, prevUrl, newDocument });
165
+ if (!isBackForward) {
166
+ const cacheId = window.location.pathname + window.location.search;
167
+ cache.set(cacheId, document.cloneNode(true));
168
+ }
154
169
  if (morphHeads) Idiomorph2.morph(document.head, newDocument.head);
155
170
  const getMergeStrategy = (oldEl, newEl) => newEl.dataset.simpleAjax || oldEl.dataset.simpleAjax || mergeStrategy;
156
171
  let autoFocusEl;
@@ -177,7 +192,7 @@ var visit = async (url, {
177
192
  if (autoFocus) autoFocusEl?.focus();
178
193
  if (action === "replace") history.replaceState(null, "", url);
179
194
  else if (action === "push") history.pushState(null, "", url);
180
- if (emitEvents) emit("visit", { url });
195
+ if (emitEvents) emit("visit", { url, prevUrl });
181
196
  };
182
197
  var findRegions = (doc) => {
183
198
  let regions = /* @__PURE__ */ new Map();
@@ -191,13 +206,15 @@ var findRegions = (doc) => {
191
206
  // src/start.ts
192
207
  var start = ({
193
208
  watchHistory = true,
194
- merge: merge2 = "replace"
209
+ merge: merge2 = "replace",
210
+ morphHeads = true
195
211
  } = {}) => {
196
212
  globalConfig.merge = merge2;
197
213
  globalConfig.watchHistory = watchHistory;
214
+ globalConfig.morphHeads = morphHeads;
198
215
  if (watchHistory) {
199
- window.addEventListener("popstate", async () => {
200
- await visit(window.location.pathname + window.location.search, {
216
+ window.addEventListener("popstate", () => {
217
+ visit(window.location.pathname + window.location.search, {
201
218
  action: "none",
202
219
  isBackForward: true
203
220
  });
@@ -206,7 +223,7 @@ var start = ({
206
223
  };
207
224
 
208
225
  // src/index.ts
209
- var src_default = { start, visit };
226
+ var index_default = { start, visit };
210
227
  export {
211
- src_default as default
228
+ index_default as default
212
229
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@very-simple/ajax",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "A very simple turbolinks inspired library",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "url": "https://github.com/very-simple/ajax.git"
14
14
  },
15
15
  "scripts": {
16
- "build": "rm -rf ./dist && tsup src/index.ts --dts --format esm",
16
+ "build": "tsup src/index.ts --dts --format esm",
17
17
  "dev": "vite",
18
18
  "test": "playwright test",
19
19
  "release": "npm run build && npm run test && bumpp"
@@ -23,14 +23,14 @@
23
23
  "devDependencies": {
24
24
  "@picocss/pico": "^2.0.6",
25
25
  "@playwright/test": "^1.48.2",
26
- "@types/node": "^18.19.64",
26
+ "@types/node": "^22.0",
27
27
  "@very-simple/components": "^0.6.0",
28
- "bumpp": "^9.8.1",
28
+ "bumpp": "^10.0.0",
29
29
  "tsup": "8",
30
30
  "typescript": "^5",
31
- "vite": "^5"
31
+ "vite": "^6"
32
32
  },
33
33
  "dependencies": {
34
- "idiomorph": "^0.3.0"
34
+ "idiomorph": "^0.7.0"
35
35
  }
36
36
  }