nano-skeleton 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - chore: add oxlint
8
+
3
9
  ## 0.5.0
4
10
 
5
11
  ### Minor Changes
package/README.md CHANGED
@@ -103,9 +103,9 @@ all of its skeletons at once.
103
103
  A `<nano-skeleton>` without a provider simply renders its content as usual.
104
104
 
105
105
  `<nano-skeleton>` works in one of two modes, picked automatically from its
106
- content (text mode can also be forced with the `text` attribute).
106
+ content (inline mode can also be forced with the `inline` attribute).
107
107
 
108
- ### Text mode
108
+ ### Inline mode
109
109
 
110
110
  If the skeleton directly contains non-empty text, it renders inline and the text
111
111
  itself becomes the placeholder. Each line of wrapped text gets its own rounded
@@ -136,11 +136,11 @@ placeholder text first and replace it with real data later:
136
136
 
137
137
  Automatic detection only looks at text placed directly inside the skeleton.
138
138
  When the text is wrapped in an inline element, like a link or `<strong>`, add
139
- `text` to force text mode:
139
+ `inline` to force inline mode:
140
140
 
141
141
  ```html
142
142
  <p>
143
- <nano-skeleton text>
143
+ <nano-skeleton inline>
144
144
  <a href="/posts/42">Read the full post</a>
145
145
  </nano-skeleton>
146
146
  </p>
@@ -155,7 +155,7 @@ placeholder still reads as a link. To hide them too, wrap the link's content in
155
155
  a `<span>`:
156
156
 
157
157
  ```html
158
- <nano-skeleton text>
158
+ <nano-skeleton inline>
159
159
  <a href="/posts/42"><span>Read the full post</span></a>
160
160
  </nano-skeleton>
161
161
  ```
@@ -304,8 +304,8 @@ React 19 passes properties to custom elements, so `loading` can be bound
304
304
  directly:
305
305
 
306
306
  ```tsx
307
- import { init } from "nano-skeleton";
308
307
  import { useQuery } from "@tanstack/react-query";
308
+ import { init } from "nano-skeleton";
309
309
 
310
310
  init();
311
311
 
@@ -359,7 +359,7 @@ declare module "react" {
359
359
  Skeleton
360
360
  > & {
361
361
  interactive?: boolean;
362
- text?: boolean;
362
+ inline?: boolean;
363
363
  };
364
364
  [PROVIDER_NAME]: DetailedHTMLProps<
365
365
  HTMLAttributes<SkeletonProvider>,
@@ -390,18 +390,19 @@ While loading, the provider is exposed to assistive technology as
390
390
 
391
391
  ### `<nano-skeleton>`
392
392
 
393
- | Attribute / property | Type | Description |
394
- | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------- |
395
- | `interactive` | `boolean` | Keeps the content focusable and clickable while loading. See [Interactive skeletons](#interactive-skeletons). |
396
- | `text` | `boolean` | Forces text mode, e.g. for text inside a link. See [Text wrapped in elements](#text-wrapped-in-elements). |
393
+ | Attribute / property | Type | Description |
394
+ | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------- |
395
+ | `interactive` | `boolean` | Keeps the content focusable and clickable while loading. See [Interactive skeletons](#interactive-skeletons). |
396
+ | `inline` | `boolean` | Forces inline mode, e.g. for text inside a link. See [Text wrapped in elements](#text-wrapped-in-elements). |
397
397
 
398
- It also exposes these state attributes, which you can use as styling hooks:
398
+ The component also manages these attributes itself, which you can use as
399
+ styling hooks:
399
400
 
400
- | Attribute | Set when |
401
- | -------------- | ------------------------------------------------------------------------------------- |
402
- | `data-loading` | The nearest provider is loading. |
403
- | `data-text` | The skeleton directly contains text, or `text` is set (text mode). |
404
- | `inert` | The nearest provider is loading and `interactive` isn't set. Blocks focus and clicks. |
401
+ | Attribute | Set when |
402
+ | --------- | ------------------------------------------------------------------------------------- |
403
+ | `loading` | The nearest provider is loading. |
404
+ | `inline` | The skeleton directly contains text, or `inline` is set (inline mode). |
405
+ | `inert` | The nearest provider is loading and `interactive` isn't set. Blocks focus and clicks. |
405
406
 
406
407
  ### Exports
407
408
 
@@ -424,7 +425,6 @@ The look is controlled by CSS custom properties:
424
425
  | `--nano-skeleton-background-image` | A light/dark-aware horizontal gradient |
425
426
  | `--nano-skeleton-background-size` | `300%` |
426
427
  | `--nano-skeleton-animation` | `shimmer 2s infinite ease-in-out` |
427
- | `--nano-skeleton-text-border-radius` | `0.375rem` (text mode only) |
428
428
 
429
429
  Each `<nano-skeleton>` inherits these from its ancestors, so set them on a
430
430
  parent element (or on `:root` to change all skeletons at once), not on the
@@ -1,5 +1,4 @@
1
- //#region src/constants.d.ts
2
- export declare const COMPONENT_NAME: "nano-skeleton";
3
- export declare const PROVIDER_NAME: "nano-skeleton-provider";
4
- //#endregion
1
+ declare const COMPONENT_NAME: "nano-skeleton";
2
+ declare const PROVIDER_NAME: "nano-skeleton-provider";
3
+ export { COMPONENT_NAME, PROVIDER_NAME };
5
4
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":["COMPONENT_NAME","PROVIDER_NAME","CSS_CONTENT","SKELETON_CONTEXT"],"sources":["../src/constants.ts"],"sourcesContent":["const isDev = import.meta.env.MODE !== \"production\";\n\nexport const COMPONENT_NAME = \"nano-skeleton\" as const;\n\nexport const PROVIDER_NAME = `${COMPONENT_NAME}-provider` as const;\n\nconst { default: CSS_CONTENT } = isDev\n ? await import(\"./styles.css?inline\")\n : { default: `__CSS_CONTENT__` };\n\nexport { CSS_CONTENT };\n\nexport const SKELETON_CONTEXT = Symbol.for(`${COMPONENT_NAME}-context`);\n"],"mappings":"AAEA,MAAaA,EAAiB,gBAEjBC,EAAgB,GAAGD,EAAe,WAEzC,CAAE,QAASE,GAEb,CAAE,QAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Gb,EAIWC,EAAmB,OAAO,IAAI,GAAGH,EAAe,SAAS,E"}
1
+ {"version":3,"file":"constants.js","names":["COMPONENT_NAME","PROVIDER_NAME","CSS_CONTENT","SKELETON_CONTEXT"],"sources":["../src/constants.ts"],"sourcesContent":["const isDev = import.meta.env.MODE !== \"production\";\n\nconst COMPONENT_NAME = \"nano-skeleton\" as const;\n\nconst PROVIDER_NAME = `${COMPONENT_NAME}-provider` as const;\n\nconst { default: CSS_CONTENT } = isDev\n ? // oxlint-disable-next-line node/no-top-level-await -- this is fine for dev mode\n await import(\"./styles.css?inline\")\n : { default: `__CSS_CONTENT__` };\n\nconst SKELETON_CONTEXT = Symbol.for(`${COMPONENT_NAME}-context`);\n\nexport { COMPONENT_NAME, PROVIDER_NAME, CSS_CONTENT, SKELETON_CONTEXT };\n"],"mappings":"AAEA,MAAMA,EAAiB,gBAEjBC,EAAgB,GAAGD,EAAe,WAElC,CAAE,QAASE,GAGb,CAAE,QAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0Gb,EAEIC,EAAmB,OAAO,IAAI,GAAGH,EAAe,SAAS,E"}
@@ -1,2 +1,2 @@
1
1
  var e=class extends Event{context;callback;subscribe;constructor(e,t,n=!1){super(`context-request`,{bubbles:!0,composed:!0}),this.context=e,this.callback=t,this.subscribe=n}};export{e as ContextRequestEvent};
2
- //# sourceMappingURL=ContextRequestEvent.js.map
2
+ //# sourceMappingURL=context-request-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-request-event.js","names":["ContextRequestEvent","context","callback","subscribe"],"sources":["../src/context-request-event.ts"],"sourcesContent":["import type { LoadingCallback } from \"#root/types.js\";\n\n/**\n * Follows the community Context Protocol (@see\n * https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md)\n */\nexport class ContextRequestEvent extends Event {\n public readonly context: unknown;\n public readonly callback: LoadingCallback;\n public readonly subscribe: boolean;\n\n public constructor(\n context: unknown,\n callback: LoadingCallback,\n subscribe = false,\n ) {\n /** Composed, so skeletons inside shadow roots can reach a provider outside */\n super(\"context-request\", { bubbles: true, composed: true });\n this.context = context;\n this.callback = callback;\n this.subscribe = subscribe;\n }\n}\n"],"mappings":"AAMA,IAAaA,EAAb,cAAyC,KAAM,CAC7C,QACA,SACA,UAEA,YACE,EACA,EACA,EAAY,GACZ,CAEA,MAAM,kBAAmB,CAAE,QAAS,GAAM,SAAU,EAAK,CAAC,EAC1D,KAAK,QAAUC,EACf,KAAK,SAAWC,EAChB,KAAK,UAAYC,CACnB,CACF,E"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,6 @@
1
1
  import { COMPONENT_NAME, PROVIDER_NAME } from "./constants.js";
2
- import { Skeleton } from "./Skeleton.js";
3
- import { SkeletonProvider } from "./SkeletonProvider.js";
4
- //#region src/index.d.ts
5
- export declare function init(): void;
6
- //#endregion
7
- export { COMPONENT_NAME, PROVIDER_NAME, Skeleton, SkeletonProvider };
2
+ import { Skeleton } from "./skeleton.js";
3
+ import { SkeletonProvider } from "./skeleton-provider.js";
4
+ declare function init(): void;
5
+ export { COMPONENT_NAME, PROVIDER_NAME, Skeleton, SkeletonProvider, init };
8
6
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{COMPONENT_NAME as e,PROVIDER_NAME as t}from"./constants.js";import{Skeleton as n}from"./Skeleton.js";import{SkeletonProvider as r}from"./SkeletonProvider.js";function i(){customElements.get(`nano-skeleton-provider`)||customElements.define(t,r),customElements.get(`nano-skeleton`)||customElements.define(e,n)}export{e as COMPONENT_NAME,t as PROVIDER_NAME,n as Skeleton,r as SkeletonProvider,i as init};
1
+ import{COMPONENT_NAME as e,PROVIDER_NAME as t}from"./constants.js";import{SkeletonProvider as n}from"./skeleton-provider.js";import{Skeleton as r}from"./skeleton.js";function i(){customElements.get(`nano-skeleton-provider`)||customElements.define(t,n),customElements.get(`nano-skeleton`)||customElements.define(e,r)}export{e as COMPONENT_NAME,t as PROVIDER_NAME,r as Skeleton,n as SkeletonProvider,i as init};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["init","PROVIDER_NAME","SkeletonProvider","COMPONENT_NAME","Skeleton"],"sources":["../src/index.ts"],"sourcesContent":["import { PROVIDER_NAME, COMPONENT_NAME } from \"#root/constants.js\";\nimport { Skeleton } from \"#root/Skeleton.js\";\nimport { SkeletonProvider } from \"#root/SkeletonProvider.js\";\n\nexport function init() {\n if (!customElements.get(PROVIDER_NAME)) {\n customElements.define(PROVIDER_NAME, SkeletonProvider);\n }\n if (!customElements.get(COMPONENT_NAME)) {\n customElements.define(COMPONENT_NAME, Skeleton);\n }\n}\n\nexport { Skeleton, SkeletonProvider };\nexport { PROVIDER_NAME, COMPONENT_NAME };\n"],"mappings":"A,qKAIA,SAAgBA,GAAO,CAChB,eAAe,IAAA,wBAAiB,GACnC,eAAe,OAAOC,EAAeC,CAAgB,EAElD,eAAe,IAAA,eAAkB,GACpC,eAAe,OAAOC,EAAgBC,CAAQ,CAElD,C"}
1
+ {"version":3,"file":"index.js","names":["init","PROVIDER_NAME","SkeletonProvider","COMPONENT_NAME","Skeleton"],"sources":["../src/index.ts"],"sourcesContent":["import { PROVIDER_NAME, COMPONENT_NAME } from \"#root/constants.js\";\nimport { SkeletonProvider } from \"#root/skeleton-provider.js\";\nimport { Skeleton } from \"#root/skeleton.js\";\n\nfunction init() {\n if (!customElements.get(PROVIDER_NAME)) {\n customElements.define(PROVIDER_NAME, SkeletonProvider);\n }\n if (!customElements.get(COMPONENT_NAME)) {\n customElements.define(COMPONENT_NAME, Skeleton);\n }\n}\n\nexport { PROVIDER_NAME, COMPONENT_NAME } from \"#root/constants.js\";\nexport { Skeleton } from \"#root/skeleton.js\";\nexport { SkeletonProvider } from \"#root/skeleton-provider.js\";\nexport { init };\n"],"mappings":"A,sKAIA,SAASA,GAAO,CACT,eAAe,IAAA,wBAAiB,GACnC,eAAe,OAAOC,EAAeC,CAAgB,EAElD,eAAe,IAAA,eAAkB,GACpC,eAAe,OAAOC,EAAgBC,CAAQ,CAElD,C"}
@@ -1,11 +1,9 @@
1
- //#region src/SkeletonProvider.d.ts
2
1
  export declare class SkeletonProvider extends HTMLElement {
3
2
  #private;
4
- static observedAttributes: string[];
3
+ static observedAttributes: readonly ["loading"];
5
4
  constructor();
6
5
  get loading(): boolean;
7
6
  set loading(value: boolean);
8
7
  attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
9
8
  }
10
- //#endregion
11
- //# sourceMappingURL=SkeletonProvider.d.ts.map
9
+ //# sourceMappingURL=skeleton-provider.d.ts.map
@@ -1,2 +1,2 @@
1
- import{SKELETON_CONTEXT as e}from"./constants.js";var t=class extends HTMLElement{static observedAttributes=[`loading`];#e=/* @__PURE__ */ new Set;#t=this.attachInternals();constructor(){super(),this.addEventListener(`context-request`,t=>{if(t.context!==e||typeof t.callback!=`function`)return;t.stopPropagation();let{callback:n}=t;t.subscribe?(this.#e.add(n),n(this.loading,()=>this.#e.delete(n))):n(this.loading)})}get loading(){return this.hasAttribute(`loading`)}set loading(e){this.toggleAttribute(`loading`,!!e)}attributeChangedCallback(e,t,n){if(t!==n){this.#t.ariaBusy=this.loading?`true`:null;for(let e of this.#e)e(this.loading)}}};export{t as SkeletonProvider};
2
- //# sourceMappingURL=SkeletonProvider.js.map
1
+ import{SKELETON_CONTEXT as e}from"./constants.js";var t=class extends HTMLElement{static observedAttributes=[`loading`];#e=/* @__PURE__ */ new Set;#t=this.attachInternals();constructor(){super(),this.addEventListener(`context-request`,t=>{if(t.context!==e||typeof t.callback!=`function`)return;t.stopPropagation();let{callback:n}=t;t.subscribe?(this.#e.add(n),n(this.loading,()=>{this.#e.delete(n)})):n(this.loading)})}get loading(){return this.hasAttribute(`loading`)}set loading(e){this.toggleAttribute(`loading`,e)}attributeChangedCallback(e,t,n){if(t!==n){this.#t.ariaBusy=this.loading?`true`:null;for(let e of this.#e)e(this.loading)}}};export{t as SkeletonProvider};
2
+ //# sourceMappingURL=skeleton-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeleton-provider.js","names":["SkeletonProvider","event","SKELETON_CONTEXT","subscriber","#subscribers","value","oldValue","newValue","#internals"],"sources":["../src/skeleton-provider.ts"],"sourcesContent":["import type { ContextRequestEvent } from \"#root/context-request-event.js\";\nimport type { LoadingCallback } from \"#root/types.js\";\n\nimport { SKELETON_CONTEXT } from \"#root/constants.js\";\n\nexport class SkeletonProvider extends HTMLElement {\n public static observedAttributes = [\"loading\"] as const;\n\n readonly #subscribers = new Set<LoadingCallback>();\n readonly #internals = this.attachInternals();\n\n public constructor() {\n super();\n /**\n * Adding listener from the constructor so the provider is ready before any\n * descendant connects and asks for context.\n */\n this.addEventListener(\n \"context-request\",\n (event: Readonly<ContextRequestEvent>) => {\n if (\n event.context !== SKELETON_CONTEXT ||\n typeof event.callback !== \"function\"\n ) {\n return;\n }\n\n /** The nearest provider wins */\n event.stopPropagation();\n\n const { callback: subscriber } = event;\n\n if (event.subscribe) {\n this.#subscribers.add(subscriber);\n subscriber(this.loading, () => {\n this.#subscribers.delete(subscriber);\n });\n } else {\n subscriber(this.loading);\n }\n },\n );\n }\n\n public get loading() {\n return this.hasAttribute(\"loading\");\n }\n\n public set loading(value: boolean) {\n this.toggleAttribute(\"loading\", value);\n }\n\n public attributeChangedCallback(\n _name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue) {\n return;\n }\n /**\n * Via internals, so an aria-busy attribute set by the user still takes\n * precedence.\n */\n // oxlint-disable-next-line unicorn/no-null -- ariaBusy accepts either string or null\n this.#internals.ariaBusy = this.loading ? \"true\" : null;\n for (const subscriber of this.#subscribers) {\n subscriber(this.loading);\n }\n }\n}\n"],"mappings":"A,kDAKA,IAAaA,EAAb,cAAsC,WAAY,CAChD,OAAc,mBAAqB,CAAC,SAAS,EAE7C,mBAAwB,IAAI,IAC5B,GAAsB,KAAK,gBAAgB,EAE3C,aAAqB,CACnB,MAAM,EAKN,KAAK,iBACH,kBACC,GAAyC,CACxC,GACEC,EAAM,UAAYC,GAClB,OAAOD,EAAM,UAAa,WAE1B,OAIFA,EAAM,gBAAgB,EAEtB,GAAM,CAAE,SAAUE,GAAeF,EAE7BA,EAAM,WACR,KAAKG,GAAa,IAAID,CAAU,EAChCA,EAAW,KAAK,YAAe,CAC7B,KAAKC,GAAa,OAAOD,CAAU,CACrC,CAAC,GAEDA,EAAW,KAAK,OAAO,CAE3B,CACF,CACF,CAEA,IAAW,SAAU,CACnB,OAAO,KAAK,aAAa,SAAS,CACpC,CAEA,IAAW,QAAQ,EAAgB,CACjC,KAAK,gBAAgB,UAAWE,CAAK,CACvC,CAEA,yBACE,EACA,EACA,EACA,CACA,GAAIC,IAAaC,EAQjB,MAAKC,GAAW,SAAW,KAAK,QAAU,OAAS,KACnD,IAAK,IAAML,KAAc,KAAKC,GAC5BD,EAAW,KAAK,OAAO,CAF0B,CAIrD,CACF,E"}
@@ -1,7 +1,6 @@
1
- //#region src/Skeleton.d.ts
2
1
  export declare class Skeleton extends HTMLElement {
3
2
  #private;
4
- static observedAttributes: string[];
3
+ static readonly observedAttributes: string[];
5
4
  get loading(): boolean;
6
5
  set loading(value: boolean);
7
6
  /** Keeps the content focusable and clickable while loading */
@@ -11,8 +10,7 @@ export declare class Skeleton extends HTMLElement {
11
10
  get inline(): boolean;
12
11
  set inline(value: boolean);
13
12
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
14
- connectedCallback(): void;
13
+ connectedCallback(): Promise<void>;
15
14
  disconnectedCallback(): void;
16
15
  }
17
- //#endregion
18
- //# sourceMappingURL=Skeleton.d.ts.map
16
+ //# sourceMappingURL=skeleton.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{SKELETON_CONTEXT as e}from"./constants.js";import{ContextRequestEvent as t}from"./context-request-event.js";import{adoptStyles as n}from"./styles.js";function r(e){return`adoptedStyleSheets`in e}var i=class extends HTMLElement{static observedAttributes=[`interactive`,`inline`];#e;#t=new MutationObserver(()=>{this.#n()});get loading(){return this.hasAttribute(`loading`)}set loading(e){this.toggleAttribute(`loading`,e)}get interactive(){return this.hasAttribute(`interactive`)}set interactive(e){this.toggleAttribute(`interactive`,e)}get inline(){return this.hasAttribute(`inline`)}set inline(e){this.toggleAttribute(`inline`,e)}attributeChangedCallback(e,t,n){t!==n&&(e===`inline`?this.#n():this.#r(this.loading))}async connectedCallback(){let i=this.getRootNode();r(i)&&await n(i),this.#n(),this.#r(!1),this.dispatchEvent(new t(e,(e,t)=>{t&&(this.#e=t),this.#r(e)},!0)),this.#t.observe(this,{childList:!0,characterData:!0,subtree:!0})}disconnectedCallback(){this.#e?.(),this.#e=void 0,this.#t.disconnect()}#n(){let e=this.inline;for(let t of this.childNodes){if(e)break;t instanceof Text&&t.data.trim()!==``&&(e=!0)}this.toggleAttribute(`inline`,e)}#r(e){this.toggleAttribute(`loading`,e),this.toggleAttribute(`inert`,e&&!this.interactive)}};export{i as Skeleton};
2
+ //# sourceMappingURL=skeleton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skeleton.js","names":["isDocumentOrShadowRoot","node","Skeleton","#update","value","oldValue","newValue","name","#render","root","adoptStyles","ContextRequestEvent","SKELETON_CONTEXT","unsubscribe","#unsubscribe","loading","#observer","isInline"],"sources":["../src/skeleton.ts"],"sourcesContent":["import type { Unsubscribe } from \"#root/types.js\";\n\nimport { SKELETON_CONTEXT } from \"#root/constants.js\";\nimport { ContextRequestEvent } from \"#root/context-request-event.js\";\nimport { adoptStyles } from \"#root/styles.js\";\n\nfunction isDocumentOrShadowRoot(\n node: Node | Document | ShadowRoot,\n): node is Document | ShadowRoot {\n return \"adoptedStyleSheets\" in node;\n}\n\nexport class Skeleton extends HTMLElement {\n public static readonly observedAttributes = [\"interactive\", \"inline\"];\n\n #unsubscribe?: Unsubscribe | undefined;\n\n readonly #observer = new MutationObserver(() => {\n this.#update();\n });\n\n public get loading() {\n return this.hasAttribute(\"loading\");\n }\n\n public set loading(value: boolean) {\n this.toggleAttribute(\"loading\", value);\n }\n\n /** Keeps the content focusable and clickable while loading */\n public get interactive() {\n return this.hasAttribute(\"interactive\");\n }\n\n public set interactive(value: boolean) {\n this.toggleAttribute(\"interactive\", value);\n }\n\n /** Forces text mode, even when the text is wrapped in elements */\n public get inline() {\n return this.hasAttribute(\"inline\");\n }\n\n public set inline(value: boolean) {\n this.toggleAttribute(\"inline\", value);\n }\n\n public attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue) {\n return;\n }\n if (name === \"inline\") {\n this.#update();\n } else {\n this.#render(this.loading);\n }\n }\n\n public async connectedCallback() {\n const root = this.getRootNode();\n\n if (isDocumentOrShadowRoot(root)) {\n await adoptStyles(root);\n }\n\n this.#update();\n this.#render(false);\n\n this.dispatchEvent(\n new ContextRequestEvent(\n SKELETON_CONTEXT,\n (loading, unsubscribe) => {\n if (unsubscribe) {\n this.#unsubscribe = unsubscribe;\n }\n this.#render(loading);\n },\n true,\n ),\n );\n\n this.#observer.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n public disconnectedCallback() {\n this.#unsubscribe?.();\n this.#unsubscribe = undefined;\n this.#observer.disconnect();\n }\n\n #update() {\n let isInline = this.inline;\n for (const node of this.childNodes) {\n if (isInline) {\n break;\n }\n if (node instanceof Text && node.data.trim() !== \"\") {\n isInline = true;\n }\n }\n this.toggleAttribute(\"inline\", isInline);\n }\n\n #render(loading: boolean) {\n this.toggleAttribute(\"loading\", loading);\n this.toggleAttribute(\"inert\", loading && !this.interactive);\n }\n}\n"],"mappings":"A,6JAMA,SAASA,EACP,EAC+B,CAC/B,MAAO,uBAAwBC,CACjC,CAEA,IAAaC,EAAb,cAA8B,WAAY,CACxC,OAAuB,mBAAqB,CAAC,cAAe,QAAQ,EAEpE,GAEA,GAAqB,IAAI,qBAAuB,CAC9C,KAAKC,GAAQ,CACf,CAAC,EAED,IAAW,SAAU,CACnB,OAAO,KAAK,aAAa,SAAS,CACpC,CAEA,IAAW,QAAQ,EAAgB,CACjC,KAAK,gBAAgB,UAAWC,CAAK,CACvC,CAGA,IAAW,aAAc,CACvB,OAAO,KAAK,aAAa,aAAa,CACxC,CAEA,IAAW,YAAY,EAAgB,CACrC,KAAK,gBAAgB,cAAeA,CAAK,CAC3C,CAGA,IAAW,QAAS,CAClB,OAAO,KAAK,aAAa,QAAQ,CACnC,CAEA,IAAW,OAAO,EAAgB,CAChC,KAAK,gBAAgB,SAAUA,CAAK,CACtC,CAEA,yBACE,EACA,EACA,EACA,CACIC,IAAaC,IAGbC,IAAS,SACX,KAAKJ,GAAQ,EAEb,KAAKK,GAAQ,KAAK,OAAO,EAE7B,CAEA,MAAa,mBAAoB,CAC/B,IAAMC,EAAO,KAAK,YAAY,EAE1BT,EAAuBS,CAAI,GAC7B,MAAMC,EAAYD,CAAI,EAGxB,KAAKN,GAAQ,EACb,KAAKK,GAAQ,EAAK,EAElB,KAAK,cACH,IAAIG,EACFC,GACC,EAAS,IAAgB,CACpBC,IACF,KAAKC,GAAeD,GAEtB,KAAKL,GAAQO,CAAO,CACtB,EACA,EACF,CACF,EAEA,KAAKC,GAAU,QAAQ,KAAM,CAC3B,UAAW,GACX,cAAe,GACf,QAAS,EACX,CAAC,CACH,CAEA,sBAA8B,CAC5B,KAAKF,KAAe,EACpB,KAAKA,GAAe,IAAA,GACpB,KAAKE,GAAU,WAAW,CAC5B,CAEA,IAAU,CACR,IAAIC,EAAW,KAAK,OACpB,IAAK,IAAMhB,KAAQ,KAAK,WAAY,CAClC,GAAIgB,EACF,MAEEhB,aAAgB,MAAQA,EAAK,KAAK,KAAK,IAAM,KAC/C,EAAW,GAEf,CACA,KAAK,gBAAgB,SAAUgB,CAAQ,CACzC,CAEA,GAAQ,EAAkB,CACxB,KAAK,gBAAgB,UAAWF,CAAO,EACvC,KAAK,gBAAgB,QAASA,GAAW,CAAC,KAAK,WAAW,CAC5D,CACF,E"}
package/dist/styles.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import{COMPONENT_NAME as e,CSS_CONTENT as t}from"./constants.js";const n=`@scope (${e}) {
2
2
  ${t}
3
- }`;let r;function i(e){r||(r=new CSSStyleSheet,r.replaceSync(n)),e.adoptedStyleSheets.includes(r)||(e.adoptedStyleSheets=[...e.adoptedStyleSheets,r])}export{i as adoptStyles};
3
+ }`,r={};async function i(e){r.sheet||(r.sheet=new CSSStyleSheet,await r.sheet.replace(n));let{sheet:t}=r;e.adoptedStyleSheets.includes(t)||(e.adoptedStyleSheets=[...e.adoptedStyleSheets,t])}export{i as adoptStyles};
4
4
  //# sourceMappingURL=styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","names":["css","COMPONENT_NAME","CSS_CONTENT","sheet","adoptStyles","root"],"sources":["../src/styles.ts"],"sourcesContent":["import { CSS_CONTENT, COMPONENT_NAME } from \"#root/constants.js\";\n\n/** It gets replaced with styles.css content during build. */\nconst css = `@scope (${COMPONENT_NAME}) {\n ${CSS_CONTENT}\n}`;\n\nlet sheet: CSSStyleSheet | undefined;\n\nexport function adoptStyles(root: Document | ShadowRoot) {\n if (!sheet) {\n sheet = new CSSStyleSheet();\n sheet.replaceSync(css);\n }\n if (!root.adoptedStyleSheets.includes(sheet)) {\n root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];\n }\n}\n"],"mappings":"A,iEAGA,MAAMA,EAAM,WAAWC,EAAe;IAClCC,EAAY;GAGhB,IAAIC,EAEJ,SAAgBC,EAAY,EAA6B,CAClDD,IACH,EAAQ,IAAI,cACZA,EAAM,YAAYH,CAAG,GAElBK,EAAK,mBAAmB,SAASF,CAAK,IACzC,EAAK,mBAAqB,CAAC,GAAGE,EAAK,mBAAoBF,CAAK,EAEhE,C"}
1
+ {"version":3,"file":"styles.js","names":["css","COMPONENT_NAME","CSS_CONTENT","cache","adoptStyles","root","sheet"],"sources":["../src/styles.ts"],"sourcesContent":["import { CSS_CONTENT, COMPONENT_NAME } from \"#root/constants.js\";\n\n/** CSS_CONTENT get replaced with styles.css content during build. */\nconst css = `@scope (${COMPONENT_NAME}) {\n ${CSS_CONTENT}\n}`;\n\nconst cache: { sheet?: CSSStyleSheet } = {};\n\nexport async function adoptStyles(root: Document | ShadowRoot) {\n if (!cache.sheet) {\n cache.sheet = new CSSStyleSheet();\n await cache.sheet.replace(css);\n }\n const { sheet } = cache;\n if (!root.adoptedStyleSheets.includes(sheet)) {\n root.adoptedStyleSheets = [...root.adoptedStyleSheets, sheet];\n }\n}\n"],"mappings":"A,iEAGA,MAAMA,EAAM,WAAWC,EAAe;IAClCC,EAAY;GAGVC,EAAmC,CAAC,EAE1C,eAAsBC,EAAY,EAA6B,CACxDD,EAAM,QACT,EAAM,MAAQ,IAAI,cAClB,MAAMA,EAAM,MAAM,QAAQH,CAAG,GAE/B,GAAM,CAAE,SAAUG,EACbE,EAAK,mBAAmB,SAASC,CAAK,IACzC,EAAK,mBAAqB,CAAC,GAAGD,EAAK,mBAAoBC,CAAK,EAEhE,C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nano-skeleton",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "The perfect skeleton component",
5
5
  "homepage": "https://smashercosmo.github.io/nano-skeleton/",
6
6
  "bugs": {
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "devDependencies": {
24
24
  "@internal/tsconfig": "0.0.0",
25
- "@tsdown/css": "0.23.1",
26
- "@types/node": "26.6.5",
25
+ "@tsdown/css": "0.23.4",
26
+ "@types/node": "26.6.6",
27
27
  "bumpp": "12.3.0",
28
- "rolldown": "1.2.14",
29
- "tsdown": "0.23.1"
28
+ "rolldown": "1.2.16",
29
+ "tsdown": "0.23.4"
30
30
  },
31
31
  "types": "./dist/index.d.ts",
32
32
  "exports": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"ContextRequestEvent.js","names":["ContextRequestEvent","context","callback","subscribe"],"sources":["../src/ContextRequestEvent.ts"],"sourcesContent":["import type { LoadingCallback } from \"#root/types.js\";\n\n/**\n * Follows the community Context Protocol (@see\n * https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md)\n */\nexport class ContextRequestEvent extends Event {\n readonly context: unknown;\n readonly callback: LoadingCallback;\n readonly subscribe: boolean;\n\n constructor(context: unknown, callback: LoadingCallback, subscribe = false) {\n /** Composed, so skeletons inside shadow roots can reach a provider outside */\n super(\"context-request\", { bubbles: true, composed: true });\n this.context = context;\n this.callback = callback;\n this.subscribe = subscribe;\n }\n}\n"],"mappings":"AAMA,IAAaA,EAAb,cAAyC,KAAM,CAC7C,QACA,SACA,UAEA,YAAY,EAAkB,EAA2B,EAAY,GAAO,CAE1E,MAAM,kBAAmB,CAAE,QAAS,GAAM,SAAU,EAAK,CAAC,EAC1D,KAAK,QAAUC,EACf,KAAK,SAAWC,EAChB,KAAK,UAAYC,CACnB,CACF,E"}
package/dist/Skeleton.js DELETED
@@ -1,2 +0,0 @@
1
- import{SKELETON_CONTEXT as e}from"./constants.js";import{ContextRequestEvent as t}from"./ContextRequestEvent.js";import{adoptStyles as n}from"./styles.js";function r(e){return`adoptedStyleSheets`in e}var i=class extends HTMLElement{static observedAttributes=[`interactive`,`inline`];#e;#t=new MutationObserver(()=>this.#n());get loading(){return this.hasAttribute(`loading`)}set loading(e){this.toggleAttribute(`loading`,!!e)}get interactive(){return this.hasAttribute(`interactive`)}set interactive(e){this.toggleAttribute(`interactive`,!!e)}get inline(){return this.hasAttribute(`inline`)}set inline(e){this.toggleAttribute(`inline`,!!e)}attributeChangedCallback(e,t,n){t!==n&&(e===`inline`?this.#n():this.#r(this.loading))}connectedCallback(){let i=this.getRootNode();r(i)&&n(i),this.#n(),this.#r(!1),this.dispatchEvent(new t(e,(e,t)=>{t&&(this.#e=t),this.#r(e)},!0)),this.#t.observe(this,{childList:!0,characterData:!0,subtree:!0})}disconnectedCallback(){this.#e?.(),this.#e=void 0,this.#t.disconnect()}#n(){let e=this.inline;for(let t of this.childNodes){if(e)break;t instanceof Text&&t.data.trim()!==``&&(e=!0)}this.toggleAttribute(`inline`,e)}#r(e){this.toggleAttribute(`loading`,e),this.toggleAttribute(`inert`,e&&!this.interactive)}};export{i as Skeleton};
2
- //# sourceMappingURL=Skeleton.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Skeleton.js","names":["isDocumentOrShadowRoot","node","Skeleton","#update","value","oldValue","newValue","name","#render","root","adoptStyles","ContextRequestEvent","SKELETON_CONTEXT","unsubscribe","#unsubscribe","loading","#observer","isInline"],"sources":["../src/Skeleton.ts"],"sourcesContent":["import { SKELETON_CONTEXT } from \"#root/constants.js\";\nimport { ContextRequestEvent } from \"#root/ContextRequestEvent.js\";\nimport { adoptStyles } from \"#root/styles.js\";\nimport { type Unsubscribe } from \"#root/types.js\";\n\nfunction isDocumentOrShadowRoot(\n node: Node | Document | ShadowRoot,\n): node is Document | ShadowRoot {\n return \"adoptedStyleSheets\" in node;\n}\n\nexport class Skeleton extends HTMLElement {\n static observedAttributes = [\"interactive\", \"inline\"];\n\n #unsubscribe?: Unsubscribe | undefined;\n #observer = new MutationObserver(() => this.#update());\n\n get loading() {\n return this.hasAttribute(\"loading\");\n }\n\n set loading(value: boolean) {\n this.toggleAttribute(\"loading\", Boolean(value));\n }\n\n /** Keeps the content focusable and clickable while loading */\n get interactive() {\n return this.hasAttribute(\"interactive\");\n }\n\n set interactive(value: boolean) {\n this.toggleAttribute(\"interactive\", Boolean(value));\n }\n\n /** Forces text mode, even when the text is wrapped in elements */\n get inline() {\n return this.hasAttribute(\"inline\");\n }\n\n set inline(value: boolean) {\n this.toggleAttribute(\"inline\", Boolean(value));\n }\n\n attributeChangedCallback(\n name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue) return;\n if (name === \"inline\") this.#update();\n else this.#render(this.loading);\n }\n\n connectedCallback() {\n const root = this.getRootNode();\n\n if (isDocumentOrShadowRoot(root)) {\n adoptStyles(root);\n }\n\n this.#update();\n this.#render(false);\n\n this.dispatchEvent(\n new ContextRequestEvent(\n SKELETON_CONTEXT,\n (loading, unsubscribe) => {\n if (unsubscribe) this.#unsubscribe = unsubscribe;\n this.#render(loading);\n },\n true,\n ),\n );\n\n this.#observer.observe(this, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n }\n\n disconnectedCallback() {\n this.#unsubscribe?.();\n this.#unsubscribe = undefined;\n this.#observer.disconnect();\n }\n\n #update() {\n let isInline = this.inline;\n for (const node of this.childNodes) {\n if (isInline) break;\n if (node instanceof Text && node.data.trim() !== \"\") {\n isInline = true;\n }\n }\n this.toggleAttribute(\"inline\", isInline);\n }\n\n #render(loading: boolean) {\n this.toggleAttribute(\"loading\", loading);\n this.toggleAttribute(\"inert\", loading && !this.interactive);\n }\n}\n"],"mappings":"A,2JAKA,SAASA,EACP,EAC+B,CAC/B,MAAO,uBAAwBC,CACjC,CAEA,IAAaC,EAAb,cAA8B,WAAY,CACxC,OAAO,mBAAqB,CAAC,cAAe,QAAQ,EAEpD,GACA,GAAY,IAAI,qBAAuB,KAAKC,GAAQ,CAAC,EAErD,IAAI,SAAU,CACZ,OAAO,KAAK,aAAa,SAAS,CACpC,CAEA,IAAI,QAAQ,EAAgB,CAC1B,KAAK,gBAAgB,UAAW,EAAQC,CAAM,CAChD,CAGA,IAAI,aAAc,CAChB,OAAO,KAAK,aAAa,aAAa,CACxC,CAEA,IAAI,YAAY,EAAgB,CAC9B,KAAK,gBAAgB,cAAe,EAAQA,CAAM,CACpD,CAGA,IAAI,QAAS,CACX,OAAO,KAAK,aAAa,QAAQ,CACnC,CAEA,IAAI,OAAO,EAAgB,CACzB,KAAK,gBAAgB,SAAU,EAAQA,CAAM,CAC/C,CAEA,yBACE,EACA,EACA,EACA,CACIC,IAAaC,IACbC,IAAS,SAAU,KAAKJ,GAAQ,EAC/B,KAAKK,GAAQ,KAAK,OAAO,EAChC,CAEA,mBAAoB,CAClB,IAAMC,EAAO,KAAK,YAAY,EAE1BT,EAAuBS,CAAI,GAC7BC,EAAYD,CAAI,EAGlB,KAAKN,GAAQ,EACb,KAAKK,GAAQ,EAAK,EAElB,KAAK,cACH,IAAIG,EACFC,GACC,EAAS,IAAgB,CACpBC,IAAa,KAAKC,GAAeD,GACrC,KAAKL,GAAQO,CAAO,CACtB,EACA,EACF,CACF,EAEA,KAAKC,GAAU,QAAQ,KAAM,CAC3B,UAAW,GACX,cAAe,GACf,QAAS,EACX,CAAC,CACH,CAEA,sBAAuB,CACrB,KAAKF,KAAe,EACpB,KAAKA,GAAe,IAAA,GACpB,KAAKE,GAAU,WAAW,CAC5B,CAEA,IAAU,CACR,IAAIC,EAAW,KAAK,OACpB,IAAK,IAAMhB,KAAQ,KAAK,WAAY,CAClC,GAAIgB,EAAU,MACVhB,aAAgB,MAAQA,EAAK,KAAK,KAAK,IAAM,KAC/C,EAAW,GAEf,CACA,KAAK,gBAAgB,SAAUgB,CAAQ,CACzC,CAEA,GAAQ,EAAkB,CACxB,KAAK,gBAAgB,UAAWF,CAAO,EACvC,KAAK,gBAAgB,QAASA,GAAW,CAAC,KAAK,WAAW,CAC5D,CACF,E"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"SkeletonProvider.js","names":["SkeletonProvider","event","SKELETON_CONTEXT","#subscribers","callback","value","oldValue","newValue","#internals"],"sources":["../src/SkeletonProvider.ts"],"sourcesContent":["import type { ContextRequestEvent } from \"#root/ContextRequestEvent.js\";\nimport type { LoadingCallback } from \"#root/types.js\";\n\nimport { SKELETON_CONTEXT } from \"#root/constants.js\";\n\nexport class SkeletonProvider extends HTMLElement {\n static observedAttributes = [\"loading\"];\n\n #subscribers = new Set<LoadingCallback>();\n #internals = this.attachInternals();\n\n constructor() {\n super();\n /**\n * Adding listener from the constructor so the provider is ready before any\n * descendant connects and asks for context.\n */\n this.addEventListener(\"context-request\", (event: ContextRequestEvent) => {\n if (\n event.context !== SKELETON_CONTEXT ||\n typeof event.callback !== \"function\"\n )\n return;\n\n /** The nearest provider wins */\n event.stopPropagation();\n\n const { callback } = event;\n\n if (event.subscribe) {\n this.#subscribers.add(callback);\n callback(this.loading, () => this.#subscribers.delete(callback));\n } else {\n callback(this.loading);\n }\n });\n }\n\n get loading() {\n return this.hasAttribute(\"loading\");\n }\n\n set loading(value: boolean) {\n this.toggleAttribute(\"loading\", Boolean(value));\n }\n\n attributeChangedCallback(\n _name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue) return;\n /**\n * Via internals, so an aria-busy attribute set by the user still takes\n * precedence.\n */\n this.#internals.ariaBusy = this.loading ? \"true\" : null;\n for (const callback of this.#subscribers) callback(this.loading);\n }\n}\n"],"mappings":"A,kDAKA,IAAaA,EAAb,cAAsC,WAAY,CAChD,OAAO,mBAAqB,CAAC,SAAS,EAEtC,mBAAe,IAAI,IACnB,GAAa,KAAK,gBAAgB,EAElC,aAAc,CACZ,MAAM,EAKN,KAAK,iBAAiB,kBAAoB,GAA+B,CACvE,GACEC,EAAM,UAAYC,GAClB,OAAOD,EAAM,UAAa,WAE1B,OAGFA,EAAM,gBAAgB,EAEtB,GAAM,CAAE,YAAaA,EAEjBA,EAAM,WACR,KAAKE,GAAa,IAAIC,CAAQ,EAC9BA,EAAS,KAAK,YAAe,KAAKD,GAAa,OAAOC,CAAQ,CAAC,GAE/DA,EAAS,KAAK,OAAO,CAEzB,CAAC,CACH,CAEA,IAAI,SAAU,CACZ,OAAO,KAAK,aAAa,SAAS,CACpC,CAEA,IAAI,QAAQ,EAAgB,CAC1B,KAAK,gBAAgB,UAAW,EAAQC,CAAM,CAChD,CAEA,yBACE,EACA,EACA,EACA,CACA,GAAIC,IAAaC,EAKjB,MAAKC,GAAW,SAAW,KAAK,QAAU,OAAS,KACnD,IAAK,IAAMJ,KAAY,KAAKD,GAAcC,EAAS,KAAK,OAAO,CADZ,CAErD,CACF,E"}