nano-skeleton 0.2.0 → 0.3.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 +6 -0
- package/README.md +32 -2
- package/dist/Skeleton.d.ts +4 -1
- package/dist/Skeleton.js +1 -1
- package/dist/Skeleton.js.map +1 -1
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ all of its skeletons at once.
|
|
|
101
101
|
A `<nano-skeleton>` without a provider simply renders its content as usual.
|
|
102
102
|
|
|
103
103
|
`<nano-skeleton>` works in one of two modes, picked automatically from its
|
|
104
|
-
content.
|
|
104
|
+
content (text mode can also be forced with the `text` attribute).
|
|
105
105
|
|
|
106
106
|
### Text mode
|
|
107
107
|
|
|
@@ -130,6 +130,34 @@ placeholder text first and replace it with real data later:
|
|
|
130
130
|
<h2><nano-skeleton>Loading title…</nano-skeleton></h2>
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
#### Text wrapped in elements
|
|
134
|
+
|
|
135
|
+
Automatic detection only looks at text placed directly inside the skeleton.
|
|
136
|
+
When the text is wrapped in an inline element, like a link or `<strong>`, add
|
|
137
|
+
`text` to force text mode:
|
|
138
|
+
|
|
139
|
+
```html
|
|
140
|
+
<p>
|
|
141
|
+
<nano-skeleton text>
|
|
142
|
+
<a href="/posts/42">Read the full post</a>
|
|
143
|
+
</nano-skeleton>
|
|
144
|
+
</p>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The placeholder follows the text lines exactly like plain text does. The
|
|
148
|
+
wrapping elements should be inline: a block or `inline-block` child gets a
|
|
149
|
+
single bar for its whole box.
|
|
150
|
+
|
|
151
|
+
Text decorations, such as a link's underline, stay visible while loading, so the
|
|
152
|
+
placeholder still reads as a link. To hide them too, wrap the link's content in
|
|
153
|
+
a `<span>`:
|
|
154
|
+
|
|
155
|
+
```html
|
|
156
|
+
<nano-skeleton text>
|
|
157
|
+
<a href="/posts/42"><span>Read the full post</span></a>
|
|
158
|
+
</nano-skeleton>
|
|
159
|
+
```
|
|
160
|
+
|
|
133
161
|
### Element mode
|
|
134
162
|
|
|
135
163
|
If the skeleton contains elements, it becomes `display: contents` (it doesn't
|
|
@@ -329,6 +357,7 @@ declare module "react" {
|
|
|
329
357
|
Skeleton
|
|
330
358
|
> & {
|
|
331
359
|
interactive?: boolean;
|
|
360
|
+
text?: boolean;
|
|
332
361
|
};
|
|
333
362
|
[PROVIDER_NAME]: DetailedHTMLProps<
|
|
334
363
|
HTMLAttributes<SkeletonProvider>,
|
|
@@ -362,13 +391,14 @@ While loading, the provider is exposed to assistive technology as
|
|
|
362
391
|
| Attribute / property | Type | Description |
|
|
363
392
|
| -------------------- | --------- | ------------------------------------------------------------------------------------------------------------- |
|
|
364
393
|
| `interactive` | `boolean` | Keeps the content focusable and clickable while loading. See [Interactive skeletons](#interactive-skeletons). |
|
|
394
|
+
| `text` | `boolean` | Forces text mode, e.g. for text inside a link. See [Text wrapped in elements](#text-wrapped-in-elements). |
|
|
365
395
|
|
|
366
396
|
It also exposes these state attributes, which you can use as styling hooks:
|
|
367
397
|
|
|
368
398
|
| Attribute | Set when |
|
|
369
399
|
| -------------- | ------------------------------------------------------------------------------------- |
|
|
370
400
|
| `data-loading` | The nearest provider is loading. |
|
|
371
|
-
| `data-text` | The skeleton directly contains text (text mode).
|
|
401
|
+
| `data-text` | The skeleton directly contains text, or `text` is set (text mode). |
|
|
372
402
|
| `inert` | The nearest provider is loading and `interactive` isn't set. Blocks focus and clicks. |
|
|
373
403
|
|
|
374
404
|
### Exports
|
package/dist/Skeleton.d.ts
CHANGED
|
@@ -5,7 +5,10 @@ export declare class Skeleton extends HTMLElement {
|
|
|
5
5
|
/** Keeps the content focusable and clickable while loading */
|
|
6
6
|
get interactive(): boolean;
|
|
7
7
|
set interactive(value: boolean);
|
|
8
|
-
|
|
8
|
+
/** Forces text mode, even when the text is wrapped in elements */
|
|
9
|
+
get text(): boolean;
|
|
10
|
+
set text(value: boolean);
|
|
11
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
9
12
|
connectedCallback(): void;
|
|
10
13
|
disconnectedCallback(): void;
|
|
11
14
|
}
|
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{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
|
+
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`,`text`];#e=!1;#t;#n=new MutationObserver(()=>this.#r());get interactive(){return this.hasAttribute(`interactive`)}set interactive(e){this.toggleAttribute(`interactive`,!!e)}get text(){return this.hasAttribute(`text`)}set text(e){this.toggleAttribute(`text`,!!e)}attributeChangedCallback(e,t,n){t!==n&&(e===`text`?this.#r():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=this.text;for(let t of this.childNodes){if(e)break;t instanceof Text&&t.data.trim()!==``&&(e=!0)}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
|
package/dist/Skeleton.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
1
|
+
{"version":3,"file":"Skeleton.js","names":["isDocumentOrShadowRoot","node","Skeleton","#update","value","oldValue","newValue","name","#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\", \"text\"];\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 /** Forces text mode, even when the text is wrapped in elements */\n get text() {\n return this.hasAttribute(\"text\");\n }\n\n set text(value: boolean) {\n this.toggleAttribute(\"text\", 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 === \"text\") 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 isText = this.text;\n for (const node of this.childNodes) {\n if (isText) break;\n if (node instanceof Text && node.data.trim() !== \"\") {\n isText = true;\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,cAAe,MAAM,EAElD,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,CAGA,IAAI,MAAO,CACT,OAAO,KAAK,aAAa,MAAM,CACjC,CAEA,IAAI,KAAK,EAAgB,CACvB,KAAK,gBAAgB,OAAQ,EAAQA,CAAM,CAC7C,CAEA,yBACE,EACA,EACA,EACA,CACIC,IAAaC,IACbC,IAAS,OAAQ,KAAKJ,GAAQ,EAC7B,KAAKK,GAAQ,KAAKC,EAAQ,EACjC,CAEA,mBAAoB,CAClB,IAAMC,EAAO,KAAK,YAAY,EAE1BV,EAAuBU,CAAI,GAC7BC,EAAYD,CAAI,EAGlB,KAAKP,GAAQ,EACb,KAAKK,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,KAAK,KAClB,IAAK,IAAMjB,KAAQ,KAAK,WAAY,CAClC,GAAIiB,EAAQ,MACRjB,aAAgB,MAAQA,EAAK,KAAK,KAAK,IAAM,KAC/C,EAAS,GAEb,CACA,KAAK,gBAAgB,YAAaiB,CAAM,CAC1C,CAEA,GAAQ,EAAkB,CACxB,KAAKT,GAAWO,EAChB,KAAK,gBAAgB,QAASA,GAAW,CAAC,KAAK,WAAW,EAC1D,KAAK,gBAAgB,eAAgBA,CAAO,CAC9C,CACF,E"}
|
package/dist/constants.js
CHANGED
|
@@ -44,6 +44,20 @@ const e=`nano-skeleton`,t=`${e}-provider`,{default:n}={default:`:scope {
|
|
|
44
44
|
* -webkit-text-fill-color overrides any "color" property that is also set.
|
|
45
45
|
*/
|
|
46
46
|
-webkit-text-fill-color: transparent;
|
|
47
|
+
/**
|
|
48
|
+
* Text might get visible when selected
|
|
49
|
+
*/
|
|
50
|
+
user-select: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Children and grandchildren need to inherit these styles
|
|
55
|
+
* for consistent outlines
|
|
56
|
+
*/
|
|
57
|
+
:scope[data-loading][data-text] > *,
|
|
58
|
+
:scope[data-loading][data-text] > * > * {
|
|
59
|
+
border-radius: inherit;
|
|
60
|
+
text-box: inherit;
|
|
47
61
|
}
|
|
48
62
|
|
|
49
63
|
:scope[data-loading] > :is(video, img):only-child {
|
package/dist/constants.js.map
CHANGED
|
@@ -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
|
|
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgGb,EAIWC,EAAmB,OAAO,IAAI,GAAGH,EAAe,SAAS,E"}
|