@studiometa/ui 1.5.0 → 1.6.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/Fetch/Fetch.d.ts +5 -2
- package/Fetch/Fetch.js +33 -8
- package/Fetch/Fetch.js.map +2 -2
- package/package.json +2 -2
package/Fetch/Fetch.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Base, type BaseConfig, type BaseProps } from '@studiometa/js-toolkit';
|
|
1
|
+
import { Base, type BaseConfig, type BaseProps, BaseInterface } from '@studiometa/js-toolkit';
|
|
2
2
|
export interface FetchProps extends BaseProps {
|
|
3
3
|
$el: HTMLAnchorElement | HTMLFormElement;
|
|
4
4
|
$refs: {
|
|
@@ -10,6 +10,8 @@ export interface FetchProps extends BaseProps {
|
|
|
10
10
|
headers: Record<string, string>;
|
|
11
11
|
mode: 'replace' | 'prepend' | 'append' | 'morph';
|
|
12
12
|
selector: string;
|
|
13
|
+
response: string;
|
|
14
|
+
viewTransition: boolean;
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
export type FetchConstructor<T extends Fetch = Fetch> = {
|
|
@@ -20,7 +22,7 @@ export type FetchConstructor<T extends Fetch = Fetch> = {
|
|
|
20
22
|
* Fetch class.
|
|
21
23
|
* @link https://ui.studiometa.dev/-/components/Fetch/
|
|
22
24
|
*/
|
|
23
|
-
export declare class Fetch<T extends BaseProps = BaseProps> extends Base<T & FetchProps> {
|
|
25
|
+
export declare class Fetch<T extends BaseProps = BaseProps> extends Base<T & FetchProps> implements BaseInterface {
|
|
24
26
|
/**
|
|
25
27
|
* Declare the `this.constructor` type
|
|
26
28
|
* @link https://github.com/microsoft/TypeScript/issues/3841#issuecomment-2381594311
|
|
@@ -32,6 +34,7 @@ export declare class Fetch<T extends BaseProps = BaseProps> extends Base<T & Fet
|
|
|
32
34
|
static FETCH_EVENTS: {
|
|
33
35
|
readonly BEFORE_FETCH: "fetch-before";
|
|
34
36
|
readonly FETCH: "fetch-fetch";
|
|
37
|
+
readonly RESPONSE: "fetch-response";
|
|
35
38
|
readonly AFTER_FETCH: "fetch-after";
|
|
36
39
|
readonly BEFORE_UPDATE: "fetch-update-before";
|
|
37
40
|
readonly UPDATE: "fetch-update";
|
package/Fetch/Fetch.js
CHANGED
|
@@ -9,6 +9,7 @@ class Fetch extends Base {
|
|
|
9
9
|
static FETCH_EVENTS = {
|
|
10
10
|
BEFORE_FETCH: "fetch-before",
|
|
11
11
|
FETCH: "fetch-fetch",
|
|
12
|
+
RESPONSE: "fetch-response",
|
|
12
13
|
AFTER_FETCH: "fetch-after",
|
|
13
14
|
BEFORE_UPDATE: "fetch-update-before",
|
|
14
15
|
UPDATE: "fetch-update",
|
|
@@ -43,6 +44,14 @@ class Fetch extends Base {
|
|
|
43
44
|
selector: {
|
|
44
45
|
type: String,
|
|
45
46
|
default: "[id]"
|
|
47
|
+
},
|
|
48
|
+
response: {
|
|
49
|
+
type: String,
|
|
50
|
+
default: "response.text()"
|
|
51
|
+
},
|
|
52
|
+
viewTransition: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: true
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
};
|
|
@@ -115,8 +124,11 @@ class Fetch extends Base {
|
|
|
115
124
|
}
|
|
116
125
|
if (isForm) {
|
|
117
126
|
const form = $el;
|
|
118
|
-
|
|
119
|
-
normalizedRequestInit.
|
|
127
|
+
const method = form.method.toLowerCase();
|
|
128
|
+
normalizedRequestInit.method = method;
|
|
129
|
+
if (method === "post") {
|
|
130
|
+
normalizedRequestInit.body = new FormData(form);
|
|
131
|
+
}
|
|
120
132
|
}
|
|
121
133
|
return normalizedRequestInit;
|
|
122
134
|
}
|
|
@@ -181,7 +193,12 @@ class Fetch extends Base {
|
|
|
181
193
|
this.__abortController.abort();
|
|
182
194
|
const newController = new AbortController();
|
|
183
195
|
newController.signal.addEventListener("abort", () => {
|
|
184
|
-
this.$emit(FETCH_EVENTS.ABORT, {
|
|
196
|
+
this.$emit(FETCH_EVENTS.ABORT, {
|
|
197
|
+
instance: this,
|
|
198
|
+
url,
|
|
199
|
+
requestInit,
|
|
200
|
+
reason: newController.signal.reason
|
|
201
|
+
});
|
|
185
202
|
});
|
|
186
203
|
this.__abortController = newController;
|
|
187
204
|
const init = {
|
|
@@ -197,14 +214,22 @@ class Fetch extends Base {
|
|
|
197
214
|
this.$emit(FETCH_EVENTS.FETCH, { instance: this, url, requestInit: init });
|
|
198
215
|
try {
|
|
199
216
|
const response = await this.client(url, init);
|
|
217
|
+
this.$emit(FETCH_EVENTS.RESPONSE, { instance: this, url, requestInit: init, response });
|
|
200
218
|
if (!response.ok) {
|
|
201
219
|
throw new Error(`Fetch failed with status ${response.status}`);
|
|
202
220
|
}
|
|
203
|
-
const
|
|
204
|
-
|
|
221
|
+
const fn = new Function(
|
|
222
|
+
"response",
|
|
223
|
+
"url",
|
|
224
|
+
"requestInit",
|
|
225
|
+
"self",
|
|
226
|
+
`return ${this.$options.response}`
|
|
227
|
+
);
|
|
228
|
+
const content = await fn.call(this, response, url, requestInit, self);
|
|
229
|
+
this.$emit(FETCH_EVENTS.AFTER_FETCH, { instance: this, url, requestInit: init, content });
|
|
205
230
|
this.update(url, init, content);
|
|
206
231
|
} catch (error) {
|
|
207
|
-
this.$emit(FETCH_EVENTS.AFTER_FETCH, { instance: this, url, requestInit, error });
|
|
232
|
+
this.$emit(FETCH_EVENTS.AFTER_FETCH, { instance: this, url, requestInit: init, error });
|
|
208
233
|
this.error(url, init, error);
|
|
209
234
|
}
|
|
210
235
|
}
|
|
@@ -247,7 +272,7 @@ class Fetch extends Base {
|
|
|
247
272
|
*/
|
|
248
273
|
async update(url, requestInit, content) {
|
|
249
274
|
const { FETCH_EVENTS } = this.constructor;
|
|
250
|
-
const { history } = this.$options;
|
|
275
|
+
const { history, viewTransition } = this.$options;
|
|
251
276
|
this.$log("content", url, content);
|
|
252
277
|
this.$emit(FETCH_EVENTS.BEFORE_UPDATE, { instance: this, url, requestInit, content });
|
|
253
278
|
const fragment = this.__domParser.parseFromString(content, "text/html");
|
|
@@ -262,7 +287,7 @@ class Fetch extends Base {
|
|
|
262
287
|
});
|
|
263
288
|
}
|
|
264
289
|
this.$emit(FETCH_EVENTS.UPDATE, { instance: this, url, requestInit, fragment });
|
|
265
|
-
if (isFunction(document.startViewTransition)) {
|
|
290
|
+
if (viewTransition && isFunction(document.startViewTransition)) {
|
|
266
291
|
await document.startViewTransition(() => {
|
|
267
292
|
this.__updateDOM(fragment);
|
|
268
293
|
}).ready;
|
package/Fetch/Fetch.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../packages/ui/Fetch/Fetch.ts"],
|
|
4
|
-
"sourcesContent": ["import { Base, type BaseConfig, type BaseProps } from '@studiometa/js-toolkit';\nimport { domScheduler, historyPush, isFunction } from '@studiometa/js-toolkit/utils';\nimport morphdom from 'morphdom';\nimport { adoptNewScripts, getScripts } from './utils.js';\n\nexport interface FetchProps extends BaseProps {\n $el: HTMLAnchorElement | HTMLFormElement;\n $refs: {\n headers: HTMLInputElement[];\n };\n $options: {\n history: boolean;\n requestInit: RequestInit;\n headers: Record<string, string>;\n mode: 'replace' | 'prepend' | 'append' | 'morph';\n selector: string;\n };\n}\n\nexport type FetchConstructor<T extends Fetch = Fetch> = {\n new (...args: any[]): T;\n prototype: Fetch;\n} & Pick<typeof Fetch, keyof typeof Fetch>;\n\n/**\n * Fetch class.\n * @link https://ui.studiometa.dev/-/components/Fetch/\n */\nexport class Fetch<T extends BaseProps = BaseProps> extends Base<T & FetchProps> {\n /**\n * Declare the `this.constructor` type\n * @link https://github.com/microsoft/TypeScript/issues/3841#issuecomment-2381594311\n */\n declare ['constructor']: FetchConstructor;\n\n /**\n * Fetch events enum.\n */\n static FETCH_EVENTS = {\n BEFORE_FETCH: 'fetch-before',\n FETCH: 'fetch-fetch',\n AFTER_FETCH: 'fetch-after',\n BEFORE_UPDATE: 'fetch-update-before',\n UPDATE: 'fetch-update',\n AFTER_UPDATE: 'fetch-update-after',\n ERROR: 'fetch-error',\n ABORT: 'fetch-abort',\n } as const;\n\n /**\n * Fetch modes enum.\n */\n static FETCH_MODES = {\n REPLACE: 'replace',\n PREPEND: 'prepend',\n APPEND: 'append',\n MORPH: 'morph',\n } as const;\n\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'Fetch',\n emits: Object.values(this.FETCH_EVENTS),\n refs: ['headers[]'],\n options: {\n history: Boolean,\n mode: {\n type: String,\n default: this.FETCH_MODES.REPLACE,\n },\n requestInit: Object,\n headers: Object,\n selector: {\n type: String,\n default: '[id]',\n },\n },\n };\n\n /**\n * Header names used by the requestInit property.\n * @internal\n */\n __headerNames = {\n ACCEPT: 'accept',\n X_REQUESTED_BY: 'x-requested-by',\n X_TRIGGERED_BY: 'x-triggered-by',\n USER_AGENT: 'user-agent',\n } as const;\n\n /**\n * DOM Parser to parse the new content to be injected.\n * @internal\n */\n __domParser = new DOMParser();\n\n /**\n * Abort controller to prevent multiple simultaneous fetches.\n * @internal\n */\n __abortController = new AbortController();\n\n /**\n * Client.\n * @internal\n */\n __client: typeof fetch;\n\n /**\n * The client used for the fetch request.\n */\n get client(): typeof fetch {\n return (this.__client ??= window.fetch.bind(window));\n }\n\n /**\n * The URL to use for the request.\n */\n get url(): URL {\n const { $el, isForm } = this;\n\n if (isForm) {\n const { action, method } = this.$el as HTMLFormElement;\n const url = new URL(action);\n\n if (method.toLowerCase() === 'get') {\n // @ts-expect-error URLSearchParams accepts FormData as parameter in the browser.\n url.search = new URLSearchParams(new FormData($el)).toString();\n }\n\n return url;\n }\n\n return new URL($el.href);\n }\n\n /**\n * Option for the fetch request.\n */\n get requestInit(): RequestInit {\n const { __headerNames: headerNames, isForm, $el, $options, $refs } = this;\n const { requestInit, headers } = $options;\n const { headers: headerRefs } = $refs;\n const requestedBy = '@studiometa/ui/Fetch';\n\n const normalizedRequestInit = {\n ...requestInit,\n headers: {\n [headerNames.USER_AGENT]: `${navigator.userAgent} ${requestedBy}`,\n ...requestInit.headers,\n ...headers,\n },\n };\n\n for (const header of headerRefs) {\n if (header.dataset.name && header.value) {\n normalizedRequestInit.headers[header.dataset.name] = header.value;\n }\n }\n\n if (isForm) {\n const form = $el as HTMLFormElement;\n normalizedRequestInit.method = form.method;\n normalizedRequestInit.body = new FormData(form);\n }\n\n return normalizedRequestInit;\n }\n\n /**\n * Is the root element a link?\n */\n get isLink() {\n return this.$el instanceof HTMLAnchorElement;\n }\n\n /**\n * Is the root element a form?\n */\n get isForm() {\n return this.$el instanceof HTMLFormElement;\n }\n\n /**\n * Emit bubbling events.\n * @inheritdoc\n */\n $emit(event: string, ...args: unknown[]) {\n const e = new CustomEvent(event, { detail: args, bubbles: true });\n return super.$emit(e, ...args);\n }\n\n /**\n * If root element is a link, prevent its default behavior and fetch its URL.\n */\n onClick({ event }: { event: MouseEvent }) {\n if (!this.isLink) return;\n\n if (\n !event.ctrlKey &&\n !event.shiftKey &&\n !event.altKey &&\n !event.metaKey &&\n event.button === 0 &&\n this.$el.target !== '_blank'\n ) {\n event.preventDefault();\n this.fetch(this.url, this.requestInit);\n }\n }\n\n /**\n * If root element is a form, prevent its default behavior on submit and fetch its action\n * following the `method` attribute and with the form's data.\n */\n onSubmit({ event }: { event: SubmitEvent }) {\n if (!this.isForm) return;\n\n if (this.$el.target !== '_blank') {\n event.preventDefault();\n this.fetch(this.url, this.requestInit);\n }\n }\n\n /**\n * Update content on history back/forward navigation.\n */\n onWindowPopstate() {\n if (!this.$options.history) return;\n\n this.fetch(new URL(window.location.href), {\n headers: {\n [this.__headerNames.X_TRIGGERED_BY]: 'popstate',\n },\n });\n }\n\n /**\n * Fetch given url.\n */\n async fetch(url: URL, requestInit: RequestInit = {}) {\n const { FETCH_EVENTS } = this.constructor;\n this.$emit(FETCH_EVENTS.BEFORE_FETCH, { instance: this, url, requestInit });\n\n this.__abortController.abort();\n const newController = new AbortController();\n newController.signal.addEventListener('abort', () => {\n this.$emit(FETCH_EVENTS.ABORT, { instance: this, url, requestInit, reason: newController.signal.reason })\n });\n this.__abortController = newController;\n const init = {\n ...this.requestInit,\n ...requestInit,\n headers: {\n ...this.requestInit.headers,\n ...requestInit.headers,\n },\n signal: newController.signal,\n };\n\n this.$log('fetch', url, init);\n this.$emit(FETCH_EVENTS.FETCH, { instance: this, url, requestInit: init });\n\n try {\n const response = await this.client(url, init);\n\n if (!response.ok) {\n throw new Error(`Fetch failed with status ${response.status}`);\n }\n\n const content = await response.text();\n this.$emit(FETCH_EVENTS.AFTER_FETCH, { instance: this, url, requestInit, content });\n this.update(url, init, content);\n } catch (error) {\n this.$emit(FETCH_EVENTS.AFTER_FETCH, { instance: this, url, requestInit, error });\n this.error(url, init, error);\n }\n }\n\n /**\n * Update the DOM with new content from the fetched HTML.\n * @internal\n */\n __updateDOM(fragment: Document) {\n const { FETCH_MODES } = this.constructor;\n const { mode, selector } = this.$options;\n\n // @ts-expect-error querySelectorAll is iterable in the browser\n for (const newElement of fragment.querySelectorAll<HTMLElement>(selector)) {\n const oldElement = newElement.id && document.getElementById(newElement.id);\n\n if (!oldElement || oldElement === newElement) {\n continue;\n }\n\n const oldScripts = getScripts(oldElement);\n\n switch (mode) {\n case FETCH_MODES.APPEND:\n oldElement.append(...newElement.childNodes);\n adoptNewScripts(getScripts(oldElement), oldScripts);\n break;\n case FETCH_MODES.PREPEND:\n oldElement.prepend(...newElement.childNodes);\n adoptNewScripts(getScripts(oldElement), oldScripts);\n break;\n case FETCH_MODES.MORPH:\n morphdom(oldElement, newElement);\n adoptNewScripts(getScripts(oldElement), oldScripts);\n break;\n case FETCH_MODES.REPLACE:\n default:\n oldElement.replaceWith(newElement);\n adoptNewScripts(getScripts(newElement), oldScripts);\n break;\n }\n }\n }\n\n /**\n * Dispatch the contents to update to their matching FrameTarget.\n */\n async update(url: URL, requestInit: RequestInit, content: string) {\n const { FETCH_EVENTS } = this.constructor;\n const { history } = this.$options;\n\n this.$log('content', url, content);\n this.$emit(FETCH_EVENTS.BEFORE_UPDATE, { instance: this, url, requestInit, content });\n\n const fragment = this.__domParser.parseFromString(content, 'text/html');\n\n if (history) {\n if (requestInit?.headers?.[this.__headerNames.X_TRIGGERED_BY] !== 'popstate') {\n historyPush({ path: url.pathname, search: url.searchParams });\n }\n domScheduler.write(() => {\n if (fragment.title) {\n document.title = fragment.title;\n }\n });\n }\n\n this.$emit(FETCH_EVENTS.UPDATE, { instance: this, url, requestInit, fragment });\n\n if (isFunction(document.startViewTransition)) {\n await document.startViewTransition(() => {\n this.__updateDOM(fragment);\n }).ready;\n } else {\n this.__updateDOM(fragment);\n }\n\n this.$emit(FETCH_EVENTS.AFTER_UPDATE, { instance: this, url, requestInit, fragment });\n }\n\n /**\n * Handle errors.\n */\n error(url: URL, requestInit: RequestInit, error: Error) {\n if (error.name === 'AbortError') return;\n\n this.$log('error', url, requestInit, error);\n this.$emit(this.constructor.FETCH_EVENTS.ERROR, { instance: this, url, requestInit, error });\n }\n\n /**\n * Abort the current request.\n */\n abort(reason?: any) {\n this.__abortController.abort(reason);\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { Base, type BaseConfig, type BaseProps, BaseInterface } from '@studiometa/js-toolkit';\nimport { domScheduler, historyPush, isFunction } from '@studiometa/js-toolkit/utils';\nimport morphdom from 'morphdom';\nimport { adoptNewScripts, getScripts } from './utils.js';\n\nexport interface FetchProps extends BaseProps {\n $el: HTMLAnchorElement | HTMLFormElement;\n $refs: {\n headers: HTMLInputElement[];\n };\n $options: {\n history: boolean;\n requestInit: RequestInit;\n headers: Record<string, string>;\n mode: 'replace' | 'prepend' | 'append' | 'morph';\n selector: string;\n response: string;\n viewTransition: boolean;\n };\n}\n\nexport type FetchConstructor<T extends Fetch = Fetch> = {\n new (...args: any[]): T;\n prototype: Fetch;\n} & Pick<typeof Fetch, keyof typeof Fetch>;\n\n/**\n * Fetch class.\n * @link https://ui.studiometa.dev/-/components/Fetch/\n */\nexport class Fetch<T extends BaseProps = BaseProps>\n extends Base<T & FetchProps>\n implements BaseInterface\n{\n /**\n * Declare the `this.constructor` type\n * @link https://github.com/microsoft/TypeScript/issues/3841#issuecomment-2381594311\n */\n declare ['constructor']: FetchConstructor;\n\n /**\n * Fetch events enum.\n */\n static FETCH_EVENTS = {\n BEFORE_FETCH: 'fetch-before',\n FETCH: 'fetch-fetch',\n RESPONSE: 'fetch-response',\n AFTER_FETCH: 'fetch-after',\n BEFORE_UPDATE: 'fetch-update-before',\n UPDATE: 'fetch-update',\n AFTER_UPDATE: 'fetch-update-after',\n ERROR: 'fetch-error',\n ABORT: 'fetch-abort',\n } as const;\n\n /**\n * Fetch modes enum.\n */\n static FETCH_MODES = {\n REPLACE: 'replace',\n PREPEND: 'prepend',\n APPEND: 'append',\n MORPH: 'morph',\n } as const;\n\n /**\n * Config.\n */\n static config: BaseConfig = {\n name: 'Fetch',\n emits: Object.values(this.FETCH_EVENTS),\n refs: ['headers[]'],\n options: {\n history: Boolean,\n mode: {\n type: String,\n default: this.FETCH_MODES.REPLACE,\n },\n requestInit: Object,\n headers: Object,\n selector: {\n type: String,\n default: '[id]',\n },\n response: {\n type: String,\n default: 'response.text()',\n },\n viewTransition: {\n type: Boolean,\n default: true,\n },\n },\n };\n\n /**\n * Header names used by the requestInit property.\n * @internal\n */\n __headerNames = {\n ACCEPT: 'accept',\n X_REQUESTED_BY: 'x-requested-by',\n X_TRIGGERED_BY: 'x-triggered-by',\n USER_AGENT: 'user-agent',\n } as const;\n\n /**\n * DOM Parser to parse the new content to be injected.\n * @internal\n */\n __domParser = new DOMParser();\n\n /**\n * Abort controller to prevent multiple simultaneous fetches.\n * @internal\n */\n __abortController = new AbortController();\n\n /**\n * Client.\n * @internal\n */\n __client: typeof fetch;\n\n /**\n * The client used for the fetch request.\n */\n get client(): typeof fetch {\n return (this.__client ??= window.fetch.bind(window));\n }\n\n /**\n * The URL to use for the request.\n */\n get url(): URL {\n const { $el, isForm } = this;\n\n if (isForm) {\n const { action, method } = this.$el as HTMLFormElement;\n const url = new URL(action);\n\n if (method.toLowerCase() === 'get') {\n // @ts-expect-error URLSearchParams accepts FormData as parameter in the browser.\n url.search = new URLSearchParams(new FormData($el)).toString();\n }\n\n return url;\n }\n\n return new URL($el.href);\n }\n\n /**\n * Option for the fetch request.\n */\n get requestInit(): RequestInit {\n const { __headerNames: headerNames, isForm, $el, $options, $refs } = this;\n const { requestInit, headers } = $options;\n const { headers: headerRefs } = $refs;\n const requestedBy = '@studiometa/ui/Fetch';\n\n const normalizedRequestInit = {\n ...requestInit,\n headers: {\n [headerNames.USER_AGENT]: `${navigator.userAgent} ${requestedBy}`,\n ...requestInit.headers,\n ...headers,\n },\n };\n\n for (const header of headerRefs) {\n if (header.dataset.name && header.value) {\n normalizedRequestInit.headers[header.dataset.name] = header.value;\n }\n }\n\n if (isForm) {\n const form = $el as HTMLFormElement;\n const method = form.method.toLowerCase();\n normalizedRequestInit.method = method;\n if (method === 'post') {\n normalizedRequestInit.body = new FormData(form);\n }\n }\n\n return normalizedRequestInit;\n }\n\n /**\n * Is the root element a link?\n */\n get isLink() {\n return this.$el instanceof HTMLAnchorElement;\n }\n\n /**\n * Is the root element a form?\n */\n get isForm() {\n return this.$el instanceof HTMLFormElement;\n }\n\n /**\n * Emit bubbling events.\n * @inheritdoc\n */\n $emit(event: string, ...args: unknown[]) {\n const e = new CustomEvent(event, { detail: args, bubbles: true });\n return super.$emit(e, ...args);\n }\n\n /**\n * If root element is a link, prevent its default behavior and fetch its URL.\n */\n onClick({ event }: { event: MouseEvent }) {\n if (!this.isLink) return;\n\n if (\n !event.ctrlKey &&\n !event.shiftKey &&\n !event.altKey &&\n !event.metaKey &&\n event.button === 0 &&\n this.$el.target !== '_blank'\n ) {\n event.preventDefault();\n this.fetch(this.url, this.requestInit);\n }\n }\n\n /**\n * If root element is a form, prevent its default behavior on submit and fetch its action\n * following the `method` attribute and with the form's data.\n */\n onSubmit({ event }: { event: SubmitEvent }) {\n if (!this.isForm) return;\n\n if (this.$el.target !== '_blank') {\n event.preventDefault();\n this.fetch(this.url, this.requestInit);\n }\n }\n\n /**\n * Update content on history back/forward navigation.\n */\n onWindowPopstate() {\n if (!this.$options.history) return;\n\n this.fetch(new URL(window.location.href), {\n headers: {\n [this.__headerNames.X_TRIGGERED_BY]: 'popstate',\n },\n });\n }\n\n /**\n * Fetch given url.\n */\n async fetch(url: URL, requestInit: RequestInit = {}) {\n const { FETCH_EVENTS } = this.constructor;\n this.$emit(FETCH_EVENTS.BEFORE_FETCH, { instance: this, url, requestInit });\n\n this.__abortController.abort();\n const newController = new AbortController();\n newController.signal.addEventListener('abort', () => {\n this.$emit(FETCH_EVENTS.ABORT, {\n instance: this,\n url,\n requestInit,\n reason: newController.signal.reason,\n });\n });\n this.__abortController = newController;\n const init = {\n ...this.requestInit,\n ...requestInit,\n headers: {\n ...this.requestInit.headers,\n ...requestInit.headers,\n },\n signal: newController.signal,\n };\n\n this.$log('fetch', url, init);\n this.$emit(FETCH_EVENTS.FETCH, { instance: this, url, requestInit: init });\n\n try {\n const response = await this.client(url, init);\n this.$emit(FETCH_EVENTS.RESPONSE, { instance: this, url, requestInit: init, response });\n\n if (!response.ok) {\n throw new Error(`Fetch failed with status ${response.status}`);\n }\n\n const fn = new Function(\n 'response',\n 'url',\n 'requestInit',\n 'self',\n `return ${this.$options.response}`,\n );\n const content = await fn.call(this, response, url, requestInit, self);\n this.$emit(FETCH_EVENTS.AFTER_FETCH, { instance: this, url, requestInit: init, content });\n this.update(url, init, content);\n } catch (error) {\n this.$emit(FETCH_EVENTS.AFTER_FETCH, { instance: this, url, requestInit: init, error });\n this.error(url, init, error);\n }\n }\n\n /**\n * Update the DOM with new content from the fetched HTML.\n * @internal\n */\n __updateDOM(fragment: Document) {\n const { FETCH_MODES } = this.constructor;\n const { mode, selector } = this.$options;\n\n // @ts-expect-error querySelectorAll is iterable in the browser\n for (const newElement of fragment.querySelectorAll<HTMLElement>(selector)) {\n const oldElement = newElement.id && document.getElementById(newElement.id);\n\n if (!oldElement || oldElement === newElement) {\n continue;\n }\n\n const oldScripts = getScripts(oldElement);\n\n switch (mode) {\n case FETCH_MODES.APPEND:\n oldElement.append(...newElement.childNodes);\n adoptNewScripts(getScripts(oldElement), oldScripts);\n break;\n case FETCH_MODES.PREPEND:\n oldElement.prepend(...newElement.childNodes);\n adoptNewScripts(getScripts(oldElement), oldScripts);\n break;\n case FETCH_MODES.MORPH:\n morphdom(oldElement, newElement);\n adoptNewScripts(getScripts(oldElement), oldScripts);\n break;\n case FETCH_MODES.REPLACE:\n default:\n oldElement.replaceWith(newElement);\n adoptNewScripts(getScripts(newElement), oldScripts);\n break;\n }\n }\n }\n\n /**\n * Dispatch the contents to update to their matching FrameTarget.\n */\n async update(url: URL, requestInit: RequestInit, content: string) {\n const { FETCH_EVENTS } = this.constructor;\n const { history, viewTransition } = this.$options;\n\n this.$log('content', url, content);\n this.$emit(FETCH_EVENTS.BEFORE_UPDATE, { instance: this, url, requestInit, content });\n\n const fragment = this.__domParser.parseFromString(content, 'text/html');\n\n if (history) {\n if (requestInit?.headers?.[this.__headerNames.X_TRIGGERED_BY] !== 'popstate') {\n historyPush({ path: url.pathname, search: url.searchParams });\n }\n domScheduler.write(() => {\n if (fragment.title) {\n document.title = fragment.title;\n }\n });\n }\n\n this.$emit(FETCH_EVENTS.UPDATE, { instance: this, url, requestInit, fragment });\n\n if (viewTransition && isFunction(document.startViewTransition)) {\n await document.startViewTransition(() => {\n this.__updateDOM(fragment);\n }).ready;\n } else {\n this.__updateDOM(fragment);\n }\n\n this.$emit(FETCH_EVENTS.AFTER_UPDATE, { instance: this, url, requestInit, fragment });\n }\n\n /**\n * Handle errors.\n */\n error(url: URL, requestInit: RequestInit, error: Error) {\n if (error.name === 'AbortError') return;\n\n this.$log('error', url, requestInit, error);\n this.$emit(this.constructor.FETCH_EVENTS.ERROR, { instance: this, url, requestInit, error });\n }\n\n /**\n * Abort the current request.\n */\n abort(reason?: any) {\n this.__abortController.abort(reason);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@studiometa/ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "A set of opiniated, unstyled and accessible components",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"morphdom": "^2.7.5"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@studiometa/js-toolkit": "3.4.
|
|
36
|
+
"@studiometa/js-toolkit": "3.4.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@studiometa/js-toolkit": "^3.4.0"
|