@camp.dev/bones 0.4.0 → 0.5.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.
@@ -1,184 +0,0 @@
1
- import { measureBones } from "./measure.mjs";
2
- //#region src/element/overlay.ts
3
- const OVERLAY_CSS = `
4
- :host([precision="measured"]) {
5
- display: block;
6
- position: relative;
7
- }
8
- /* Hidden content still lays out, so re-measurement stays valid. Deliberately
9
- not !important: outer-tree rules beat ::slotted, which is what lets the
10
- auto.css opt-out rule re-show exempt subtrees (and lets an author
11
- visibility rule on a direct child win — a documented edge). */
12
- :host([data-bones-measured]) ::slotted(*) {
13
- visibility: hidden;
14
- }
15
- [part~="overlay"] {
16
- position: absolute;
17
- inset: 0;
18
- pointer-events: none;
19
- }
20
- [part~="bone"] {
21
- position: absolute;
22
- background: var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent));
23
- border-radius: var(--bone-radius, 4px);
24
- }
25
- @keyframes bone-shimmer {
26
- 0% { background-position: 200% 0; }
27
- 100% { background-position: -200% 0; }
28
- }
29
- @keyframes bone-pulse {
30
- 0%, 100% { opacity: 1; }
31
- 50% { opacity: 0.5; }
32
- }
33
- [part~="overlay"]:not([data-bone-animate]) [part~="bone"],
34
- [part~="overlay"][data-bone-animate="shimmer"] [part~="bone"] {
35
- animation: bone-shimmer var(--bone-duration, 1.5s) ease-in-out infinite;
36
- background: linear-gradient(
37
- 90deg,
38
- var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent)) 25%,
39
- var(--bone-highlight, color-mix(in srgb, rgb(from currentcolor r g b / 1) 6%, transparent)) 50%,
40
- var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent)) 75%
41
- );
42
- background-size: 200% 100%;
43
- }
44
- [part~="overlay"][data-bone-animate="pulse"] [part~="bone"] {
45
- animation: bone-pulse var(--bone-duration, 1.5s) ease-in-out infinite;
46
- }
47
- [part~="overlay"][data-bone-animate="none"] [part~="bone"] {
48
- animation: none;
49
- }
50
- /* Matches the shimmer/pulse selectors above at equal (0,3,0) specificity so
51
- this override always wins the cascade instead of losing to source order.
52
- data-bone-animate="none" is deliberately excluded: none still means none. */
53
- @media (prefers-reduced-motion: reduce) {
54
- [part~="overlay"]:not([data-bone-animate]) [part~="bone"],
55
- [part~="overlay"][data-bone-animate="shimmer"] [part~="bone"],
56
- [part~="overlay"][data-bone-animate="pulse"] [part~="bone"] {
57
- animation: bone-pulse 2s ease-in-out infinite;
58
- background: var(--bone-base, color-mix(in srgb, rgb(from currentcolor r g b / 1) 12%, transparent));
59
- background-size: auto;
60
- }
61
- }
62
- `;
63
- let sharedSheet;
64
- function applyStyles(root) {
65
- if ("adoptedStyleSheets" in root && typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype) {
66
- if (sharedSheet === void 0) {
67
- sharedSheet = new CSSStyleSheet();
68
- sharedSheet.replaceSync(OVERLAY_CSS);
69
- }
70
- root.adoptedStyleSheets = [...root.adoptedStyleSheets, sharedSheet];
71
- return;
72
- }
73
- const style = root.ownerDocument.createElement("style");
74
- style.textContent = OVERLAY_CSS;
75
- root.append(style);
76
- }
77
- var MeasuredOverlay = class {
78
- #host;
79
- #container;
80
- #observer;
81
- #mutations;
82
- #active = false;
83
- #ownsAutoOff = false;
84
- constructor(host) {
85
- this.#host = host;
86
- }
87
- get active() {
88
- return this.#active;
89
- }
90
- prepare() {
91
- if (this.#host.shadowRoot) return;
92
- const root = this.#host.attachShadow({ mode: "open" });
93
- root.append(this.#host.ownerDocument.createElement("slot"));
94
- applyStyles(root);
95
- }
96
- activate() {
97
- this.prepare();
98
- if (!this.#active) {
99
- this.#active = true;
100
- if (!this.#host.hasAttribute("data-bones-auto")) {
101
- this.#host.setAttribute("data-bones-auto", "off");
102
- this.#ownsAutoOff = true;
103
- }
104
- this.#host.setAttribute("data-bones-measured", "");
105
- }
106
- if (!this.#renderBars()) {
107
- this.deactivate();
108
- return;
109
- }
110
- this.#observe();
111
- }
112
- deactivate() {
113
- this.#unobserve();
114
- this.#container?.remove();
115
- this.#container = void 0;
116
- if (!this.#active) return;
117
- this.#active = false;
118
- this.#host.removeAttribute("data-bones-measured");
119
- if (this.#ownsAutoOff) {
120
- this.#host.removeAttribute("data-bones-auto");
121
- this.#ownsAutoOff = false;
122
- }
123
- }
124
- pause() {
125
- this.#unobserve();
126
- }
127
- #observe() {
128
- if (typeof ResizeObserver !== "undefined" && !this.#observer) {
129
- this.#observer = new ResizeObserver(() => this.#invalidate());
130
- this.#observer.observe(this.#host);
131
- }
132
- if (typeof MutationObserver !== "undefined" && !this.#mutations) {
133
- this.#mutations = new MutationObserver(() => this.#invalidate());
134
- this.#mutations.observe(this.#host, {
135
- childList: true,
136
- subtree: true,
137
- characterData: true
138
- });
139
- }
140
- }
141
- #unobserve() {
142
- this.#observer?.disconnect();
143
- this.#observer = void 0;
144
- this.#mutations?.disconnect();
145
- this.#mutations = void 0;
146
- }
147
- #invalidate() {
148
- if (this.#active && !this.#renderBars()) this.deactivate();
149
- }
150
- #renderBars() {
151
- const root = this.#host.shadowRoot;
152
- if (!root) return false;
153
- const bones = measureBones(this.#host);
154
- if (bones.length === 0) return false;
155
- const doc = this.#host.ownerDocument;
156
- if (!this.#container) {
157
- this.#container = doc.createElement("div");
158
- this.#container.setAttribute("part", "overlay");
159
- this.#container.setAttribute("aria-hidden", "true");
160
- root.append(this.#container);
161
- }
162
- const animate = this.#host.closest("[data-bone-animate]")?.getAttribute("data-bone-animate");
163
- if (animate) this.#container.setAttribute("data-bone-animate", animate);
164
- else this.#container.removeAttribute("data-bone-animate");
165
- const origin = this.#container.getBoundingClientRect();
166
- const style = getComputedStyle(this.#container);
167
- const layoutWidth = Number.parseFloat(style.width);
168
- const layoutHeight = Number.parseFloat(style.height);
169
- const scaleX = layoutWidth > 0 ? origin.width / layoutWidth : 1;
170
- const scaleY = layoutHeight > 0 ? origin.height / layoutHeight : 1;
171
- this.#container.replaceChildren(...bones.map((bone) => {
172
- const bar = doc.createElement("div");
173
- bar.setAttribute("part", `bone bone-${bone.kind}`);
174
- bar.style.left = `${(bone.left - origin.left) / scaleX}px`;
175
- bar.style.top = `${(bone.top - origin.top) / scaleY}px`;
176
- bar.style.width = `${bone.width / scaleX}px`;
177
- bar.style.height = `${bone.height / scaleY}px`;
178
- return bar;
179
- }));
180
- return true;
181
- }
182
- };
183
- //#endregion
184
- export { MeasuredOverlay };
package/dist/index.d.mts DELETED
@@ -1,2 +0,0 @@
1
- import { BoneAttributes, BoneOptions, BoneType, MinMax, TRANSPARENT_PIXEL, boneAttributes, isMinMax, minMax, resolveLength } from "./core/attributes.mjs";
2
- export { type BoneAttributes, type BoneOptions, type BoneType, type MinMax, TRANSPARENT_PIXEL, boneAttributes, isMinMax, minMax, resolveLength };
package/dist/index.mjs DELETED
@@ -1,2 +0,0 @@
1
- import { TRANSPARENT_PIXEL, boneAttributes, isMinMax, minMax, resolveLength } from "./core/attributes.mjs";
2
- export { TRANSPARENT_PIXEL, boneAttributes, isMinMax, minMax, resolveLength };
@@ -1,40 +0,0 @@
1
- import { BonesBoundary as BonesBoundary$1 } from "../element/boundary.mjs";
2
- import { HTMLAttributes, ReactNode, Ref } from "react";
3
-
4
- //#region src/react/boundary.d.ts
5
- interface ElementAttributes extends HTMLAttributes<HTMLElement> {
6
- busy?: boolean;
7
- force?: boolean;
8
- delay?: number;
9
- "min-duration"?: number;
10
- transition?: "auto" | "none";
11
- precision?: "css" | "measured";
12
- class?: string;
13
- ref?: Ref<BonesBoundary$1>;
14
- [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
15
- }
16
- declare module "react" {
17
- namespace JSX {
18
- interface IntrinsicElements {
19
- "bones-boundary": ElementAttributes;
20
- }
21
- }
22
- }
23
- interface BonesBoundaryProps extends Omit<ElementAttributes, "min-duration" | "inert" | "aria-busy" | "class"> {
24
- minDuration?: number;
25
- onShow?: (event: CustomEvent) => void;
26
- onHide?: (event: CustomEvent) => void;
27
- }
28
- declare function BonesBoundary({
29
- busy,
30
- force,
31
- delay,
32
- minDuration,
33
- transition,
34
- onShow,
35
- onHide,
36
- children,
37
- ...rest
38
- }: BonesBoundaryProps): ReactNode;
39
- //#endregion
40
- export { BonesBoundary, BonesBoundaryProps };
@@ -1,21 +0,0 @@
1
- import { createElement } from "react";
2
- //#region src/react/boundary.ts
3
- function BonesBoundary({ busy, force, delay, minDuration, transition, onShow, onHide, children, ...rest }) {
4
- return createElement("bones-boundary", {
5
- ...rest,
6
- busy: busy ? true : void 0,
7
- force: force ? true : void 0,
8
- delay,
9
- "min-duration": minDuration,
10
- transition,
11
- ...force ? {
12
- "aria-busy": "true",
13
- inert: true
14
- } : {},
15
- suppressHydrationWarning: true,
16
- "onbones:show": onShow,
17
- "onbones:hide": onHide
18
- }, children);
19
- }
20
- //#endregion
21
- export { BonesBoundary };
@@ -1,23 +0,0 @@
1
- import { BoneOptions, BoneType, MinMax, isMinMax, minMax } from "../core/attributes.mjs";
2
- import { ReactNode } from "react";
3
-
4
- //#region src/react/create-bones.d.ts
5
- declare function readPromise<T>(promise: Promise<T>): T;
6
- type BoneProps = Record<string, unknown>;
7
- declare const forceBones: Promise<never>;
8
- interface CreateBonesOptions {
9
- loading?: boolean;
10
- }
11
- interface CreateBonesReturn<T> {
12
- bone: {
13
- (type: "text", options?: BoneOptions): BoneProps;
14
- (type: "block" | "container"): BoneProps;
15
- };
16
- data: T | null | undefined;
17
- repeat: <U>(arr: U[] | undefined | null, count: number, render: (item: U | undefined, index: number) => ReactNode) => ReactNode[];
18
- lines: <V>(value: V | null | undefined, count: number, render: (item: V | ReactNode) => ReactNode) => ReactNode[];
19
- }
20
- declare function createBones(options: CreateBonesOptions): CreateBonesReturn<never>;
21
- declare function createBones<T>(data: T | Promise<T> | undefined | null, options?: CreateBonesOptions): CreateBonesReturn<T>;
22
- //#endregion
23
- export { CreateBonesOptions, CreateBonesReturn, createBones, forceBones, readPromise };
@@ -1,69 +0,0 @@
1
- import { boneAttributes } from "../core/attributes.mjs";
2
- import { cloneElement, createElement, isValidElement } from "react";
3
- //#region src/react/create-bones.ts
4
- function withKey(node, key) {
5
- return isValidElement(node) ? cloneElement(node, { key }) : node;
6
- }
7
- function readPromise(promise) {
8
- const tracked = promise;
9
- if (tracked._status === void 0) {
10
- tracked._status = "pending";
11
- promise.then((result) => {
12
- tracked._status = "fulfilled";
13
- tracked._result = result;
14
- }, (error) => {
15
- tracked._status = "rejected";
16
- tracked._error = error;
17
- });
18
- }
19
- if (tracked._status === "fulfilled") return tracked._result;
20
- if (tracked._status === "rejected") throw tracked._error;
21
- throw promise;
22
- }
23
- const forceBones = Object.freeze({});
24
- function createBones(dataOrOptions, maybeOptions) {
25
- let data;
26
- let options;
27
- if (maybeOptions === void 0 && dataOrOptions !== null && dataOrOptions !== void 0 && typeof dataOrOptions === "object" && !(dataOrOptions instanceof Promise) && "loading" in dataOrOptions) {
28
- data = void 0;
29
- options = dataOrOptions;
30
- } else {
31
- data = dataOrOptions;
32
- options = maybeOptions;
33
- }
34
- let resolved;
35
- let isLoading = false;
36
- if (options?.loading) {
37
- isLoading = true;
38
- resolved = void 0;
39
- } else if (data != null && data === forceBones) {
40
- isLoading = true;
41
- resolved = void 0;
42
- } else if (data != null && data instanceof Promise) resolved = readPromise(data);
43
- else resolved = data;
44
- let boneCallIndex = 0;
45
- const bone = (type, options) => {
46
- if (!isLoading) return {};
47
- return { ...boneAttributes(type, options, boneCallIndex++) };
48
- };
49
- function repeat(arr, count, render) {
50
- return (isLoading ? Array.from({ length: count }) : arr ?? []).map(render);
51
- }
52
- function lines(value, count, render) {
53
- if (isLoading) return [withKey(render(Array.from({ length: count }, (_, i) => createElement("span", {
54
- key: i,
55
- "data-bone-line": true,
56
- ...bone("text")
57
- }))), 0)];
58
- if (value == null) return [];
59
- return [withKey(render(value), 0)];
60
- }
61
- return {
62
- bone,
63
- data: isLoading ? void 0 : resolved,
64
- repeat,
65
- lines
66
- };
67
- }
68
- //#endregion
69
- export { createBones, forceBones, readPromise };
@@ -1,4 +0,0 @@
1
- import { BoneOptions, BoneType, MinMax, isMinMax, minMax } from "../core/attributes.mjs";
2
- import { CreateBonesOptions, CreateBonesReturn, createBones, forceBones, readPromise } from "./create-bones.mjs";
3
- import { BonesBoundary, BonesBoundaryProps } from "./boundary.mjs";
4
- export { type BoneOptions, type BoneType, BonesBoundary, type BonesBoundaryProps, type CreateBonesOptions, type CreateBonesReturn, type MinMax, createBones, forceBones, isMinMax, minMax, readPromise };
@@ -1,4 +0,0 @@
1
- import { isMinMax, minMax } from "../core/attributes.mjs";
2
- import { createBones, forceBones, readPromise } from "./create-bones.mjs";
3
- import { BonesBoundary } from "./boundary.mjs";
4
- export { BonesBoundary, createBones, forceBones, isMinMax, minMax, readPromise };
@@ -1,4 +0,0 @@
1
- //#region src/server/bootstrap.d.ts
2
- declare const BOOTSTRAP_SCRIPT = "<script>function __bonesSwap(e,r){var t=document.querySelector('template[data-bones-chunk=\"'+e+'\"]'),n=document.querySelector('[data-bones-slot=\"'+e+'\"]');n?(t&&(n.replaceChildren(t.content),t.remove()),r&&n.setAttribute(\"data-bones-error\",\"\"),\"boolean\"==typeof n.busy?n.busy=!1:(n.removeAttribute(\"busy\"),n.removeAttribute(\"aria-busy\"),n.removeAttribute(\"inert\"))):t&&t.remove()}</script>";
3
- //#endregion
4
- export { BOOTSTRAP_SCRIPT };
@@ -1,3 +0,0 @@
1
- const BOOTSTRAP_SCRIPT = `<script>function __bonesSwap(e,r){var t=document.querySelector('template[data-bones-chunk="'+e+'"]'),n=document.querySelector('[data-bones-slot="'+e+'"]');n?(t&&(n.replaceChildren(t.content),t.remove()),r&&n.setAttribute("data-bones-error",""),"boolean"==typeof n.busy?n.busy=!1:(n.removeAttribute("busy"),n.removeAttribute("aria-busy"),n.removeAttribute("inert"))):t&&t.remove()}<\/script>`;
2
- //#endregion
3
- export { BOOTSTRAP_SCRIPT };
@@ -1,17 +0,0 @@
1
- import { BOOTSTRAP_SCRIPT } from "./bootstrap.mjs";
2
-
3
- //#region src/server/index.d.ts
4
- declare function renderBoundary(id: string, fallbackHtml: string, attrs?: string): string;
5
- declare function renderChunk(id: string, html: string): string;
6
- declare function renderErrorChunk(id: string, html?: string): string;
7
- interface StreamBonesOptions {
8
- /**
9
- * Renders the error HTML for a rejected slot. Returning undefined (and
10
- * throwing) falls back to the bare error chunk, which keeps the boundary's
11
- * fallback children.
12
- */
13
- onError?: (id: string, error: unknown) => string | undefined;
14
- }
15
- declare function streamBones(shell: string, slots: Record<string, Promise<string>>, options?: StreamBonesOptions): ReadableStream<Uint8Array>;
16
- //#endregion
17
- export { BOOTSTRAP_SCRIPT, StreamBonesOptions, renderBoundary, renderChunk, renderErrorChunk, streamBones };
@@ -1,57 +0,0 @@
1
- import { BOOTSTRAP_SCRIPT } from "./bootstrap.mjs";
2
- //#region src/server/index.ts
3
- const ID_PATTERN = /^[A-Za-z0-9_-]+$/;
4
- function assertId(id) {
5
- if (!ID_PATTERN.test(id)) throw new Error(`bones slot id ${JSON.stringify(id)} must match [A-Za-z0-9_-]+`);
6
- }
7
- function renderBoundary(id, fallbackHtml, attrs) {
8
- assertId(id);
9
- return `<bones-boundary busy aria-busy="true" inert data-bones-slot="${id}"${attrs === void 0 || attrs === "" ? "" : ` ${attrs}`}>${fallbackHtml}</bones-boundary>`;
10
- }
11
- function renderChunk(id, html) {
12
- assertId(id);
13
- return `<template data-bones-chunk="${id}">${html}</template><script>__bonesSwap("${id}")<\/script>`;
14
- }
15
- function renderErrorChunk(id, html) {
16
- assertId(id);
17
- if (html === void 0) return `<script>__bonesSwap("${id}",1)<\/script>`;
18
- return `<template data-bones-chunk="${id}">${html}</template><script>__bonesSwap("${id}",1)<\/script>`;
19
- }
20
- function streamBones(shell, slots, options = {}) {
21
- const ids = Object.keys(slots);
22
- for (const id of ids) assertId(id);
23
- const encoder = new TextEncoder();
24
- let cancelled = false;
25
- return new ReadableStream({
26
- start(controller) {
27
- const send = (html) => {
28
- if (!cancelled) controller.enqueue(encoder.encode(html));
29
- };
30
- send(shell + BOOTSTRAP_SCRIPT);
31
- if (ids.length === 0) {
32
- controller.close();
33
- return;
34
- }
35
- let pending = ids.length;
36
- const settle = (chunk) => {
37
- send(chunk);
38
- pending -= 1;
39
- if (pending === 0 && !cancelled) controller.close();
40
- };
41
- for (const id of ids) slots[id].then((html) => settle(renderChunk(id, html)), (error) => {
42
- let html;
43
- try {
44
- html = options.onError?.(id, error);
45
- } catch {
46
- html = void 0;
47
- }
48
- settle(renderErrorChunk(id, html));
49
- });
50
- },
51
- cancel() {
52
- cancelled = true;
53
- }
54
- });
55
- }
56
- //#endregion
57
- export { BOOTSTRAP_SCRIPT, renderBoundary, renderChunk, renderErrorChunk, streamBones };