@threadlabs/looma 0.37.5 → 0.37.6
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/components/ui-skeleton/ui-skeleton.html +91 -0
- package/dist/index.js +1 -1
- package/docs/catalog.json +169 -1
- package/package.json +1 -1
- package/styles/ui-skeleton.css +91 -0
- package/vanilla/UiSkeleton.d.ts +26 -0
- package/vanilla/UiSkeleton.js +42 -0
- package/vanilla/index.js +1 -0
- package/vue/UiSkeleton.d.ts +37 -0
- package/vue/UiSkeleton.js +2 -0
- package/vue/UiSkeleton.vue +235 -0
- package/vue/chunks/UiSkeleton.js +151 -0
- package/vue/components.css +71 -0
- package/vue/index.d.ts +1 -0
- package/vue/index.js +2 -1
package/docs/catalog.json
CHANGED
|
@@ -365,6 +365,11 @@
|
|
|
365
365
|
"package": "@threadlabs/looma",
|
|
366
366
|
"resource": "@threadlabs/looma/components/ui-sidebar/ui-sidebar.html"
|
|
367
367
|
},
|
|
368
|
+
{
|
|
369
|
+
"tag": "ui-skeleton",
|
|
370
|
+
"package": "@threadlabs/looma",
|
|
371
|
+
"resource": "@threadlabs/looma/components/ui-skeleton/ui-skeleton.html"
|
|
372
|
+
},
|
|
368
373
|
{
|
|
369
374
|
"tag": "ui-slider",
|
|
370
375
|
"package": "@threadlabs/looma",
|
|
@@ -453,7 +458,7 @@
|
|
|
453
458
|
],
|
|
454
459
|
"schemaVersion": 1,
|
|
455
460
|
"package": "@threadlabs/looma",
|
|
456
|
-
"version": "0.37.
|
|
461
|
+
"version": "0.37.6",
|
|
457
462
|
"groups": [
|
|
458
463
|
{
|
|
459
464
|
"id": "layout",
|
|
@@ -20399,6 +20404,169 @@
|
|
|
20399
20404
|
}
|
|
20400
20405
|
]
|
|
20401
20406
|
},
|
|
20407
|
+
{
|
|
20408
|
+
"tag": "ui-skeleton",
|
|
20409
|
+
"package": "@threadlabs/looma",
|
|
20410
|
+
"root": "span",
|
|
20411
|
+
"description": "Reserve the shape of incoming content without announcing each placeholder. Text follows the surrounding line metrics; circles and rectangles stand in for avatars and media. Use one status message and aria-busy on the loading region, then replace the placeholders with real content. Keep existing content visible during refresh when possible. Animation is optional and respects reduced motion.",
|
|
20412
|
+
"designTokens": {
|
|
20413
|
+
"sources": [
|
|
20414
|
+
"packages/looma/src/components/ui-skeleton/ui-skeleton.html"
|
|
20415
|
+
],
|
|
20416
|
+
"component": [
|
|
20417
|
+
{
|
|
20418
|
+
"name": "--ui-skeleton-height",
|
|
20419
|
+
"fallbacks": [
|
|
20420
|
+
"1lh",
|
|
20421
|
+
"auto",
|
|
20422
|
+
"8rem"
|
|
20423
|
+
]
|
|
20424
|
+
},
|
|
20425
|
+
{
|
|
20426
|
+
"name": "--ui-skeleton-width",
|
|
20427
|
+
"fallbacks": [
|
|
20428
|
+
"100%",
|
|
20429
|
+
"calc(2.5rem + 2 * var(--ui-border-width))"
|
|
20430
|
+
]
|
|
20431
|
+
}
|
|
20432
|
+
],
|
|
20433
|
+
"shared": [
|
|
20434
|
+
{
|
|
20435
|
+
"name": "--ui-border-width"
|
|
20436
|
+
},
|
|
20437
|
+
{
|
|
20438
|
+
"name": "--ui-radius-md"
|
|
20439
|
+
},
|
|
20440
|
+
{
|
|
20441
|
+
"name": "--ui-radius-round"
|
|
20442
|
+
},
|
|
20443
|
+
{
|
|
20444
|
+
"name": "--ui-radius-sm"
|
|
20445
|
+
},
|
|
20446
|
+
{
|
|
20447
|
+
"name": "--ui-text-primary"
|
|
20448
|
+
}
|
|
20449
|
+
]
|
|
20450
|
+
},
|
|
20451
|
+
"attributes": [
|
|
20452
|
+
{
|
|
20453
|
+
"name": "animated",
|
|
20454
|
+
"property": "animated",
|
|
20455
|
+
"description": "Gently pulses the placeholder. Off by default, and always still when the reader requests reduced motion.",
|
|
20456
|
+
"type": "boolean",
|
|
20457
|
+
"default": false
|
|
20458
|
+
},
|
|
20459
|
+
{
|
|
20460
|
+
"name": "height",
|
|
20461
|
+
"property": "height",
|
|
20462
|
+
"description": "Block size as a CSS length. Empty reserves one line for text, a square for circles, or 8rem for rectangles. Set only width to keep a circle square.",
|
|
20463
|
+
"type": "string",
|
|
20464
|
+
"default": ""
|
|
20465
|
+
},
|
|
20466
|
+
{
|
|
20467
|
+
"name": "shape",
|
|
20468
|
+
"property": "shape",
|
|
20469
|
+
"description": "The content's silhouette. text reserves one inherited line box with a shorter bar inside it; circle matches a medium Avatar; rectangle reserves a media area.",
|
|
20470
|
+
"type": "keyword",
|
|
20471
|
+
"default": "text",
|
|
20472
|
+
"options": [
|
|
20473
|
+
"text",
|
|
20474
|
+
"circle",
|
|
20475
|
+
"rectangle"
|
|
20476
|
+
]
|
|
20477
|
+
},
|
|
20478
|
+
{
|
|
20479
|
+
"name": "width",
|
|
20480
|
+
"property": "width",
|
|
20481
|
+
"description": "Inline size as a CSS length or percentage, constrained to the container. Empty fills the available width for text and rectangles, or matches a medium Avatar's outer size for circles.",
|
|
20482
|
+
"type": "string",
|
|
20483
|
+
"default": ""
|
|
20484
|
+
}
|
|
20485
|
+
],
|
|
20486
|
+
"properties": [
|
|
20487
|
+
{
|
|
20488
|
+
"name": "animated",
|
|
20489
|
+
"description": "Gently pulses the placeholder. Off by default, and always still when the reader requests reduced motion.",
|
|
20490
|
+
"type": "boolean",
|
|
20491
|
+
"default": false
|
|
20492
|
+
},
|
|
20493
|
+
{
|
|
20494
|
+
"name": "height",
|
|
20495
|
+
"description": "Block size as a CSS length. Empty reserves one line for text, a square for circles, or 8rem for rectangles. Set only width to keep a circle square.",
|
|
20496
|
+
"type": "string",
|
|
20497
|
+
"default": ""
|
|
20498
|
+
},
|
|
20499
|
+
{
|
|
20500
|
+
"name": "shape",
|
|
20501
|
+
"description": "The content's silhouette. text reserves one inherited line box with a shorter bar inside it; circle matches a medium Avatar; rectangle reserves a media area.",
|
|
20502
|
+
"type": "\"text\" | \"circle\" | \"rectangle\"",
|
|
20503
|
+
"default": "text",
|
|
20504
|
+
"options": [
|
|
20505
|
+
"text",
|
|
20506
|
+
"circle",
|
|
20507
|
+
"rectangle"
|
|
20508
|
+
]
|
|
20509
|
+
},
|
|
20510
|
+
{
|
|
20511
|
+
"name": "width",
|
|
20512
|
+
"description": "Inline size as a CSS length or percentage, constrained to the container. Empty fills the available width for text and rectangles, or matches a medium Avatar's outer size for circles.",
|
|
20513
|
+
"type": "string",
|
|
20514
|
+
"default": ""
|
|
20515
|
+
}
|
|
20516
|
+
],
|
|
20517
|
+
"events": [],
|
|
20518
|
+
"slots": [],
|
|
20519
|
+
"group": "display",
|
|
20520
|
+
"id": "@threadlabs/looma/ui-skeleton",
|
|
20521
|
+
"title": "Skeleton",
|
|
20522
|
+
"resource": "@threadlabs/looma/components/ui-skeleton/ui-skeleton.html",
|
|
20523
|
+
"targets": [
|
|
20524
|
+
"html-next",
|
|
20525
|
+
"vue"
|
|
20526
|
+
],
|
|
20527
|
+
"examples": [
|
|
20528
|
+
{
|
|
20529
|
+
"id": "@threadlabs/looma/ui-skeleton/01-text",
|
|
20530
|
+
"name": "01-text",
|
|
20531
|
+
"title": "Text lines",
|
|
20532
|
+
"description": "Each placeholder reserves a whole text line. Vary the widths to suggest the content without pretending to know its words.",
|
|
20533
|
+
"markup": "<ui-stack gap=\"none\">\n <ui-skeleton></ui-skeleton>\n <ui-skeleton width=\"85%\"></ui-skeleton>\n <ui-skeleton width=\"60%\"></ui-skeleton>\n</ui-stack>",
|
|
20534
|
+
"resizable": false,
|
|
20535
|
+
"bindings": "",
|
|
20536
|
+
"frameworks": {}
|
|
20537
|
+
},
|
|
20538
|
+
{
|
|
20539
|
+
"id": "@threadlabs/looma/ui-skeleton/02-shapes",
|
|
20540
|
+
"name": "02-shapes",
|
|
20541
|
+
"title": "Avatar and media shapes",
|
|
20542
|
+
"description": "The default circle matches a medium Avatar. Supply dimensions for a thumbnail or a larger media area; narrow containers constrain the width.",
|
|
20543
|
+
"markup": "<ui-stack gap=\"m\">\n <ui-cluster gap=\"m\">\n <ui-skeleton shape=\"circle\"></ui-skeleton>\n <ui-skeleton shape=\"circle\" width=\"1.75rem\"></ui-skeleton>\n <ui-skeleton shape=\"rectangle\" width=\"6rem\" height=\"2.5rem\"></ui-skeleton>\n </ui-cluster>\n <ui-skeleton shape=\"rectangle\"></ui-skeleton>\n</ui-stack>",
|
|
20544
|
+
"resizable": false,
|
|
20545
|
+
"bindings": "",
|
|
20546
|
+
"frameworks": {}
|
|
20547
|
+
},
|
|
20548
|
+
{
|
|
20549
|
+
"id": "@threadlabs/looma/ui-skeleton/03-loading-card",
|
|
20550
|
+
"name": "03-loading-card",
|
|
20551
|
+
"title": "Loading a profile",
|
|
20552
|
+
"description": "One status describes the work; the section stays busy until the application replaces its decorative placeholders. Compare the loaded card's avatar and text metrics.",
|
|
20553
|
+
"markup": "<ui-stack gap=\"m\">\n <ui-status-message kind=\"loading\">Loading a profile…</ui-status-message>\n <ui-grid>\n <ui-card as=\"section\" aria-label=\"Profile\" aria-busy=\"true\">\n <ui-cluster gap=\"s\">\n <ui-skeleton shape=\"circle\"></ui-skeleton>\n <ui-stack fill gap=\"none\">\n <ui-skeleton width=\"65%\"></ui-skeleton>\n <ui-skeleton width=\"45%\"></ui-skeleton>\n </ui-stack>\n </ui-cluster>\n <ui-stack gap=\"none\">\n <ui-skeleton></ui-skeleton>\n <ui-skeleton width=\"80%\"></ui-skeleton>\n </ui-stack>\n </ui-card>\n <ui-card as=\"section\" aria-label=\"Loaded profile\">\n <ui-cluster gap=\"s\">\n <ui-avatar name=\"Ada Lovelace\" decorative></ui-avatar>\n <ui-stack fill gap=\"none\">\n <ui-text weight=\"semibold\">Ada Lovelace</ui-text>\n <ui-text tone=\"secondary\">Mathematician</ui-text>\n </ui-stack>\n </ui-cluster>\n <ui-text>Notes on an analytical engine and the possibilities of computation.</ui-text>\n </ui-card>\n </ui-grid>\n</ui-stack>",
|
|
20554
|
+
"resizable": false,
|
|
20555
|
+
"bindings": "",
|
|
20556
|
+
"frameworks": {}
|
|
20557
|
+
},
|
|
20558
|
+
{
|
|
20559
|
+
"id": "@threadlabs/looma/ui-skeleton/04-animation",
|
|
20560
|
+
"name": "04-animation",
|
|
20561
|
+
"title": "Optional motion",
|
|
20562
|
+
"description": "Still by default. Opt in to a gentle pulse when it helps convey loading; reduced-motion preferences always keep it still.",
|
|
20563
|
+
"markup": "<ui-stack gap=\"s\">\n <ui-text tone=\"secondary\" size=\"sm\">Still</ui-text>\n <ui-skeleton width=\"75%\"></ui-skeleton>\n <ui-text tone=\"secondary\" size=\"sm\">Animated when motion is welcome</ui-text>\n <ui-skeleton animated width=\"75%\"></ui-skeleton>\n</ui-stack>",
|
|
20564
|
+
"resizable": false,
|
|
20565
|
+
"bindings": "",
|
|
20566
|
+
"frameworks": {}
|
|
20567
|
+
}
|
|
20568
|
+
]
|
|
20569
|
+
},
|
|
20402
20570
|
{
|
|
20403
20571
|
"tag": "ui-slider",
|
|
20404
20572
|
"package": "@threadlabs/looma",
|
package/package.json
CHANGED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Generated by HTML Next 1.0.0-alpha.69. Do not edit. */
|
|
2
|
+
@supports (-moz-appearance: none) {
|
|
3
|
+
:where([data-component], [data-slotted]) { --html-next-scope-owner: boundary; }
|
|
4
|
+
:where([data-slotted]:not([data-component])) { --html-next-scope-owner: projected !important; }
|
|
5
|
+
}
|
|
6
|
+
@supports (-moz-appearance: none) {
|
|
7
|
+
[data-component~="ui-skeleton"] { --html-next-scope-owner: "ui-skeleton" !important; }
|
|
8
|
+
}
|
|
9
|
+
@scope ([data-component~="ui-skeleton"]) to ([data-component], [data-slotted]) {
|
|
10
|
+
/* Dimensions belong to this placeholder, never a nested or neighbouring one. */
|
|
11
|
+
@property --ui-skeleton-width {
|
|
12
|
+
syntax: "*";
|
|
13
|
+
inherits: false;
|
|
14
|
+
}
|
|
15
|
+
@property --ui-skeleton-height {
|
|
16
|
+
syntax: "*";
|
|
17
|
+
inherits: false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
:scope:where([data-component~="ui-skeleton"]) {
|
|
21
|
+
display: block !important;
|
|
22
|
+
position: relative;
|
|
23
|
+
box-sizing: border-box !important;
|
|
24
|
+
flex: none;
|
|
25
|
+
min-inline-size: 0;
|
|
26
|
+
max-inline-size: 100%;
|
|
27
|
+
inline-size: var(--ui-skeleton-width, 100%);
|
|
28
|
+
block-size: var(--ui-skeleton-height, 1lh);
|
|
29
|
+
margin: 0 !important;
|
|
30
|
+
font: inherit;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
:scope:where([data-component~="ui-skeleton"])::before {
|
|
34
|
+
content: "";
|
|
35
|
+
position: absolute;
|
|
36
|
+
inset: 0;
|
|
37
|
+
border-radius: var(--ui-radius-sm);
|
|
38
|
+
background: color-mix(in srgb, var(--ui-text-primary) 16%, transparent);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="shape=text"]::before {
|
|
42
|
+
inset-block: 22%;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="shape=circle"] {
|
|
46
|
+
inline-size: var(--ui-skeleton-width, calc(2.5rem + 2 * var(--ui-border-width)));
|
|
47
|
+
block-size: var(--ui-skeleton-height, auto);
|
|
48
|
+
aspect-ratio: 1;
|
|
49
|
+
}
|
|
50
|
+
:scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="shape=circle"]::before {
|
|
51
|
+
border-radius: var(--ui-radius-round);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
:scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="shape=rectangle"] {
|
|
55
|
+
block-size: var(--ui-skeleton-height, 8rem);
|
|
56
|
+
}
|
|
57
|
+
:scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="shape=rectangle"]::before {
|
|
58
|
+
border-radius: var(--ui-radius-md);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="animated"]::before {
|
|
62
|
+
animation: ui-skeleton-pulse 1.6s ease-in-out infinite;
|
|
63
|
+
}
|
|
64
|
+
@keyframes ui-skeleton-pulse {
|
|
65
|
+
50% {
|
|
66
|
+
opacity: 0.45;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
@media (prefers-reduced-motion: reduce) {
|
|
70
|
+
:scope:where([data-component~="ui-skeleton"])[data-ui-skeleton-state~="animated"]::before {
|
|
71
|
+
animation: none;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
@media (forced-colors: active) {
|
|
75
|
+
:scope:where([data-component~="ui-skeleton"])::before {
|
|
76
|
+
background: CanvasText;
|
|
77
|
+
opacity: 0.2;
|
|
78
|
+
animation: none;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
:scope:where([data-component~="ui-skeleton"])[hidden] {
|
|
82
|
+
display: none !important;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
@scope ([data-component~="ui-skeleton"]) to ([data-component]) {
|
|
86
|
+
/* Dimensions belong to this placeholder, never a nested or neighbouring one. */
|
|
87
|
+
@media (prefers-reduced-motion: reduce) {
|
|
88
|
+
}
|
|
89
|
+
@media (forced-colors: active) {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Generated by HTML Next 1.0.0-alpha.69 for Vanilla DOM. Do not edit.
|
|
2
|
+
export interface UiSkeletonEventMap {
|
|
3
|
+
}
|
|
4
|
+
export interface UiSkeletonElement extends HTMLSpanElement {
|
|
5
|
+
addEventListener<K extends keyof UiSkeletonEventMap>(type: K, listener: (this: UiSkeletonElement, event: UiSkeletonEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
6
|
+
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: UiSkeletonElement, event: HTMLElementEventMap[K]) => unknown, options?: boolean | AddEventListenerOptions): void;
|
|
7
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
|
8
|
+
removeEventListener<K extends keyof UiSkeletonEventMap>(type: K, listener: (this: UiSkeletonElement, event: UiSkeletonEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
|
|
9
|
+
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: UiSkeletonElement, event: HTMLElementEventMap[K]) => unknown, options?: boolean | EventListenerOptions): void;
|
|
10
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
|
11
|
+
}
|
|
12
|
+
export interface UiSkeletonProps {
|
|
13
|
+
animated?: boolean | null;
|
|
14
|
+
height?: string | null;
|
|
15
|
+
shape?: "text" | "circle" | "rectangle" | null;
|
|
16
|
+
width?: string | null;
|
|
17
|
+
attributes?: Readonly<Record<string, string | number | boolean | null | undefined>>;
|
|
18
|
+
children?: readonly (string | Node)[];
|
|
19
|
+
slots?: Readonly<Record<string, readonly (string | Node)[]>>;
|
|
20
|
+
}
|
|
21
|
+
export declare function createUiSkeleton(props?: UiSkeletonProps): UiSkeletonElement;
|
|
22
|
+
declare global {
|
|
23
|
+
interface ComponentTagMap {
|
|
24
|
+
"ui-skeleton": { element: UiSkeletonElement; props: Omit<UiSkeletonProps, "attributes" | "children" | "slots"> };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Generated by HTML Next 1.0.0-alpha.69 for Vanilla DOM. Do not edit.
|
|
2
|
+
import { attachGeneratedController, toText, writeAttribute, formatOf, keywordFormat, hostState, acceptProps, manageProps, generatedInputs, checkBoolean, checkFormat, checkString, defineComponent } from "@nextwebwg/html-next/generated-runtime";
|
|
3
|
+
import "../styles/ui-skeleton.css";
|
|
4
|
+
|
|
5
|
+
const S = { n: [], t: [], f: import.meta.url, g: "ui-skeleton", b: generatedInputs };
|
|
6
|
+
const D = { g: "ui-skeleton", props: { "animated": { "required":false,"default":false, c: checkBoolean, t: "boolean", j: 0, m: "b" }, "height": { "required":false,"default":"", c: checkString, t: "string", j: 0, m: "s" }, "shape": { "required":false,"values":["text","circle","rectangle"],"default":"text", c: checkFormat("keyword", keywordFormat), t: "keyword", j: 0, m: ["p",formatOf(keywordFormat)] }, "width": { "required":false,"default":"", c: checkString, t: "string", j: 0, m: "s" } } };
|
|
7
|
+
|
|
8
|
+
export function createUiSkeleton(options = {}, html) {
|
|
9
|
+
const { attributes = {}, children = [], slots = {}, ...componentProps } = options;
|
|
10
|
+
const element = document.createElement("span");
|
|
11
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
12
|
+
if (value === null || value === undefined || value === false) continue;
|
|
13
|
+
element.setAttribute(name, value === true ? "" : String(value));
|
|
14
|
+
}
|
|
15
|
+
if (!element.hasAttribute("aria-hidden")) element.setAttribute("aria-hidden", "true");
|
|
16
|
+
element.setAttribute("data-component", "ui-skeleton");
|
|
17
|
+
const v = [null, null, null, null];
|
|
18
|
+
const B = acceptProps(element, D, S.n, v, componentProps, [], html);
|
|
19
|
+
const p0 = (r, c, d) => {
|
|
20
|
+
if (c & 8) {
|
|
21
|
+
const x0 = v[3];
|
|
22
|
+
const s0 = toText(x0);
|
|
23
|
+
if (s0 !== r.v0) r.n.style.setProperty("--ui-skeleton-width", r.v0 = s0);
|
|
24
|
+
}
|
|
25
|
+
if (c & 2) {
|
|
26
|
+
const x1 = v[1];
|
|
27
|
+
const s1 = toText(x1);
|
|
28
|
+
if (s1 !== r.v1) r.n.style.setProperty("--ui-skeleton-height", r.v1 = s1);
|
|
29
|
+
}
|
|
30
|
+
if (c & 5) {
|
|
31
|
+
const x2 = hostState(["shape","animated"], [v[2], v[0]]);
|
|
32
|
+
const s2 = x2;
|
|
33
|
+
if (s2 !== r.v2) writeAttribute(r.n, "data-ui-skeleton-state", (r.v2 = s2) === "" ? null : s2);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const R = { n: element, v0: undefined, v1: undefined, v2: undefined };
|
|
37
|
+
const I = { B };
|
|
38
|
+
attachGeneratedController(element, S, v, ((c, d) => p0(R, c, d)), undefined, undefined, I);
|
|
39
|
+
manageProps(I);
|
|
40
|
+
return element;
|
|
41
|
+
}
|
|
42
|
+
defineComponent("ui-skeleton", createUiSkeleton, ["animated","height","shape","width"]);
|
package/vanilla/index.js
CHANGED
|
@@ -71,6 +71,7 @@ export * from "./UiSection.js";
|
|
|
71
71
|
export * from "./UiSelect.js";
|
|
72
72
|
export * from "./UiSeparator.js";
|
|
73
73
|
export * from "./UiSidebar.js";
|
|
74
|
+
export * from "./UiSkeleton.js";
|
|
74
75
|
export * from "./UiSlider.js";
|
|
75
76
|
export * from "./UiSpinner.js";
|
|
76
77
|
export * from "./UiStack.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PropType } from 'vue';
|
|
2
|
+
import { type InputBindings } from './props.js';
|
|
3
|
+
declare const __VLS_export: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
|
+
animated: {
|
|
5
|
+
type: PropType<boolean | null>;
|
|
6
|
+
};
|
|
7
|
+
height: {
|
|
8
|
+
type: PropType<string | null>;
|
|
9
|
+
};
|
|
10
|
+
shape: {
|
|
11
|
+
type: PropType<"text" | "circle" | "rectangle" | null>;
|
|
12
|
+
};
|
|
13
|
+
width: {
|
|
14
|
+
type: PropType<string | null>;
|
|
15
|
+
};
|
|
16
|
+
"\0htmlNextInputStatus": {
|
|
17
|
+
type: PropType<InputBindings>;
|
|
18
|
+
};
|
|
19
|
+
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
20
|
+
animated: {
|
|
21
|
+
type: PropType<boolean | null>;
|
|
22
|
+
};
|
|
23
|
+
height: {
|
|
24
|
+
type: PropType<string | null>;
|
|
25
|
+
};
|
|
26
|
+
shape: {
|
|
27
|
+
type: PropType<"text" | "circle" | "rectangle" | null>;
|
|
28
|
+
};
|
|
29
|
+
width: {
|
|
30
|
+
type: PropType<string | null>;
|
|
31
|
+
};
|
|
32
|
+
"\0htmlNextInputStatus": {
|
|
33
|
+
type: PropType<InputBindings>;
|
|
34
|
+
};
|
|
35
|
+
}>> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
36
|
+
declare const _default: typeof __VLS_export;
|
|
37
|
+
export default _default;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
<!-- Generated by HTML Next 1.0.0-alpha.69 for Vue 3.5. Do not edit. -->
|
|
2
|
+
<script setup lang="ts">
|
|
3
|
+
import { computed, watchSyncEffect } from 'vue'
|
|
4
|
+
import type { PropType } from 'vue'
|
|
5
|
+
import { checkHydratedRoot, nativeAttrs, useReflectedProp } from './host'
|
|
6
|
+
import {
|
|
7
|
+
BINDING_INPUTS_PROP,
|
|
8
|
+
checkedProp,
|
|
9
|
+
checkPropInput,
|
|
10
|
+
type InputBindings,
|
|
11
|
+
READY_INPUT,
|
|
12
|
+
requireInputStatus,
|
|
13
|
+
UNRESOLVED_INPUT,
|
|
14
|
+
vPropValidity,
|
|
15
|
+
vuePropInputs,
|
|
16
|
+
} from './props'
|
|
17
|
+
|
|
18
|
+
defineOptions({ inheritAttrs: false })
|
|
19
|
+
|
|
20
|
+
const propValidityContract = {
|
|
21
|
+
props: {
|
|
22
|
+
animated: {
|
|
23
|
+
type: { kind: 'terminal', name: 'boolean' },
|
|
24
|
+
required: false,
|
|
25
|
+
target: { attribute: 'animated' },
|
|
26
|
+
description:
|
|
27
|
+
'Gently pulses the placeholder. Off by default, and always still when the reader requests reduced motion.',
|
|
28
|
+
default: false,
|
|
29
|
+
},
|
|
30
|
+
height: {
|
|
31
|
+
type: { kind: 'terminal', name: 'string' },
|
|
32
|
+
required: false,
|
|
33
|
+
target: { attribute: 'height' },
|
|
34
|
+
description:
|
|
35
|
+
'Block size as a CSS length. Empty reserves one line for text, a square for circles, or 8rem for rectangles. Set only width to keep a circle square.',
|
|
36
|
+
default: '',
|
|
37
|
+
},
|
|
38
|
+
shape: {
|
|
39
|
+
type: { kind: 'terminal', name: 'keyword' },
|
|
40
|
+
required: false,
|
|
41
|
+
target: { attribute: 'shape' },
|
|
42
|
+
description:
|
|
43
|
+
'The content\'s silhouette. text reserves one inherited line box with a shorter bar inside it; circle matches a medium Avatar; rectangle reserves a media\n area.',
|
|
44
|
+
values: ['text', 'circle', 'rectangle'],
|
|
45
|
+
default: 'text',
|
|
46
|
+
},
|
|
47
|
+
width: {
|
|
48
|
+
type: { kind: 'terminal', name: 'string' },
|
|
49
|
+
required: false,
|
|
50
|
+
target: { attribute: 'width' },
|
|
51
|
+
description:
|
|
52
|
+
'Inline size as a CSS length or percentage, constrained to the container. Empty fills the available width for text and rectangles, or matches a medium\n Avatar\'s outer size for circles.',
|
|
53
|
+
default: '',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
} as const
|
|
57
|
+
const props = defineProps({
|
|
58
|
+
animated: { type: null as unknown as PropType<boolean | null> },
|
|
59
|
+
height: { type: null as unknown as PropType<string | null> },
|
|
60
|
+
shape: { type: null as unknown as PropType<'text' | 'circle' | 'rectangle' | null> },
|
|
61
|
+
width: { type: null as unknown as PropType<string | null> },
|
|
62
|
+
[BINDING_INPUTS_PROP]: { type: null as unknown as PropType<InputBindings> },
|
|
63
|
+
})
|
|
64
|
+
const acceptedProps: Record<string, unknown> = {
|
|
65
|
+
animated: false,
|
|
66
|
+
height: '',
|
|
67
|
+
shape: 'text',
|
|
68
|
+
width: '',
|
|
69
|
+
}
|
|
70
|
+
const inputTracking = vuePropInputs(acceptedProps, {
|
|
71
|
+
animated: false,
|
|
72
|
+
height: '',
|
|
73
|
+
shape: 'text',
|
|
74
|
+
width: '',
|
|
75
|
+
}, (name, value, source, values) => {
|
|
76
|
+
switch (name) {
|
|
77
|
+
case 'animated':
|
|
78
|
+
return checkPropInput(value, { kind: 'terminal', name: 'boolean' }, true, true)
|
|
79
|
+
case 'height':
|
|
80
|
+
return checkPropInput(value, { kind: 'terminal', name: 'string' }, true, true)
|
|
81
|
+
case 'shape':
|
|
82
|
+
return checkPropInput(value, { kind: 'terminal', name: 'keyword' }, true, true)
|
|
83
|
+
case 'width':
|
|
84
|
+
return checkPropInput(value, { kind: 'terminal', name: 'string' }, true, true)
|
|
85
|
+
default:
|
|
86
|
+
return { ok: false }
|
|
87
|
+
}
|
|
88
|
+
}, () => {
|
|
89
|
+
void checkedProps.value
|
|
90
|
+
})
|
|
91
|
+
const propInputs = inputTracking.inputs
|
|
92
|
+
const checkedProps = computed(() => {
|
|
93
|
+
void inputTracking.version.value
|
|
94
|
+
return ({
|
|
95
|
+
animated: propInputs.value<boolean | null>(
|
|
96
|
+
'animated',
|
|
97
|
+
props['animated'],
|
|
98
|
+
props[BINDING_INPUTS_PROP],
|
|
99
|
+
) as boolean,
|
|
100
|
+
height: propInputs.value<string | null>(
|
|
101
|
+
'height',
|
|
102
|
+
props['height'],
|
|
103
|
+
props[BINDING_INPUTS_PROP],
|
|
104
|
+
) as string,
|
|
105
|
+
shape: propInputs.value<'text' | 'circle' | 'rectangle' | null>(
|
|
106
|
+
'shape',
|
|
107
|
+
props['shape'],
|
|
108
|
+
props[BINDING_INPUTS_PROP],
|
|
109
|
+
) as 'text' | 'circle' | 'rectangle',
|
|
110
|
+
width: propInputs.value<string | null>(
|
|
111
|
+
'width',
|
|
112
|
+
props['width'],
|
|
113
|
+
props[BINDING_INPUTS_PROP],
|
|
114
|
+
) as string,
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
const inputStatus = (name: string, handlesError: boolean) => {
|
|
118
|
+
void checkedProps.value
|
|
119
|
+
void inputTracking.version.value
|
|
120
|
+
return requireInputStatus(propInputs.status(name), handlesError)
|
|
121
|
+
}
|
|
122
|
+
void checkedProps.value
|
|
123
|
+
watchSyncEffect(() => {
|
|
124
|
+
void checkedProps.value
|
|
125
|
+
})
|
|
126
|
+
const reflectedProp = useReflectedProp()
|
|
127
|
+
|
|
128
|
+
checkHydratedRoot('span', 'ui-skeleton')
|
|
129
|
+
|
|
130
|
+
/** The values the styles' :host() and :state() rules test. */
|
|
131
|
+
const hostState = computed(() =>
|
|
132
|
+
[
|
|
133
|
+
checkedProps.value.shape && 'shape',
|
|
134
|
+
checkedProps.value.shape != null && `shape=${encodeURIComponent(checkedProps.value.shape)}`,
|
|
135
|
+
checkedProps.value.animated && 'animated',
|
|
136
|
+
].filter(Boolean).join(' ')
|
|
137
|
+
)
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<template>
|
|
141
|
+
<span
|
|
142
|
+
class="ui-skeleton"
|
|
143
|
+
aria-hidden="true"
|
|
144
|
+
v-bind="nativeAttrs($attrs)"
|
|
145
|
+
data-hn
|
|
146
|
+
:style="{
|
|
147
|
+
'--ui-skeleton-width': checkedProps.width ?? undefined,
|
|
148
|
+
'--ui-skeleton-height': checkedProps.height ?? undefined,
|
|
149
|
+
}"
|
|
150
|
+
v-prop-validity="{ contract: propValidityContract, values: { ...props } }"
|
|
151
|
+
:data-ui-skeleton-prop-animated="reflectedProp('animated', 'animated', checkedProps.animated, undefined, false)"
|
|
152
|
+
:data-ui-skeleton-prop-height="reflectedProp('height', 'height', checkedProps.height, undefined, false)"
|
|
153
|
+
:data-ui-skeleton-prop-shape="reflectedProp('shape', 'shape', checkedProps.shape, undefined, false)"
|
|
154
|
+
:data-ui-skeleton-prop-width="reflectedProp('width', 'width', checkedProps.width, undefined, false)"
|
|
155
|
+
:data-ui-skeleton-state="hostState || undefined"
|
|
156
|
+
></span>
|
|
157
|
+
</template>
|
|
158
|
+
|
|
159
|
+
<style scoped>
|
|
160
|
+
/* Dimensions belong to this placeholder, never a nested or neighbouring one. */
|
|
161
|
+
@property --ui-skeleton-width {
|
|
162
|
+
syntax: "*";
|
|
163
|
+
inherits: false;
|
|
164
|
+
}
|
|
165
|
+
@property --ui-skeleton-height {
|
|
166
|
+
syntax: "*";
|
|
167
|
+
inherits: false;
|
|
168
|
+
}
|
|
169
|
+
@keyframes ui-skeleton-pulse {
|
|
170
|
+
50% {
|
|
171
|
+
opacity: 0.45;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
@scope (.ui-skeleton) to ([data-hn]) {
|
|
175
|
+
:global(:scope) {
|
|
176
|
+
display: block !important;
|
|
177
|
+
position: relative;
|
|
178
|
+
box-sizing: border-box !important;
|
|
179
|
+
flex: none;
|
|
180
|
+
min-inline-size: 0;
|
|
181
|
+
max-inline-size: 100%;
|
|
182
|
+
inline-size: var(--ui-skeleton-width, 100%);
|
|
183
|
+
block-size: var(--ui-skeleton-height, 1lh);
|
|
184
|
+
margin: 0 !important;
|
|
185
|
+
font: inherit;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
:global(:scope::before) {
|
|
189
|
+
content: "";
|
|
190
|
+
position: absolute;
|
|
191
|
+
inset: 0;
|
|
192
|
+
border-radius: var(--ui-radius-sm);
|
|
193
|
+
background: color-mix(in srgb, var(--ui-text-primary) 16%, transparent);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
:global(:scope[data-ui-skeleton-state~="shape=text"]::before) {
|
|
197
|
+
inset-block: 22%;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
:global(:scope[data-ui-skeleton-state~="shape=circle"]) {
|
|
201
|
+
inline-size: var(--ui-skeleton-width, calc(2.5rem + 2 * var(--ui-border-width)));
|
|
202
|
+
block-size: var(--ui-skeleton-height, auto);
|
|
203
|
+
aspect-ratio: 1;
|
|
204
|
+
}
|
|
205
|
+
:global(:scope[data-ui-skeleton-state~="shape=circle"]::before) {
|
|
206
|
+
border-radius: var(--ui-radius-round);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
:global(:scope[data-ui-skeleton-state~="shape=rectangle"]) {
|
|
210
|
+
block-size: var(--ui-skeleton-height, 8rem);
|
|
211
|
+
}
|
|
212
|
+
:global(:scope[data-ui-skeleton-state~="shape=rectangle"]::before) {
|
|
213
|
+
border-radius: var(--ui-radius-md);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
:global(:scope[data-ui-skeleton-state~="animated"]::before) {
|
|
217
|
+
animation: ui-skeleton-pulse 1.6s ease-in-out infinite;
|
|
218
|
+
}
|
|
219
|
+
@media (prefers-reduced-motion: reduce) {
|
|
220
|
+
:global(:scope[data-ui-skeleton-state~="animated"]::before) {
|
|
221
|
+
animation: none;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
@media (forced-colors: active) {
|
|
225
|
+
:global(:scope::before) {
|
|
226
|
+
background: CanvasText;
|
|
227
|
+
opacity: 0.2;
|
|
228
|
+
animation: none;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
:global(:scope[hidden]) {
|
|
232
|
+
display: none !important;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
</style>
|