nano-skeleton 0.1.1 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - feat: add `interactive` attribute to keep loading content focusable
8
+
9
+ - feat: expose `aria-busy` on loading providers
10
+
3
11
  ## 0.1.1
4
12
 
5
13
  ### Patch Changes
package/README.md CHANGED
@@ -34,7 +34,8 @@ for a shimmering placeholder of the same shape.
34
34
  - **Media-aware.** `<img>` and `<video>` keep their box while their content is
35
35
  hidden.
36
36
  - **Accessible by default.** Loading content is made `inert`, so it can't be
37
- focused or clicked, and the shimmer respects `prefers-reduced-motion`.
37
+ focused or clicked, the provider is marked `aria-busy`, and the shimmer
38
+ respects `prefers-reduced-motion`. Opt out per skeleton with `interactive`.
38
39
  - **Light and dark mode** out of the box via `light-dark()`.
39
40
  - **Works everywhere:** plain HTML, any framework, inside Shadow DOM.
40
41
  - **No dependencies**, styles included. No CSS file to import.
@@ -156,6 +157,38 @@ Because the placeholder is just the element's own box, make sure it has a size
156
157
  while loading, e.g. `width`/`height` attributes or `aspect-ratio` on images, or
157
158
  a fixed size on containers.
158
159
 
160
+ The content is hidden with `opacity: 0`, so if an element inside has a
161
+ `transition` on `opacity` (or `transition: all`), it fades in and out when
162
+ `loading` changes instead of switching instantly.
163
+
164
+ ### Interactive skeletons
165
+
166
+ By default, a loading skeleton is `inert`: its content can't be focused, clicked
167
+ or reached by assistive technology. That's usually what you want, but sometimes
168
+ the content is usable before the data arrives, e.g. a link whose `href` is
169
+ already known, or a button that doesn't depend on the data.
170
+
171
+ It matters most when content goes back into the loading state (a background
172
+ refetch, pagination): an `inert` element that had focus loses it, and keyboard
173
+ users are sent back to the top of the page.
174
+
175
+ Add `interactive` to keep the content focusable and clickable while loading:
176
+
177
+ ```html
178
+ <nano-skeleton-provider loading>
179
+ <nano-skeleton interactive>
180
+ <a href="/posts/42" class="card__link">
181
+ <span>Placeholder post title</span>
182
+ </a>
183
+ </nano-skeleton>
184
+ </nano-skeleton-provider>
185
+ ```
186
+
187
+ The placeholder looks the same, but the content stays in the tab order and
188
+ keeps its accessible name. Make sure that name makes sense while loading, as screen reader users
189
+ will hear it. The provider sets `aria-busy="true"` while loading to tell them
190
+ the region is still updating.
191
+
159
192
  ## Examples
160
193
 
161
194
  ### A card
@@ -291,7 +324,12 @@ import type { DetailedHTMLProps, HTMLAttributes } from "react";
291
324
  declare module "react" {
292
325
  namespace JSX {
293
326
  interface IntrinsicElements {
294
- [COMPONENT_NAME]: DetailedHTMLProps<HTMLAttributes<Skeleton>, Skeleton>;
327
+ [COMPONENT_NAME]: DetailedHTMLProps<
328
+ HTMLAttributes<Skeleton>,
329
+ Skeleton
330
+ > & {
331
+ interactive?: boolean;
332
+ };
295
333
  [PROVIDER_NAME]: DetailedHTMLProps<
296
334
  HTMLAttributes<SkeletonProvider>,
297
335
  SkeletonProvider
@@ -316,16 +354,22 @@ Registers `<nano-skeleton-provider>` and `<nano-skeleton>` with
316
354
  | -------------------- | --------- | --------------------------------------------------------------------- |
317
355
  | `loading` | `boolean` | When present/`true`, all descendant skeletons render as placeholders. |
318
356
 
357
+ While loading, the provider is exposed to assistive technology as
358
+ `aria-busy="true"`. An `aria-busy` attribute you set yourself takes precedence.
359
+
319
360
  ### `<nano-skeleton>`
320
361
 
321
- No attributes to set. It exposes these state attributes, which you can use as
322
- styling hooks:
362
+ | Attribute / property | Type | Description |
363
+ | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------- |
364
+ | `interactive` | `boolean` | Keeps the content focusable and clickable while loading. See [Interactive skeletons](#interactive-skeletons). |
365
+
366
+ It also exposes these state attributes, which you can use as styling hooks:
323
367
 
324
- | Attribute | Set when |
325
- | -------------- | --------------------------------------------------------- |
326
- | `data-loading` | The nearest provider is loading. |
327
- | `data-text` | The skeleton directly contains text (text mode). |
328
- | `inert` | The nearest provider is loading. Blocks focus and clicks. |
368
+ | Attribute | Set when |
369
+ | -------------- | ------------------------------------------------------------------------------------- |
370
+ | `data-loading` | The nearest provider is loading. |
371
+ | `data-text` | The skeleton directly contains text (text mode). |
372
+ | `inert` | The nearest provider is loading and `interactive` isn't set. Blocks focus and clicks. |
329
373
 
330
374
  ### Exports
331
375
 
@@ -1,6 +1,11 @@
1
1
  //#region src/Skeleton.d.ts
2
2
  export declare class Skeleton extends HTMLElement {
3
3
  #private;
4
+ static observedAttributes: string[];
5
+ /** Keeps the content focusable and clickable while loading */
6
+ get interactive(): boolean;
7
+ set interactive(value: boolean);
8
+ attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
4
9
  connectedCallback(): void;
5
10
  disconnectedCallback(): void;
6
11
  }
package/dist/Skeleton.js CHANGED
@@ -1,2 +1,2 @@
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{#e;#t=new MutationObserver(()=>this.#n());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=!1;for(let t of this.childNodes)if(t instanceof Text&&t.data.trim()!==``){e=!0;break}this.toggleAttribute(`data-text`,e)}#r(e){this.toggleAttribute(`inert`,e),this.toggleAttribute(`data-loading`,e)}};export{i as Skeleton};
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`];#e=!1;#t;#n=new MutationObserver(()=>this.#r());get interactive(){return this.hasAttribute(`interactive`)}set interactive(e){this.toggleAttribute(`interactive`,!!e)}attributeChangedCallback(e,t,n){t!==n&&this.#i(this.#e)}connectedCallback(){let i=this.getRootNode();r(i)&&n(i),this.#r(),this.#i(!1),this.dispatchEvent(new t(e,(e,t)=>{t&&(this.#t=t),this.#i(e)},!0)),this.#n.observe(this,{childList:!0,characterData:!0,subtree:!0})}disconnectedCallback(){this.#t?.(),this.#t=void 0,this.#n.disconnect()}#r(){let e=!1;for(let t of this.childNodes)if(t instanceof Text&&t.data.trim()!==``){e=!0;break}this.toggleAttribute(`data-text`,e)}#i(e){this.#e=e,this.toggleAttribute(`inert`,e&&!this.interactive),this.toggleAttribute(`data-loading`,e)}};export{i as Skeleton};
2
2
  //# sourceMappingURL=Skeleton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Skeleton.js","names":["isDocumentOrShadowRoot","node","Skeleton","#update","root","adoptStyles","#render","ContextRequestEvent","SKELETON_CONTEXT","unsubscribe","#unsubscribe","loading","#observer","isText"],"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 #unsubscribe?: Unsubscribe | undefined;\n #observer = new MutationObserver(() => this.#update());\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 isText = false;\n for (const node of this.childNodes) {\n if (node instanceof Text && node.data.trim() !== \"\") {\n isText = true;\n break;\n }\n }\n this.toggleAttribute(\"data-text\", isText);\n }\n\n #render(loading: boolean) {\n this.toggleAttribute(\"inert\", loading);\n this.toggleAttribute(\"data-loading\", loading);\n }\n}\n"],"mappings":"A,2JAKA,SAASA,EACP,EAC+B,CAC/B,MAAO,uBAAwBC,CACjC,CAEA,IAAaC,EAAb,cAA8B,WAAY,CACxC,GACA,GAAY,IAAI,qBAAuB,KAAKC,GAAQ,CAAC,EAErD,mBAAoB,CAClB,IAAMC,EAAO,KAAK,YAAY,EAE1BJ,EAAuBI,CAAI,GAC7BC,EAAYD,CAAI,EAGlB,KAAKD,GAAQ,EACb,KAAKG,GAAQ,EAAK,EAElB,KAAK,cACH,IAAIC,EACFC,GACC,EAAS,IAAgB,CACpBC,IAAa,KAAKC,GAAeD,GACrC,KAAKH,GAAQK,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,EAAS,GACb,IAAK,IAAMZ,KAAQ,KAAK,WACtB,GAAIA,aAAgB,MAAQA,EAAK,KAAK,KAAK,IAAM,GAAI,CACnD,EAAS,GACT,KACF,CAEF,KAAK,gBAAgB,YAAaY,CAAM,CAC1C,CAEA,GAAQ,EAAkB,CACxB,KAAK,gBAAgB,QAASF,CAAO,EACrC,KAAK,gBAAgB,eAAgBA,CAAO,CAC9C,CACF,E"}
1
+ {"version":3,"file":"Skeleton.js","names":["isDocumentOrShadowRoot","node","Skeleton","#update","value","oldValue","newValue","#render","#loading","root","adoptStyles","ContextRequestEvent","SKELETON_CONTEXT","unsubscribe","#unsubscribe","loading","#observer","isText"],"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\"];\n\n #loading = false;\n #unsubscribe?: Unsubscribe | undefined;\n #observer = new MutationObserver(() => this.#update());\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 attributeChangedCallback(\n _name: string,\n oldValue: string | null,\n newValue: string | null,\n ) {\n if (oldValue === newValue) return;\n 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 isText = false;\n for (const node of this.childNodes) {\n if (node instanceof Text && node.data.trim() !== \"\") {\n isText = true;\n break;\n }\n }\n this.toggleAttribute(\"data-text\", isText);\n }\n\n #render(loading: boolean) {\n this.#loading = loading;\n this.toggleAttribute(\"inert\", loading && !this.interactive);\n this.toggleAttribute(\"data-loading\", loading);\n }\n}\n"],"mappings":"A,2JAKA,SAASA,EACP,EAC+B,CAC/B,MAAO,uBAAwBC,CACjC,CAEA,IAAaC,EAAb,cAA8B,WAAY,CACxC,OAAO,mBAAqB,CAAC,aAAa,EAE1C,GAAW,GACX,GACA,GAAY,IAAI,qBAAuB,KAAKC,GAAQ,CAAC,EAGrD,IAAI,aAAc,CAChB,OAAO,KAAK,aAAa,aAAa,CACxC,CAEA,IAAI,YAAY,EAAgB,CAC9B,KAAK,gBAAgB,cAAe,EAAQC,CAAM,CACpD,CAEA,yBACE,EACA,EACA,EACA,CACIC,IAAaC,GACjB,KAAKC,GAAQ,KAAKC,EAAQ,CAC5B,CAEA,mBAAoB,CAClB,IAAMC,EAAO,KAAK,YAAY,EAE1BT,EAAuBS,CAAI,GAC7BC,EAAYD,CAAI,EAGlB,KAAKN,GAAQ,EACb,KAAKI,GAAQ,EAAK,EAElB,KAAK,cACH,IAAII,EACFC,GACC,EAAS,IAAgB,CACpBC,IAAa,KAAKC,GAAeD,GACrC,KAAKN,GAAQQ,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,EAAS,GACb,IAAK,IAAMhB,KAAQ,KAAK,WACtB,GAAIA,aAAgB,MAAQA,EAAK,KAAK,KAAK,IAAM,GAAI,CACnD,EAAS,GACT,KACF,CAEF,KAAK,gBAAgB,YAAagB,CAAM,CAC1C,CAEA,GAAQ,EAAkB,CACxB,KAAKT,GAAWO,EAChB,KAAK,gBAAgB,QAASA,GAAW,CAAC,KAAK,WAAW,EAC1D,KAAK,gBAAgB,eAAgBA,CAAO,CAC9C,CACF,E"}
@@ -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;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)for(let e of this.#e)e(this.loading)}};export{t as SkeletonProvider};
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
2
  //# sourceMappingURL=SkeletonProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SkeletonProvider.js","names":["SkeletonProvider","event","SKELETON_CONTEXT","#subscribers","callback","value","oldValue","newValue"],"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\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 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,IAEnB,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,EACjB,IAAK,IAAMH,KAAY,KAAKD,GAAcC,EAAS,KAAK,OAAO,CACjE,CACF,E"}
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"}
package/dist/constants.js CHANGED
@@ -21,10 +21,11 @@ const e=`nano-skeleton`,t=`${e}-provider`,{default:n}={default:`:scope {
21
21
 
22
22
  /*
23
23
  * Everything inside the root should disappear, but nothing jumps,
24
- * as layout is intact.
24
+ * as layout is intact. Hidden visually only: focus and accessibility are
25
+ * handled by inert, so interactive skeletons keep both.
25
26
  */
26
- :scope[data-loading] > * :is(*, *:before, *:after) {
27
- visibility: hidden !important;
27
+ :scope[data-loading] > * > * {
28
+ opacity: 0 !important;
28
29
  }
29
30
 
30
31
  /**
@@ -37,8 +38,7 @@ const e=`nano-skeleton`,t=`${e}-provider`,{default:n}={default:`:scope {
37
38
  var(--nano-skeleton-text-border-radius-default)
38
39
  );
39
40
  box-decoration-break: clone;
40
- text-box-edge: cap alphabetic;
41
- text-box-trim: trim-both;
41
+ text-box: cap alphabetic;
42
42
  /**
43
43
  * This ensures the ellipsis on truncated text is also hidden.
44
44
  * -webkit-text-fill-color overrides any "color" property that is also set.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nano-skeleton",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "The perfect skeleton component",
5
5
  "homepage": "https://github.com/smashercosmo/nano-skeleton#readme",
6
6
  "bugs": {