@aejkatappaja/phantom-ui 0.1.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/LICENSE +21 -0
- package/README.md +346 -0
- package/custom-elements.json +228 -0
- package/dist/phantom-ui.cdn.js +135 -0
- package/dist/phantom-ui.d.ts +78 -0
- package/dist/phantom-ui.js +84 -0
- package/package.json +70 -0
- package/src/cli/init.mjs +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aejkatappaja
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo-phantom.svg" alt="phantom-ui" width="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Structure-aware skeleton loader. One Web Component. Every framework.</strong>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/phantom-ui"><img src="https://img.shields.io/npm/v/phantom-ui.svg?style=flat-square" alt="npm version" /></a>
|
|
11
|
+
<a href="https://bundlephobia.com/package/phantom-ui"><img src="https://img.shields.io/bundlephobia/minzip/phantom-ui?style=flat-square" alt="bundle size" /></a>
|
|
12
|
+
<a href="https://github.com/anthropics/phantom-ui/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/phantom-ui?style=flat-square" alt="license" /></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
Stop building skeleton screens by hand. Wrap your real UI in `<phantom-ui>` and it generates shimmer placeholders automatically by measuring your actual DOM at runtime.
|
|
18
|
+
|
|
19
|
+
No separate skeleton components to maintain. No copy-pasting layouts. The real component _is_ the skeleton template.
|
|
20
|
+
|
|
21
|
+
## Why
|
|
22
|
+
|
|
23
|
+
Traditional skeleton loaders require you to build and maintain a second version of every component, just for the loading state. When the real component changes, the skeleton drifts out of sync.
|
|
24
|
+
|
|
25
|
+
`phantom-ui` takes a different approach. It renders your real component with invisible text, measures the position and size of every leaf element (`getBoundingClientRect`), and overlays animated shimmer blocks at the exact same coordinates. Container backgrounds and borders stay visible, giving a natural card outline while loading.
|
|
26
|
+
|
|
27
|
+
Because it is a standard Web Component (built with Lit), it works in React, Vue, Svelte, Angular, Solid, Qwik, or plain HTML. No framework adapters needed.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bun add phantom-ui # bun
|
|
33
|
+
npm install phantom-ui # npm
|
|
34
|
+
pnpm add phantom-ui # pnpm
|
|
35
|
+
yarn add phantom-ui # yarn
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or drop in a script tag with no build step:
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<script src="https://cdn.jsdelivr.net/npm/phantom-ui/dist/phantom-ui.cdn.js"></script>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<phantom-ui loading>
|
|
48
|
+
<div class="card">
|
|
49
|
+
<img src="avatar.png" width="48" height="48" style="border-radius: 50%" />
|
|
50
|
+
<h3>Ada Lovelace</h3>
|
|
51
|
+
<p>First computer programmer, probably.</p>
|
|
52
|
+
</div>
|
|
53
|
+
</phantom-ui>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Set `loading` to show the shimmer. Remove it to reveal the real content.
|
|
57
|
+
|
|
58
|
+
## Framework examples
|
|
59
|
+
|
|
60
|
+
### React
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import "phantom-ui";
|
|
64
|
+
|
|
65
|
+
function ProfileCard({ user, isLoading }: Props) {
|
|
66
|
+
return (
|
|
67
|
+
<phantom-ui loading={isLoading || undefined}>
|
|
68
|
+
<div className="card">
|
|
69
|
+
<img src={user?.avatar ?? "/placeholder.png"} className="avatar" />
|
|
70
|
+
<h3>{user?.name ?? "Placeholder Name"}</h3>
|
|
71
|
+
<p>{user?.bio ?? "A few words about this person go here."}</p>
|
|
72
|
+
</div>
|
|
73
|
+
</phantom-ui>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Vue
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<script setup lang="ts">
|
|
82
|
+
import "phantom-ui";
|
|
83
|
+
|
|
84
|
+
const props = defineProps<{ loading: boolean }>();
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<template>
|
|
88
|
+
<phantom-ui :loading="props.loading">
|
|
89
|
+
<div class="card">
|
|
90
|
+
<img src="/avatar.png" class="avatar" />
|
|
91
|
+
<h3>Ada Lovelace</h3>
|
|
92
|
+
<p>First computer programmer, probably.</p>
|
|
93
|
+
</div>
|
|
94
|
+
</phantom-ui>
|
|
95
|
+
</template>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Svelte
|
|
99
|
+
|
|
100
|
+
```svelte
|
|
101
|
+
<script lang="ts">
|
|
102
|
+
import "phantom-ui";
|
|
103
|
+
|
|
104
|
+
export let loading = true;
|
|
105
|
+
</script>
|
|
106
|
+
|
|
107
|
+
<phantom-ui {loading}>
|
|
108
|
+
<div class="card">
|
|
109
|
+
<img src="/avatar.png" alt="avatar" class="avatar" />
|
|
110
|
+
<h3>Ada Lovelace</h3>
|
|
111
|
+
<p>First computer programmer, probably.</p>
|
|
112
|
+
</div>
|
|
113
|
+
</phantom-ui>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Angular
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Component, signal, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
|
120
|
+
import "phantom-ui";
|
|
121
|
+
|
|
122
|
+
@Component({
|
|
123
|
+
selector: "app-profile",
|
|
124
|
+
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
|
125
|
+
template: `
|
|
126
|
+
<phantom-ui [attr.loading]="loading() ? '' : null">
|
|
127
|
+
<div class="card">
|
|
128
|
+
<img src="/avatar.png" class="avatar" />
|
|
129
|
+
<h3>Ada Lovelace</h3>
|
|
130
|
+
<p>First computer programmer, probably.</p>
|
|
131
|
+
</div>
|
|
132
|
+
</phantom-ui>
|
|
133
|
+
`,
|
|
134
|
+
})
|
|
135
|
+
export class ProfileComponent {
|
|
136
|
+
loading = signal(true);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Solid
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { createSignal } from "solid-js";
|
|
144
|
+
import "phantom-ui";
|
|
145
|
+
|
|
146
|
+
function ProfileCard() {
|
|
147
|
+
const [loading, setLoading] = createSignal(true);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<phantom-ui attr:loading={loading() || undefined}>
|
|
151
|
+
<div class="card">
|
|
152
|
+
<img src="/avatar.png" class="avatar" />
|
|
153
|
+
<h3>Ada Lovelace</h3>
|
|
154
|
+
<p>First computer programmer, probably.</p>
|
|
155
|
+
</div>
|
|
156
|
+
</phantom-ui>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### SSR frameworks (Next.js, Nuxt, SvelteKit, Remix)
|
|
162
|
+
|
|
163
|
+
The component needs browser APIs to measure the DOM. Import it client-side only:
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
// Next.js
|
|
167
|
+
"use client";
|
|
168
|
+
import { useEffect } from "react";
|
|
169
|
+
|
|
170
|
+
export default function Page() {
|
|
171
|
+
useEffect(() => { import("phantom-ui"); }, []);
|
|
172
|
+
return <phantom-ui loading>...</phantom-ui>;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
```vue
|
|
177
|
+
<!-- Nuxt -->
|
|
178
|
+
<script setup>
|
|
179
|
+
onMounted(() => import("phantom-ui"));
|
|
180
|
+
</script>
|
|
181
|
+
|
|
182
|
+
<template>
|
|
183
|
+
<ClientOnly>
|
|
184
|
+
<phantom-ui loading>...</phantom-ui>
|
|
185
|
+
</ClientOnly>
|
|
186
|
+
</template>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
```svelte
|
|
190
|
+
<!-- SvelteKit -->
|
|
191
|
+
<script>
|
|
192
|
+
import { onMount } from "svelte";
|
|
193
|
+
onMount(() => import("phantom-ui"));
|
|
194
|
+
</script>
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
The `<phantom-ui>` tag can exist in server-rendered HTML. The browser treats it as an unknown element until hydration, then the Web Component activates and measures the DOM. Content renders normally on the server, which is good for SEO.
|
|
198
|
+
|
|
199
|
+
## TypeScript
|
|
200
|
+
|
|
201
|
+
The package ships full type definitions. A `postinstall` script automatically detects your framework and generates a `phantom-ui.d.ts` in your `src/` directory. No extra step needed.
|
|
202
|
+
|
|
203
|
+
Vue, Svelte, and Angular work out of the box without any type declaration.
|
|
204
|
+
|
|
205
|
+
If the postinstall did not run (CI, monorepos, `--ignore-scripts`), you can generate it manually:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npx phantom-ui init # npm
|
|
209
|
+
bunx phantom-ui init # bun
|
|
210
|
+
pnpx phantom-ui init # pnpm
|
|
211
|
+
yarn dlx phantom-ui init # yarn
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
<details>
|
|
215
|
+
<summary>Or create the file yourself:</summary>
|
|
216
|
+
|
|
217
|
+
**React**
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
import type { PhantomUiAttributes } from "phantom-ui";
|
|
221
|
+
|
|
222
|
+
declare module "react/jsx-runtime" {
|
|
223
|
+
export namespace JSX {
|
|
224
|
+
interface IntrinsicElements {
|
|
225
|
+
"phantom-ui": PhantomUiAttributes;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
**Solid**
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import type { SolidPhantomUiAttributes } from "phantom-ui";
|
|
235
|
+
|
|
236
|
+
declare module "solid-js" {
|
|
237
|
+
namespace JSX {
|
|
238
|
+
interface IntrinsicElements {
|
|
239
|
+
"phantom-ui": SolidPhantomUiAttributes;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Qwik**
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import type { PhantomUiAttributes } from "phantom-ui";
|
|
249
|
+
|
|
250
|
+
declare module "@builder.io/qwik" {
|
|
251
|
+
namespace QwikJSX {
|
|
252
|
+
interface IntrinsicElements {
|
|
253
|
+
"phantom-ui": PhantomUiAttributes & Record<string, unknown>;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
</details>
|
|
261
|
+
|
|
262
|
+
## Attributes
|
|
263
|
+
|
|
264
|
+
| Attribute | Type | Default | Description |
|
|
265
|
+
| --- | --- | --- | --- |
|
|
266
|
+
| `loading` | `boolean` | `false` | Show shimmer overlay or real content |
|
|
267
|
+
| `shimmer-color` | `string` | `rgba(255,255,255,0.3)` | Color of the animated gradient sweep |
|
|
268
|
+
| `background-color` | `string` | `rgba(255,255,255,0.08)` | Background of each shimmer block |
|
|
269
|
+
| `duration` | `number` | `1.5` | Animation cycle in seconds |
|
|
270
|
+
| `fallback-radius` | `number` | `4` | Border radius (px) for flat elements like text |
|
|
271
|
+
|
|
272
|
+
## Fine-grained control
|
|
273
|
+
|
|
274
|
+
Two data attributes let you control which elements get shimmer treatment:
|
|
275
|
+
|
|
276
|
+
**`data-shimmer-ignore`** keeps an element and all its descendants visible during loading. Useful for logos, brand marks, or live indicators that should always be shown.
|
|
277
|
+
|
|
278
|
+
**`data-shimmer-no-children`** captures the element as one single shimmer block instead of recursing into its children. Useful for dense metric groups that should appear as a single placeholder.
|
|
279
|
+
|
|
280
|
+
```html
|
|
281
|
+
<phantom-ui loading>
|
|
282
|
+
<div class="dashboard">
|
|
283
|
+
<div class="logo" data-shimmer-ignore>ACME</div>
|
|
284
|
+
<div class="kpi-row" data-shimmer-no-children>
|
|
285
|
+
<span>$48.2k</span>
|
|
286
|
+
<span>2,847 users</span>
|
|
287
|
+
<span>42ms p99</span>
|
|
288
|
+
</div>
|
|
289
|
+
<div class="content">
|
|
290
|
+
<p>Each leaf element here gets its own shimmer block.</p>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
</phantom-ui>
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## How it works
|
|
297
|
+
|
|
298
|
+
1. Your real content is rendered in the DOM with `color: transparent` and media elements hidden. Container backgrounds and borders stay visible, preserving the natural card/section outline.
|
|
299
|
+
|
|
300
|
+
2. The component walks the DOM tree and identifies "leaf" elements: text nodes, images, buttons, inputs, and anything without child elements. Container divs are recursed into, not captured.
|
|
301
|
+
|
|
302
|
+
3. Each leaf element is measured with `getBoundingClientRect()` relative to the host. Border radius is read from `getComputedStyle()`. Table cells get special handling to measure actual text width, not cell width.
|
|
303
|
+
|
|
304
|
+
4. An absolutely-positioned overlay renders one shimmer block per measured element, with a CSS gradient animation sweeping across each block.
|
|
305
|
+
|
|
306
|
+
5. A `ResizeObserver` and `MutationObserver` re-measure automatically when the layout changes (window resize, content injection, DOM mutations).
|
|
307
|
+
|
|
308
|
+
6. When `loading` is removed, the overlay is destroyed and real content is revealed.
|
|
309
|
+
|
|
310
|
+
## CSS custom properties
|
|
311
|
+
|
|
312
|
+
You can style the component from the outside using CSS custom properties instead of (or in addition to) attributes:
|
|
313
|
+
|
|
314
|
+
```css
|
|
315
|
+
phantom-ui {
|
|
316
|
+
--shimmer-color: rgba(100, 200, 255, 0.3);
|
|
317
|
+
--shimmer-duration: 2s;
|
|
318
|
+
--shimmer-bg: rgba(100, 200, 255, 0.08);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Custom Elements Manifest
|
|
323
|
+
|
|
324
|
+
The package ships a `custom-elements.json` manifest, which gives IDE autocomplete, Storybook autodocs, and framework tooling the full picture of attributes, properties, slots, and types.
|
|
325
|
+
|
|
326
|
+
## Bundle size
|
|
327
|
+
|
|
328
|
+
The CDN build (Lit included) is ~22kb / ~8kb gzipped.
|
|
329
|
+
|
|
330
|
+
When used as an ES module with a bundler, Lit is likely already in your dependency tree, bringing the component cost down to under 2kb.
|
|
331
|
+
|
|
332
|
+
## Development
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
bun install
|
|
336
|
+
bun run storybook # dev server on :6006
|
|
337
|
+
bun run build # tsc + custom elements manifest + CDN bundle
|
|
338
|
+
bun run lint # biome check
|
|
339
|
+
bun run lint:fix # biome auto-fix
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The `examples/` directory contains test apps for React, Vue, Solid, Angular, and Qwik, each wired to the local package.
|
|
343
|
+
|
|
344
|
+
## License
|
|
345
|
+
|
|
346
|
+
MIT
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"readme": "",
|
|
4
|
+
"modules": [
|
|
5
|
+
{
|
|
6
|
+
"kind": "javascript-module",
|
|
7
|
+
"path": "src/phantom-ui.ts",
|
|
8
|
+
"declarations": [
|
|
9
|
+
{
|
|
10
|
+
"kind": "class",
|
|
11
|
+
"description": "`<phantom-ui>` — A structure-aware shimmer skeleton loader.\n\nWraps real content and, when `loading` is true, measures the DOM structure\nof the slotted children to generate perfectly-aligned shimmer overlay blocks.",
|
|
12
|
+
"name": "PhantomUi",
|
|
13
|
+
"slots": [
|
|
14
|
+
{
|
|
15
|
+
"description": "The real content to show (or measure for skeleton generation)",
|
|
16
|
+
"name": ""
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"members": [
|
|
20
|
+
{
|
|
21
|
+
"kind": "field",
|
|
22
|
+
"name": "loading",
|
|
23
|
+
"type": {
|
|
24
|
+
"text": "boolean"
|
|
25
|
+
},
|
|
26
|
+
"default": "false",
|
|
27
|
+
"description": "Whether to show the shimmer overlay or the real content",
|
|
28
|
+
"attribute": "loading",
|
|
29
|
+
"reflects": true
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"kind": "field",
|
|
33
|
+
"name": "shimmerColor",
|
|
34
|
+
"type": {
|
|
35
|
+
"text": "string"
|
|
36
|
+
},
|
|
37
|
+
"default": "\"rgba(255, 255, 255, 0.3)\"",
|
|
38
|
+
"description": "Color of the animated shimmer gradient wave",
|
|
39
|
+
"attribute": "shimmer-color"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"kind": "field",
|
|
43
|
+
"name": "backgroundColor",
|
|
44
|
+
"type": {
|
|
45
|
+
"text": "string"
|
|
46
|
+
},
|
|
47
|
+
"default": "\"rgba(255, 255, 255, 0.08)\"",
|
|
48
|
+
"description": "Background color of each shimmer block",
|
|
49
|
+
"attribute": "background-color"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"kind": "field",
|
|
53
|
+
"name": "duration",
|
|
54
|
+
"type": {
|
|
55
|
+
"text": "number"
|
|
56
|
+
},
|
|
57
|
+
"default": "1.5",
|
|
58
|
+
"description": "Animation cycle duration in seconds",
|
|
59
|
+
"attribute": "duration"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"kind": "field",
|
|
63
|
+
"name": "fallbackRadius",
|
|
64
|
+
"type": {
|
|
65
|
+
"text": "number"
|
|
66
|
+
},
|
|
67
|
+
"default": "4",
|
|
68
|
+
"description": "Border radius (px) for elements with border-radius: 0",
|
|
69
|
+
"attribute": "fallback-radius"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"kind": "field",
|
|
73
|
+
"name": "_blocks",
|
|
74
|
+
"type": {
|
|
75
|
+
"text": "ElementInfo[]"
|
|
76
|
+
},
|
|
77
|
+
"privacy": "private",
|
|
78
|
+
"default": "[]"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"kind": "field",
|
|
82
|
+
"name": "_resizeObserver",
|
|
83
|
+
"type": {
|
|
84
|
+
"text": "ResizeObserver | null"
|
|
85
|
+
},
|
|
86
|
+
"privacy": "private",
|
|
87
|
+
"default": "null"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"kind": "field",
|
|
91
|
+
"name": "_mutationObserver",
|
|
92
|
+
"type": {
|
|
93
|
+
"text": "MutationObserver | null"
|
|
94
|
+
},
|
|
95
|
+
"privacy": "private",
|
|
96
|
+
"default": "null"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"kind": "field",
|
|
100
|
+
"name": "_measureScheduled",
|
|
101
|
+
"type": {
|
|
102
|
+
"text": "boolean"
|
|
103
|
+
},
|
|
104
|
+
"privacy": "private",
|
|
105
|
+
"default": "false"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"kind": "method",
|
|
109
|
+
"name": "_scheduleMeasure",
|
|
110
|
+
"privacy": "private",
|
|
111
|
+
"return": {
|
|
112
|
+
"type": {
|
|
113
|
+
"text": "void"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"kind": "method",
|
|
119
|
+
"name": "_measure",
|
|
120
|
+
"privacy": "private",
|
|
121
|
+
"return": {
|
|
122
|
+
"type": {
|
|
123
|
+
"text": "void"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"kind": "method",
|
|
129
|
+
"name": "_setupObservers",
|
|
130
|
+
"privacy": "private",
|
|
131
|
+
"return": {
|
|
132
|
+
"type": {
|
|
133
|
+
"text": "void"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"kind": "method",
|
|
139
|
+
"name": "_teardownObservers",
|
|
140
|
+
"privacy": "private",
|
|
141
|
+
"return": {
|
|
142
|
+
"type": {
|
|
143
|
+
"text": "void"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"kind": "method",
|
|
149
|
+
"name": "_renderBlocks",
|
|
150
|
+
"privacy": "private"
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"attributes": [
|
|
154
|
+
{
|
|
155
|
+
"name": "loading",
|
|
156
|
+
"type": {
|
|
157
|
+
"text": "boolean"
|
|
158
|
+
},
|
|
159
|
+
"default": "false",
|
|
160
|
+
"description": "Whether to show the shimmer overlay or the real content",
|
|
161
|
+
"fieldName": "loading"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"name": "shimmer-color",
|
|
165
|
+
"type": {
|
|
166
|
+
"text": "string"
|
|
167
|
+
},
|
|
168
|
+
"default": "\"rgba(255, 255, 255, 0.3)\"",
|
|
169
|
+
"description": "Color of the animated shimmer gradient wave",
|
|
170
|
+
"fieldName": "shimmerColor"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "background-color",
|
|
174
|
+
"type": {
|
|
175
|
+
"text": "string"
|
|
176
|
+
},
|
|
177
|
+
"default": "\"rgba(255, 255, 255, 0.08)\"",
|
|
178
|
+
"description": "Background color of each shimmer block",
|
|
179
|
+
"fieldName": "backgroundColor"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"name": "duration",
|
|
183
|
+
"type": {
|
|
184
|
+
"text": "number"
|
|
185
|
+
},
|
|
186
|
+
"default": "1.5",
|
|
187
|
+
"description": "Animation cycle duration in seconds",
|
|
188
|
+
"fieldName": "duration"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"name": "fallback-radius",
|
|
192
|
+
"type": {
|
|
193
|
+
"text": "number"
|
|
194
|
+
},
|
|
195
|
+
"default": "4",
|
|
196
|
+
"description": "Border radius (px) for elements with border-radius: 0",
|
|
197
|
+
"fieldName": "fallbackRadius"
|
|
198
|
+
}
|
|
199
|
+
],
|
|
200
|
+
"superclass": {
|
|
201
|
+
"name": "LitElement",
|
|
202
|
+
"package": "lit"
|
|
203
|
+
},
|
|
204
|
+
"tagName": "phantom-ui",
|
|
205
|
+
"customElement": true
|
|
206
|
+
}
|
|
207
|
+
],
|
|
208
|
+
"exports": [
|
|
209
|
+
{
|
|
210
|
+
"kind": "js",
|
|
211
|
+
"name": "PhantomUi",
|
|
212
|
+
"declaration": {
|
|
213
|
+
"name": "PhantomUi",
|
|
214
|
+
"module": "src/phantom-ui.ts"
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"kind": "custom-element-definition",
|
|
219
|
+
"name": "phantom-ui",
|
|
220
|
+
"declaration": {
|
|
221
|
+
"name": "PhantomUi",
|
|
222
|
+
"module": "src/phantom-ui.ts"
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";(()=>{var Rt=Object.defineProperty;var Tt=Object.getOwnPropertyDescriptor;var g=(i,t,e,s)=>{for(var r=s>1?void 0:s?Tt(t,e):t,o=i.length-1,n;o>=0;o--)(n=i[o])&&(r=(s?n(t,e,r):n(r))||r);return s&&r&&Rt(t,e,r),r};var H=globalThis,I=H.ShadowRoot&&(H.ShadyCSS===void 0||H.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,V=Symbol(),rt=new WeakMap,w=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==V)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o,e=this.t;if(I&&t===void 0){let s=e!==void 0&&e.length===1;s&&(t=rt.get(e)),t===void 0&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),s&&rt.set(e,t))}return t}toString(){return this.cssText}},it=i=>new w(typeof i=="string"?i:i+"",void 0,V),W=(i,...t)=>{let e=i.length===1?i[0]:t.reduce((s,r,o)=>s+(n=>{if(n._$cssResult$===!0)return n.cssText;if(typeof n=="number")return n;throw Error("Value passed to 'css' function must be a 'css' function result: "+n+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+i[o+1],i[0]);return new w(e,i,V)},ot=(i,t)=>{if(I)i.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of t){let s=document.createElement("style"),r=H.litNonce;r!==void 0&&s.setAttribute("nonce",r),s.textContent=e.cssText,i.appendChild(s)}},F=I?i=>i:i=>i instanceof CSSStyleSheet?(t=>{let e="";for(let s of t.cssRules)e+=s.cssText;return it(e)})(i):i;var{is:Pt,defineProperty:Mt,getOwnPropertyDescriptor:kt,getOwnPropertyNames:Nt,getOwnPropertySymbols:Ut,getPrototypeOf:Ht}=Object,L=globalThis,nt=L.trustedTypes,It=nt?nt.emptyScript:"",Lt=L.reactiveElementPolyfillSupport,O=(i,t)=>i,R={toAttribute(i,t){switch(t){case Boolean:i=i?It:null;break;case Object:case Array:i=i==null?i:JSON.stringify(i)}return i},fromAttribute(i,t){let e=i;switch(t){case Boolean:e=i!==null;break;case Number:e=i===null?null:Number(i);break;case Object:case Array:try{e=JSON.parse(i)}catch{e=null}}return e}},D=(i,t)=>!Pt(i,t),at={attribute:!0,type:String,converter:R,reflect:!1,useDefault:!1,hasChanged:D};Symbol.metadata??=Symbol("metadata"),L.litPropertyMetadata??=new WeakMap;var f=class extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,e=at){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(t)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(t,e),!e.noAccessor){let s=Symbol(),r=this.getPropertyDescriptor(t,s,e);r!==void 0&&Mt(this.prototype,t,r)}}static getPropertyDescriptor(t,e,s){let{get:r,set:o}=kt(this.prototype,t)??{get(){return this[e]},set(n){this[e]=n}};return{get:r,set(n){let h=r?.call(this);o?.call(this,n),this.requestUpdate(t,h,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)??at}static _$Ei(){if(this.hasOwnProperty(O("elementProperties")))return;let t=Ht(this);t.finalize(),t.l!==void 0&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties)}static finalize(){if(this.hasOwnProperty(O("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(O("properties"))){let e=this.properties,s=[...Nt(e),...Ut(e)];for(let r of s)this.createProperty(r,e[r])}let t=this[Symbol.metadata];if(t!==null){let e=litPropertyMetadata.get(t);if(e!==void 0)for(let[s,r]of e)this.elementProperties.set(s,r)}this._$Eh=new Map;for(let[e,s]of this.elementProperties){let r=this._$Eu(e,s);r!==void 0&&this._$Eh.set(r,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(t){let e=[];if(Array.isArray(t)){let s=new Set(t.flat(1/0).reverse());for(let r of s)e.unshift(F(r))}else t!==void 0&&e.push(F(t));return e}static _$Eu(t,e){let s=e.attribute;return s===!1?void 0:typeof s=="string"?s:typeof t=="string"?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(t=>this.enableUpdating=t),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(t=>t(this))}addController(t){(this._$EO??=new Set).add(t),this.renderRoot!==void 0&&this.isConnected&&t.hostConnected?.()}removeController(t){this._$EO?.delete(t)}_$E_(){let t=new Map,e=this.constructor.elementProperties;for(let s of e.keys())this.hasOwnProperty(s)&&(t.set(s,this[s]),delete this[s]);t.size>0&&(this._$Ep=t)}createRenderRoot(){let t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return ot(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(t=>t.hostConnected?.())}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach(t=>t.hostDisconnected?.())}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$ET(t,e){let s=this.constructor.elementProperties.get(t),r=this.constructor._$Eu(t,s);if(r!==void 0&&s.reflect===!0){let o=(s.converter?.toAttribute!==void 0?s.converter:R).toAttribute(e,s.type);this._$Em=t,o==null?this.removeAttribute(r):this.setAttribute(r,o),this._$Em=null}}_$AK(t,e){let s=this.constructor,r=s._$Eh.get(t);if(r!==void 0&&this._$Em!==r){let o=s.getPropertyOptions(r),n=typeof o.converter=="function"?{fromAttribute:o.converter}:o.converter?.fromAttribute!==void 0?o.converter:R;this._$Em=r;let h=n.fromAttribute(e,o.type);this[r]=h??this._$Ej?.get(r)??h,this._$Em=null}}requestUpdate(t,e,s,r=!1,o){if(t!==void 0){let n=this.constructor;if(r===!1&&(o=this[t]),s??=n.getPropertyOptions(t),!((s.hasChanged??D)(o,e)||s.useDefault&&s.reflect&&o===this._$Ej?.get(t)&&!this.hasAttribute(n._$Eu(t,s))))return;this.C(t,e,s)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(t,e,{useDefault:s,reflect:r,wrapped:o},n){s&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,n??e??this[t]),o!==!0||n!==void 0)||(this._$AL.has(t)||(this.hasUpdated||s||(e=void 0),this._$AL.set(t,e)),r===!0&&this._$Em!==t&&(this._$Eq??=new Set).add(t))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let t=this.scheduleUpdate();return t!=null&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[r,o]of this._$Ep)this[r]=o;this._$Ep=void 0}let s=this.constructor.elementProperties;if(s.size>0)for(let[r,o]of s){let{wrapped:n}=o,h=this[r];n!==!0||this._$AL.has(r)||h===void 0||this.C(r,void 0,o,h)}}let t=!1,e=this._$AL;try{t=this.shouldUpdate(e),t?(this.willUpdate(e),this._$EO?.forEach(s=>s.hostUpdate?.()),this.update(e)):this._$EM()}catch(s){throw t=!1,this._$EM(),s}t&&this._$AE(e)}willUpdate(t){}_$AE(t){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return!0}update(t){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(t){}firstUpdated(t){}};f.elementStyles=[],f.shadowRootOptions={mode:"open"},f[O("elementProperties")]=new Map,f[O("finalized")]=new Map,Lt?.({ReactiveElement:f}),(L.reactiveElementVersions??=[]).push("2.1.2");var Q=globalThis,ht=i=>i,B=Q.trustedTypes,lt=B?B.createPolicy("lit-html",{createHTML:i=>i}):void 0,ft="$lit$",y=`lit$${Math.random().toFixed(9).slice(2)}$`,$t="?"+y,Dt=`<${$t}>`,S=document,P=()=>S.createComment(""),M=i=>i===null||typeof i!="object"&&typeof i!="function",tt=Array.isArray,Bt=i=>tt(i)||typeof i?.[Symbol.iterator]=="function",K=`[
|
|
2
|
+
\f\r]`,T=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ct=/-->/g,dt=/>/g,A=RegExp(`>|${K}(?:([^\\s"'>=/]+)(${K}*=${K}*(?:[^
|
|
3
|
+
\f\r"'\`<>=]|("|')|))|$)`,"g"),ut=/'/g,pt=/"/g,_t=/^(?:script|style|textarea|title)$/i,et=i=>(t,...e)=>({_$litType$:i,strings:t,values:e}),z=et(1),se=et(2),re=et(3),$=Symbol.for("lit-noChange"),u=Symbol.for("lit-nothing"),mt=new WeakMap,E=S.createTreeWalker(S,129);function gt(i,t){if(!tt(i)||!i.hasOwnProperty("raw"))throw Error("invalid template strings array");return lt!==void 0?lt.createHTML(t):t}var zt=(i,t)=>{let e=i.length-1,s=[],r,o=t===2?"<svg>":t===3?"<math>":"",n=T;for(let h=0;h<e;h++){let a=i[h],l,d,c=-1,m=0;for(;m<a.length&&(n.lastIndex=m,d=n.exec(a),d!==null);)m=n.lastIndex,n===T?d[1]==="!--"?n=ct:d[1]!==void 0?n=dt:d[2]!==void 0?(_t.test(d[2])&&(r=RegExp("</"+d[2],"g")),n=A):d[3]!==void 0&&(n=A):n===A?d[0]===">"?(n=r??T,c=-1):d[1]===void 0?c=-2:(c=n.lastIndex-d[2].length,l=d[1],n=d[3]===void 0?A:d[3]==='"'?pt:ut):n===pt||n===ut?n=A:n===ct||n===dt?n=T:(n=A,r=void 0);let _=n===A&&i[h+1].startsWith("/>")?" ":"";o+=n===T?a+Dt:c>=0?(s.push(l),a.slice(0,c)+ft+a.slice(c)+y+_):a+y+(c===-2?h:_)}return[gt(i,o+(i[e]||"<?>")+(t===2?"</svg>":t===3?"</math>":"")),s]},k=class i{constructor({strings:t,_$litType$:e},s){let r;this.parts=[];let o=0,n=0,h=t.length-1,a=this.parts,[l,d]=zt(t,e);if(this.el=i.createElement(l,s),E.currentNode=this.el.content,e===2||e===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(r=E.nextNode())!==null&&a.length<h;){if(r.nodeType===1){if(r.hasAttributes())for(let c of r.getAttributeNames())if(c.endsWith(ft)){let m=d[n++],_=r.getAttribute(c).split(y),U=/([.?@])?(.*)/.exec(m);a.push({type:1,index:o,name:U[2],strings:_,ctor:U[1]==="."?J:U[1]==="?"?X:U[1]==="@"?Y:C}),r.removeAttribute(c)}else c.startsWith(y)&&(a.push({type:6,index:o}),r.removeAttribute(c));if(_t.test(r.tagName)){let c=r.textContent.split(y),m=c.length-1;if(m>0){r.textContent=B?B.emptyScript:"";for(let _=0;_<m;_++)r.append(c[_],P()),E.nextNode(),a.push({type:2,index:++o});r.append(c[m],P())}}}else if(r.nodeType===8)if(r.data===$t)a.push({type:2,index:o});else{let c=-1;for(;(c=r.data.indexOf(y,c+1))!==-1;)a.push({type:7,index:o}),c+=y.length-1}o++}}static createElement(t,e){let s=S.createElement("template");return s.innerHTML=t,s}};function x(i,t,e=i,s){if(t===$)return t;let r=s!==void 0?e._$Co?.[s]:e._$Cl,o=M(t)?void 0:t._$litDirective$;return r?.constructor!==o&&(r?._$AO?.(!1),o===void 0?r=void 0:(r=new o(i),r._$AT(i,e,s)),s!==void 0?(e._$Co??=[])[s]=r:e._$Cl=r),r!==void 0&&(t=x(i,r._$AS(i,t.values),r,s)),t}var G=class{constructor(t,e){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){let{el:{content:e},parts:s}=this._$AD,r=(t?.creationScope??S).importNode(e,!0);E.currentNode=r;let o=E.nextNode(),n=0,h=0,a=s[0];for(;a!==void 0;){if(n===a.index){let l;a.type===2?l=new N(o,o.nextSibling,this,t):a.type===1?l=new a.ctor(o,a.name,a.strings,this,t):a.type===6&&(l=new Z(o,this,t)),this._$AV.push(l),a=s[++h]}n!==a?.index&&(o=E.nextNode(),n++)}return E.currentNode=S,r}p(t){let e=0;for(let s of this._$AV)s!==void 0&&(s.strings!==void 0?(s._$AI(t,s,e),e+=s.strings.length-2):s._$AI(t[e])),e++}},N=class i{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,e,s,r){this.type=2,this._$AH=u,this._$AN=void 0,this._$AA=t,this._$AB=e,this._$AM=s,this.options=r,this._$Cv=r?.isConnected??!0}get parentNode(){let t=this._$AA.parentNode,e=this._$AM;return e!==void 0&&t?.nodeType===11&&(t=e.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,e=this){t=x(this,t,e),M(t)?t===u||t==null||t===""?(this._$AH!==u&&this._$AR(),this._$AH=u):t!==this._$AH&&t!==$&&this._(t):t._$litType$!==void 0?this.$(t):t.nodeType!==void 0?this.T(t):Bt(t)?this.k(t):this._(t)}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t))}_(t){this._$AH!==u&&M(this._$AH)?this._$AA.nextSibling.data=t:this.T(S.createTextNode(t)),this._$AH=t}$(t){let{values:e,_$litType$:s}=t,r=typeof s=="number"?this._$AC(t):(s.el===void 0&&(s.el=k.createElement(gt(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===r)this._$AH.p(e);else{let o=new G(r,this),n=o.u(this.options);o.p(e),this.T(n),this._$AH=o}}_$AC(t){let e=mt.get(t.strings);return e===void 0&&mt.set(t.strings,e=new k(t)),e}k(t){tt(this._$AH)||(this._$AH=[],this._$AR());let e=this._$AH,s,r=0;for(let o of t)r===e.length?e.push(s=new i(this.O(P()),this.O(P()),this,this.options)):s=e[r],s._$AI(o),r++;r<e.length&&(this._$AR(s&&s._$AB.nextSibling,r),e.length=r)}_$AR(t=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);t!==this._$AB;){let s=ht(t).nextSibling;ht(t).remove(),t=s}}setConnected(t){this._$AM===void 0&&(this._$Cv=t,this._$AP?.(t))}},C=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,e,s,r,o){this.type=1,this._$AH=u,this._$AN=void 0,this.element=t,this.name=e,this._$AM=r,this.options=o,s.length>2||s[0]!==""||s[1]!==""?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=u}_$AI(t,e=this,s,r){let o=this.strings,n=!1;if(o===void 0)t=x(this,t,e,0),n=!M(t)||t!==this._$AH&&t!==$,n&&(this._$AH=t);else{let h=t,a,l;for(t=o[0],a=0;a<o.length-1;a++)l=x(this,h[s+a],e,a),l===$&&(l=this._$AH[a]),n||=!M(l)||l!==this._$AH[a],l===u?t=u:t!==u&&(t+=(l??"")+o[a+1]),this._$AH[a]=l}n&&!r&&this.j(t)}j(t){t===u?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"")}},J=class extends C{constructor(){super(...arguments),this.type=3}j(t){this.element[this.name]=t===u?void 0:t}},X=class extends C{constructor(){super(...arguments),this.type=4}j(t){this.element.toggleAttribute(this.name,!!t&&t!==u)}},Y=class extends C{constructor(t,e,s,r,o){super(t,e,s,r,o),this.type=5}_$AI(t,e=this){if((t=x(this,t,e,0)??u)===$)return;let s=this._$AH,r=t===u&&s!==u||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,o=t!==u&&(s===u||r);r&&this.element.removeEventListener(this.name,this,s),o&&this.element.addEventListener(this.name,this,t),this._$AH=t}handleEvent(t){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t)}},Z=class{constructor(t,e,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=e,this.options=s}get _$AU(){return this._$AM._$AU}_$AI(t){x(this,t)}};var jt=Q.litHtmlPolyfillSupport;jt?.(k,N),(Q.litHtmlVersions??=[]).push("3.3.2");var yt=(i,t,e)=>{let s=e?.renderBefore??t,r=s._$litPart$;if(r===void 0){let o=e?.renderBefore??null;s._$litPart$=r=new N(t.insertBefore(P(),o),o,void 0,e??{})}return r._$AI(i),r};var st=globalThis,b=class extends f{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=yt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return $}};b._$litElement$=!0,b.finalized=!0,st.litElementHydrateSupport?.({LitElement:b});var qt=st.litElementPolyfillSupport;qt?.({LitElement:b});(st.litElementVersions??=[]).push("4.2.2");var bt=i=>(t,e)=>{e!==void 0?e.addInitializer(()=>{customElements.define(i,t)}):customElements.define(i,t)};var Vt={attribute:!0,type:String,converter:R,reflect:!1,hasChanged:D},Wt=(i=Vt,t,e)=>{let{kind:s,metadata:r}=e,o=globalThis.litPropertyMetadata.get(r);if(o===void 0&&globalThis.litPropertyMetadata.set(r,o=new Map),s==="setter"&&((i=Object.create(i)).wrapped=!0),o.set(e.name,i),s==="accessor"){let{name:n}=e;return{set(h){let a=t.get.call(this);t.set.call(this,h),this.requestUpdate(n,a,i,!0,h)},init(h){return h!==void 0&&this.C(n,void 0,i,h),h}}}if(s==="setter"){let{name:n}=e;return function(h){let a=this[n];t.call(this,h),this.requestUpdate(n,a,i,!0,h)}}throw Error("Unsupported decorator location: "+s)};function v(i){return(t,e)=>typeof e=="object"?Wt(i,t,e):((s,r,o)=>{let n=r.hasOwnProperty(o);return r.constructor.createProperty(o,s),n?Object.getOwnPropertyDescriptor(r,o):void 0})(i,t,e)}function vt(i){return v({...i,state:!0,attribute:!1})}var At={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},Et=i=>(...t)=>({_$litDirective$:i,values:t}),q=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,s){this._$Ct=t,this._$AM=e,this._$Ci=s}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}};var St="important",Ft=" !"+St,xt=Et(class extends q{constructor(i){if(super(i),i.type!==At.ATTRIBUTE||i.name!=="style"||i.strings?.length>2)throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.")}render(i){return Object.keys(i).reduce((t,e)=>{let s=i[e];return s==null?t:t+`${e=e.includes("-")?e:e.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g,"-$&").toLowerCase()}:${s};`},"")}update(i,[t]){let{style:e}=i.element;if(this.ft===void 0)return this.ft=new Set(Object.keys(t)),this.render(t);for(let s of this.ft)t[s]==null&&(this.ft.delete(s),s.includes("-")?e.removeProperty(s):e[s]=null);for(let s in t){let r=t[s];if(r!=null){this.ft.add(s);let o=typeof r=="string"&&r.endsWith(Ft);s.includes("-")||o?e.setProperty(s,o?r.slice(0,-11):r,o?St:""):e[s]=r}}return $}});var Kt=new Set(["IMG","SVG","VIDEO","CANVAS","IFRAME","INPUT","TEXTAREA","BUTTON","HR"]),Gt=new Set(["BR","WBR","HR"]);function Jt(i){if(Kt.has(i.tagName))return!0;for(let t of i.children)if(!Gt.has(t.tagName))return!1;return!0}function Xt(i){for(let t of i.childNodes)if(t.nodeType===Node.TEXT_NODE&&t.textContent?.trim())return!0;return!1}function Ct(i,t){let e=[];function s(r){let o=r.getBoundingClientRect();if(o.width===0||o.height===0||r.hasAttribute("data-shimmer-ignore"))return;if(r.hasAttribute("data-shimmer-no-children")||Jt(r)){let a=getComputedStyle(r).borderRadius;if((r.tagName==="TD"||r.tagName==="TH")&&Xt(r)){let l=document.createElement("span");l.style.visibility="hidden",l.style.position="absolute",l.textContent=r.textContent,r.appendChild(l);let d=l.getBoundingClientRect();r.removeChild(l),e.push({x:o.left-t.left,y:o.top-t.top,width:Math.min(d.width,o.width),height:o.height,tag:r.tagName.toLowerCase(),borderRadius:a==="0px"?"":a});return}e.push({x:o.left-t.left,y:o.top-t.top,width:o.width,height:o.height,tag:r.tagName.toLowerCase(),borderRadius:a==="0px"?"":a});return}for(let h of r.children)s(h)}for(let r of i.children)s(r);return e}function wt(i,t){let e=null,s=new ResizeObserver(()=>{e!==null&&cancelAnimationFrame(e),e=requestAnimationFrame(()=>{e=null,t()})});return s.observe(i),s}var Ot=W`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
position: relative;
|
|
7
|
+
--shimmer-color: rgba(255, 255, 255, 0.3);
|
|
8
|
+
--shimmer-duration: 1.5s;
|
|
9
|
+
--shimmer-bg: rgba(255, 255, 255, 0.08);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
:host([loading]) ::slotted(*) {
|
|
13
|
+
color: transparent !important;
|
|
14
|
+
-webkit-text-fill-color: transparent !important;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
user-select: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:host([loading]) ::slotted(img),
|
|
20
|
+
:host([loading]) ::slotted(svg),
|
|
21
|
+
:host([loading]) ::slotted(video),
|
|
22
|
+
:host([loading]) ::slotted(canvas) {
|
|
23
|
+
opacity: 0 !important;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.shimmer-overlay {
|
|
27
|
+
position: absolute;
|
|
28
|
+
inset: 0;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.shimmer-block {
|
|
34
|
+
position: absolute;
|
|
35
|
+
overflow: hidden;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.shimmer-block::after {
|
|
39
|
+
content: "";
|
|
40
|
+
position: absolute;
|
|
41
|
+
inset: 0;
|
|
42
|
+
animation: shimmer-sweep var(--shimmer-duration, 1.5s) ease-in-out infinite;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@keyframes shimmer-sweep {
|
|
46
|
+
0% {
|
|
47
|
+
background: linear-gradient(
|
|
48
|
+
90deg,
|
|
49
|
+
transparent 0%,
|
|
50
|
+
var(--shimmer-color) 50%,
|
|
51
|
+
transparent 100%
|
|
52
|
+
);
|
|
53
|
+
background-size: 200% 100%;
|
|
54
|
+
background-position: -100% 0;
|
|
55
|
+
}
|
|
56
|
+
100% {
|
|
57
|
+
background: linear-gradient(
|
|
58
|
+
90deg,
|
|
59
|
+
transparent 0%,
|
|
60
|
+
var(--shimmer-color) 50%,
|
|
61
|
+
transparent 100%
|
|
62
|
+
);
|
|
63
|
+
background-size: 200% 100%;
|
|
64
|
+
background-position: 200% 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
`;var p=class extends b{constructor(){super(...arguments);this.loading=!1;this.shimmerColor="rgba(255, 255, 255, 0.3)";this.backgroundColor="rgba(255, 255, 255, 0.08)";this.duration=1.5;this.fallbackRadius=4;this._blocks=[];this._resizeObserver=null;this._mutationObserver=null;this._measureScheduled=!1}disconnectedCallback(){super.disconnectedCallback(),this._teardownObservers()}updated(e){e.has("loading")&&(this.loading?(this._scheduleMeasure(),this._setupObservers()):(this._blocks=[],this._teardownObservers()))}render(){let e=xt({"--shimmer-color":this.shimmerColor,"--shimmer-duration":`${this.duration}s`,"--shimmer-bg":this.backgroundColor});return z`
|
|
68
|
+
<slot></slot>
|
|
69
|
+
${this.loading?z`
|
|
70
|
+
<div class="shimmer-overlay" style=${e} aria-hidden="true">
|
|
71
|
+
${this._renderBlocks()}
|
|
72
|
+
</div>
|
|
73
|
+
`:""}
|
|
74
|
+
`}_scheduleMeasure(){this._measureScheduled||(this._measureScheduled=!0,requestAnimationFrame(()=>{this._measureScheduled=!1,this._measure()}))}_measure(){if(!this.loading)return;let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let s=this.shadowRoot?.querySelector("slot");if(!s)return;let r=s.assignedElements({flatten:!0}),o=[];for(let n of r){let h=Ct(n,e);o.push(...h)}this._blocks=o}_setupObservers(){this._teardownObservers(),this._resizeObserver=wt(this,()=>{this._scheduleMeasure()}),this._mutationObserver=new MutationObserver(()=>{this._scheduleMeasure()}),this._mutationObserver.observe(this,{childList:!0,subtree:!0,attributes:!0})}_teardownObservers(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._mutationObserver&&(this._mutationObserver.disconnect(),this._mutationObserver=null)}_renderBlocks(){return this._blocks.map(e=>{let s=e.borderRadius||`${this.fallbackRadius}px`;return z`
|
|
75
|
+
<div
|
|
76
|
+
class="shimmer-block"
|
|
77
|
+
style="
|
|
78
|
+
left: ${e.x}px;
|
|
79
|
+
top: ${e.y}px;
|
|
80
|
+
width: ${e.width}px;
|
|
81
|
+
height: ${e.height}px;
|
|
82
|
+
border-radius: ${s};
|
|
83
|
+
background: var(--shimmer-bg, ${this.backgroundColor});
|
|
84
|
+
"
|
|
85
|
+
></div>
|
|
86
|
+
`})}};p.styles=Ot,g([v({type:Boolean,reflect:!0})],p.prototype,"loading",2),g([v({attribute:"shimmer-color"})],p.prototype,"shimmerColor",2),g([v({attribute:"background-color"})],p.prototype,"backgroundColor",2),g([v({type:Number})],p.prototype,"duration",2),g([v({type:Number,attribute:"fallback-radius"})],p.prototype,"fallbackRadius",2),g([vt()],p.prototype,"_blocks",2),p=g([bt("phantom-ui")],p);})();
|
|
87
|
+
/*! Bundled license information:
|
|
88
|
+
|
|
89
|
+
@lit/reactive-element/css-tag.js:
|
|
90
|
+
(**
|
|
91
|
+
* @license
|
|
92
|
+
* Copyright 2019 Google LLC
|
|
93
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
94
|
+
*)
|
|
95
|
+
|
|
96
|
+
@lit/reactive-element/reactive-element.js:
|
|
97
|
+
lit-html/lit-html.js:
|
|
98
|
+
lit-element/lit-element.js:
|
|
99
|
+
@lit/reactive-element/decorators/custom-element.js:
|
|
100
|
+
@lit/reactive-element/decorators/property.js:
|
|
101
|
+
@lit/reactive-element/decorators/state.js:
|
|
102
|
+
@lit/reactive-element/decorators/event-options.js:
|
|
103
|
+
@lit/reactive-element/decorators/base.js:
|
|
104
|
+
@lit/reactive-element/decorators/query.js:
|
|
105
|
+
@lit/reactive-element/decorators/query-all.js:
|
|
106
|
+
@lit/reactive-element/decorators/query-async.js:
|
|
107
|
+
@lit/reactive-element/decorators/query-assigned-nodes.js:
|
|
108
|
+
lit-html/directive.js:
|
|
109
|
+
(**
|
|
110
|
+
* @license
|
|
111
|
+
* Copyright 2017 Google LLC
|
|
112
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
113
|
+
*)
|
|
114
|
+
|
|
115
|
+
lit-html/is-server.js:
|
|
116
|
+
(**
|
|
117
|
+
* @license
|
|
118
|
+
* Copyright 2022 Google LLC
|
|
119
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
120
|
+
*)
|
|
121
|
+
|
|
122
|
+
@lit/reactive-element/decorators/query-assigned-elements.js:
|
|
123
|
+
(**
|
|
124
|
+
* @license
|
|
125
|
+
* Copyright 2021 Google LLC
|
|
126
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
127
|
+
*)
|
|
128
|
+
|
|
129
|
+
lit-html/directives/style-map.js:
|
|
130
|
+
(**
|
|
131
|
+
* @license
|
|
132
|
+
* Copyright 2018 Google LLC
|
|
133
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
134
|
+
*)
|
|
135
|
+
*/
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { CSSResult } from "lit";
|
|
3
|
+
/**
|
|
4
|
+
* `<phantom-ui>` — A structure-aware shimmer skeleton loader.
|
|
5
|
+
*
|
|
6
|
+
* Wraps real content and, when `loading` is true, measures the DOM structure
|
|
7
|
+
* of the slotted children to generate perfectly-aligned shimmer overlay blocks.
|
|
8
|
+
*
|
|
9
|
+
* @slot - The real content to show (or measure for skeleton generation)
|
|
10
|
+
*
|
|
11
|
+
* @property {boolean} loading - Whether to show the shimmer overlay or the real content
|
|
12
|
+
* @property {string} shimmerColor - Color of the animated shimmer gradient wave
|
|
13
|
+
* @property {string} backgroundColor - Background color of each shimmer block
|
|
14
|
+
* @property {number} duration - Animation cycle duration in seconds
|
|
15
|
+
* @property {number} fallbackRadius - Border radius (px) for elements with border-radius: 0
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```html
|
|
19
|
+
* <phantom-ui loading>
|
|
20
|
+
* <div class="card">
|
|
21
|
+
* <img src="avatar.png" width="48" height="48" />
|
|
22
|
+
* <h3>User Name</h3>
|
|
23
|
+
* <p>Some description text here</p>
|
|
24
|
+
* </div>
|
|
25
|
+
* </phantom-ui>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare class PhantomUi extends LitElement {
|
|
29
|
+
static styles: CSSResult;
|
|
30
|
+
/** Whether to show the shimmer overlay or the real content */
|
|
31
|
+
loading: boolean;
|
|
32
|
+
/** Color of the animated shimmer gradient wave */
|
|
33
|
+
shimmerColor: string;
|
|
34
|
+
/** Background color of each shimmer block */
|
|
35
|
+
backgroundColor: string;
|
|
36
|
+
/** Animation cycle duration in seconds */
|
|
37
|
+
duration: number;
|
|
38
|
+
/** Border radius applied to elements with border-radius: 0 (like text) */
|
|
39
|
+
fallbackRadius: number;
|
|
40
|
+
private _blocks;
|
|
41
|
+
private _resizeObserver;
|
|
42
|
+
private _mutationObserver;
|
|
43
|
+
private _measureScheduled;
|
|
44
|
+
disconnectedCallback(): void;
|
|
45
|
+
updated(changedProperties: Map<PropertyKey, unknown>): void;
|
|
46
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
47
|
+
private _scheduleMeasure;
|
|
48
|
+
private _measure;
|
|
49
|
+
private _setupObservers;
|
|
50
|
+
private _teardownObservers;
|
|
51
|
+
private _renderBlocks;
|
|
52
|
+
}
|
|
53
|
+
export interface PhantomUiAttributes {
|
|
54
|
+
loading?: boolean;
|
|
55
|
+
"shimmer-color"?: string;
|
|
56
|
+
"background-color"?: string;
|
|
57
|
+
duration?: number;
|
|
58
|
+
"fallback-radius"?: number;
|
|
59
|
+
children?: unknown;
|
|
60
|
+
class?: string;
|
|
61
|
+
id?: string;
|
|
62
|
+
style?: string;
|
|
63
|
+
slot?: string;
|
|
64
|
+
}
|
|
65
|
+
/** Solid uses `attr:` prefix to set HTML attributes. This maps all PhantomUiAttributes to their `attr:` equivalents. */
|
|
66
|
+
export type SolidPhantomUiAttributes = PhantomUiAttributes & {
|
|
67
|
+
[K in keyof PhantomUiAttributes as `attr:${K & string}`]?: PhantomUiAttributes[K];
|
|
68
|
+
};
|
|
69
|
+
declare global {
|
|
70
|
+
interface HTMLElementTagNameMap {
|
|
71
|
+
"phantom-ui": PhantomUi;
|
|
72
|
+
}
|
|
73
|
+
namespace JSX {
|
|
74
|
+
interface IntrinsicElements {
|
|
75
|
+
"phantom-ui": PhantomUiAttributes;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
var v=Object.defineProperty;var _=Object.getOwnPropertyDescriptor;var a=(o,r,e,i)=>{for(var t=i>1?void 0:i?_(r,e):r,s=o.length-1,l;s>=0;s--)(l=o[s])&&(t=(i?l(r,e,t):l(t))||t);return i&&t&&v(r,e,t),t};import{LitElement as x,html as c}from"lit";import{customElement as R,property as d,state as C}from"lit/decorators.js";import{styleMap as S}from"lit/directives/style-map.js";var y=new Set(["IMG","SVG","VIDEO","CANVAS","IFRAME","INPUT","TEXTAREA","BUTTON","HR"]),k=new Set(["BR","WBR","HR"]);function w(o){if(y.has(o.tagName))return!0;for(let r of o.children)if(!k.has(r.tagName))return!1;return!0}function O(o){for(let r of o.childNodes)if(r.nodeType===Node.TEXT_NODE&&r.textContent?.trim())return!0;return!1}function b(o,r){let e=[];function i(t){let s=t.getBoundingClientRect();if(s.width===0||s.height===0||t.hasAttribute("data-shimmer-ignore"))return;if(t.hasAttribute("data-shimmer-no-children")||w(t)){let h=getComputedStyle(t).borderRadius;if((t.tagName==="TD"||t.tagName==="TH")&&O(t)){let u=document.createElement("span");u.style.visibility="hidden",u.style.position="absolute",u.textContent=t.textContent,t.appendChild(u);let g=u.getBoundingClientRect();t.removeChild(u),e.push({x:s.left-r.left,y:s.top-r.top,width:Math.min(g.width,s.width),height:s.height,tag:t.tagName.toLowerCase(),borderRadius:h==="0px"?"":h});return}e.push({x:s.left-r.left,y:s.top-r.top,width:s.width,height:s.height,tag:t.tagName.toLowerCase(),borderRadius:h==="0px"?"":h});return}for(let m of t.children)i(m)}for(let t of o.children)i(t);return e}function p(o,r){let e=null,i=new ResizeObserver(()=>{e!==null&&cancelAnimationFrame(e),e=requestAnimationFrame(()=>{e=null,r()})});return i.observe(o),i}import{css as E}from"lit";var f=E`
|
|
2
|
+
:host {
|
|
3
|
+
display: block;
|
|
4
|
+
position: relative;
|
|
5
|
+
--shimmer-color: rgba(255, 255, 255, 0.3);
|
|
6
|
+
--shimmer-duration: 1.5s;
|
|
7
|
+
--shimmer-bg: rgba(255, 255, 255, 0.08);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
:host([loading]) ::slotted(*) {
|
|
11
|
+
color: transparent !important;
|
|
12
|
+
-webkit-text-fill-color: transparent !important;
|
|
13
|
+
pointer-events: none;
|
|
14
|
+
user-select: none;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
:host([loading]) ::slotted(img),
|
|
18
|
+
:host([loading]) ::slotted(svg),
|
|
19
|
+
:host([loading]) ::slotted(video),
|
|
20
|
+
:host([loading]) ::slotted(canvas) {
|
|
21
|
+
opacity: 0 !important;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.shimmer-overlay {
|
|
25
|
+
position: absolute;
|
|
26
|
+
inset: 0;
|
|
27
|
+
pointer-events: none;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.shimmer-block {
|
|
32
|
+
position: absolute;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.shimmer-block::after {
|
|
37
|
+
content: "";
|
|
38
|
+
position: absolute;
|
|
39
|
+
inset: 0;
|
|
40
|
+
animation: shimmer-sweep var(--shimmer-duration, 1.5s) ease-in-out infinite;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@keyframes shimmer-sweep {
|
|
44
|
+
0% {
|
|
45
|
+
background: linear-gradient(
|
|
46
|
+
90deg,
|
|
47
|
+
transparent 0%,
|
|
48
|
+
var(--shimmer-color) 50%,
|
|
49
|
+
transparent 100%
|
|
50
|
+
);
|
|
51
|
+
background-size: 200% 100%;
|
|
52
|
+
background-position: -100% 0;
|
|
53
|
+
}
|
|
54
|
+
100% {
|
|
55
|
+
background: linear-gradient(
|
|
56
|
+
90deg,
|
|
57
|
+
transparent 0%,
|
|
58
|
+
var(--shimmer-color) 50%,
|
|
59
|
+
transparent 100%
|
|
60
|
+
);
|
|
61
|
+
background-size: 200% 100%;
|
|
62
|
+
background-position: 200% 0;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`;var n=class extends x{constructor(){super(...arguments);this.loading=!1;this.shimmerColor="rgba(255, 255, 255, 0.3)";this.backgroundColor="rgba(255, 255, 255, 0.08)";this.duration=1.5;this.fallbackRadius=4;this._blocks=[];this._resizeObserver=null;this._mutationObserver=null;this._measureScheduled=!1}disconnectedCallback(){super.disconnectedCallback(),this._teardownObservers()}updated(e){e.has("loading")&&(this.loading?(this._scheduleMeasure(),this._setupObservers()):(this._blocks=[],this._teardownObservers()))}render(){let e=S({"--shimmer-color":this.shimmerColor,"--shimmer-duration":`${this.duration}s`,"--shimmer-bg":this.backgroundColor});return c`
|
|
66
|
+
<slot></slot>
|
|
67
|
+
${this.loading?c`
|
|
68
|
+
<div class="shimmer-overlay" style=${e} aria-hidden="true">
|
|
69
|
+
${this._renderBlocks()}
|
|
70
|
+
</div>
|
|
71
|
+
`:""}
|
|
72
|
+
`}_scheduleMeasure(){this._measureScheduled||(this._measureScheduled=!0,requestAnimationFrame(()=>{this._measureScheduled=!1,this._measure()}))}_measure(){if(!this.loading)return;let e=this.getBoundingClientRect();if(e.width===0||e.height===0)return;let i=this.shadowRoot?.querySelector("slot");if(!i)return;let t=i.assignedElements({flatten:!0}),s=[];for(let l of t){let m=b(l,e);s.push(...m)}this._blocks=s}_setupObservers(){this._teardownObservers(),this._resizeObserver=p(this,()=>{this._scheduleMeasure()}),this._mutationObserver=new MutationObserver(()=>{this._scheduleMeasure()}),this._mutationObserver.observe(this,{childList:!0,subtree:!0,attributes:!0})}_teardownObservers(){this._resizeObserver&&(this._resizeObserver.disconnect(),this._resizeObserver=null),this._mutationObserver&&(this._mutationObserver.disconnect(),this._mutationObserver=null)}_renderBlocks(){return this._blocks.map(e=>{let i=e.borderRadius||`${this.fallbackRadius}px`;return c`
|
|
73
|
+
<div
|
|
74
|
+
class="shimmer-block"
|
|
75
|
+
style="
|
|
76
|
+
left: ${e.x}px;
|
|
77
|
+
top: ${e.y}px;
|
|
78
|
+
width: ${e.width}px;
|
|
79
|
+
height: ${e.height}px;
|
|
80
|
+
border-radius: ${i};
|
|
81
|
+
background: var(--shimmer-bg, ${this.backgroundColor});
|
|
82
|
+
"
|
|
83
|
+
></div>
|
|
84
|
+
`})}};n.styles=f,a([d({type:Boolean,reflect:!0})],n.prototype,"loading",2),a([d({attribute:"shimmer-color"})],n.prototype,"shimmerColor",2),a([d({attribute:"background-color"})],n.prototype,"backgroundColor",2),a([d({type:Number})],n.prototype,"duration",2),a([d({type:Number,attribute:"fallback-radius"})],n.prototype,"fallbackRadius",2),a([C()],n.prototype,"_blocks",2),n=a([R("phantom-ui")],n);export{n as PhantomUi};
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aejkatappaja/phantom-ui",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Structure-aware shimmer skeleton loader as a universal Web Component built with Lit. Works with React, Vue, Svelte, Angular, Solid, or vanilla JS.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/phantom-ui.js",
|
|
7
|
+
"module": "dist/phantom-ui.js",
|
|
8
|
+
"types": "dist/phantom-ui.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/phantom-ui.js",
|
|
12
|
+
"types": "./dist/phantom-ui.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"phantom-ui": "./src/cli/init.mjs"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/phantom-ui.js",
|
|
20
|
+
"dist/phantom-ui.cdn.js",
|
|
21
|
+
"dist/phantom-ui.d.ts",
|
|
22
|
+
"src/cli/init.mjs",
|
|
23
|
+
"custom-elements.json"
|
|
24
|
+
],
|
|
25
|
+
"postinstall": "node ./src/cli/init.mjs || true",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "npm run build:esm && npm run build:cdn && npm run build:types && cem analyze",
|
|
28
|
+
"build:esm": "esbuild src/phantom-ui.ts --bundle --format=esm --outfile=dist/phantom-ui.js --minify --target=es2022 --packages=external",
|
|
29
|
+
"build:cdn": "esbuild src/phantom-ui.ts --bundle --format=iife --outfile=dist/phantom-ui.cdn.js --minify --target=es2022",
|
|
30
|
+
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
31
|
+
"dev": "tsc --watch",
|
|
32
|
+
"lint": "biome check .",
|
|
33
|
+
"lint:fix": "biome check --write .",
|
|
34
|
+
"format": "biome format --write .",
|
|
35
|
+
"storybook": "storybook dev -p 6006",
|
|
36
|
+
"build-storybook": "storybook build",
|
|
37
|
+
"prepare": "husky"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"skeleton",
|
|
41
|
+
"shimmer",
|
|
42
|
+
"loading",
|
|
43
|
+
"placeholder",
|
|
44
|
+
"web-component",
|
|
45
|
+
"custom-element",
|
|
46
|
+
"lit",
|
|
47
|
+
"framework-agnostic"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"lit": "^3.2.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@biomejs/biome": "^1.9.0",
|
|
55
|
+
"@custom-elements-manifest/analyzer": "^0.11.0",
|
|
56
|
+
"@storybook/addon-essentials": "^8.4.0",
|
|
57
|
+
"@storybook/web-components": "^8.4.0",
|
|
58
|
+
"@storybook/web-components-vite": "^8.4.0",
|
|
59
|
+
"husky": "^9.1.7",
|
|
60
|
+
"lint-staged": "^16.4.0",
|
|
61
|
+
"storybook": "^8.4.0",
|
|
62
|
+
"typescript": "^5.7.0"
|
|
63
|
+
},
|
|
64
|
+
"customElements": "custom-elements.json",
|
|
65
|
+
"lint-staged": {
|
|
66
|
+
"*.{ts,mjs}": [
|
|
67
|
+
"biome check --write --no-errors-on-unmatched"
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/cli/init.mjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
const FILENAME = "phantom-ui.d.ts";
|
|
7
|
+
|
|
8
|
+
const templates = {
|
|
9
|
+
react: `import type { PhantomUiAttributes } from "phantom-ui";
|
|
10
|
+
|
|
11
|
+
declare module "react/jsx-runtime" {
|
|
12
|
+
\texport namespace JSX {
|
|
13
|
+
\t\tinterface IntrinsicElements {
|
|
14
|
+
\t\t\t"phantom-ui": PhantomUiAttributes;
|
|
15
|
+
\t\t}
|
|
16
|
+
\t}
|
|
17
|
+
}
|
|
18
|
+
`,
|
|
19
|
+
solid: `import type { SolidPhantomUiAttributes } from "phantom-ui";
|
|
20
|
+
|
|
21
|
+
declare module "solid-js" {
|
|
22
|
+
\tnamespace JSX {
|
|
23
|
+
\t\tinterface IntrinsicElements {
|
|
24
|
+
\t\t\t"phantom-ui": SolidPhantomUiAttributes;
|
|
25
|
+
\t\t}
|
|
26
|
+
\t}
|
|
27
|
+
}
|
|
28
|
+
`,
|
|
29
|
+
qwik: `import type { PhantomUiAttributes } from "phantom-ui";
|
|
30
|
+
|
|
31
|
+
declare module "@builder.io/qwik" {
|
|
32
|
+
\tnamespace QwikJSX {
|
|
33
|
+
\t\tinterface IntrinsicElements {
|
|
34
|
+
\t\t\t"phantom-ui": PhantomUiAttributes & Record<string, unknown>;
|
|
35
|
+
\t\t}
|
|
36
|
+
\t}
|
|
37
|
+
}
|
|
38
|
+
`,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function findProjectRoot() {
|
|
42
|
+
// During postinstall, INIT_CWD is set to the directory where `npm install` was run
|
|
43
|
+
if (process.env.INIT_CWD && existsSync(join(process.env.INIT_CWD, "package.json"))) {
|
|
44
|
+
return process.env.INIT_CWD;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let dir = process.cwd();
|
|
48
|
+
// If we're inside node_modules, walk up to the project root
|
|
49
|
+
if (dir.includes("node_modules")) {
|
|
50
|
+
dir = dir.slice(0, dir.indexOf("node_modules") - 1);
|
|
51
|
+
}
|
|
52
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function detectFramework(root) {
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
|
|
59
|
+
const deps = Object.keys({ ...pkg.dependencies, ...pkg.devDependencies });
|
|
60
|
+
const has = (name) => deps.includes(name);
|
|
61
|
+
|
|
62
|
+
if (has("react") || has("next") || has("@remix-run/react")) return "react";
|
|
63
|
+
if (has("solid-js")) return "solid";
|
|
64
|
+
if (has("@builder.io/qwik")) return "qwik";
|
|
65
|
+
if (has("vue") || has("nuxt")) return "vue";
|
|
66
|
+
if (has("svelte") || has("@sveltejs/kit")) return "svelte";
|
|
67
|
+
if (has("@angular/core")) return "angular";
|
|
68
|
+
} catch {
|
|
69
|
+
// no package.json
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function findSrcDir(root) {
|
|
75
|
+
for (const dir of ["src", "app"]) {
|
|
76
|
+
if (existsSync(join(root, dir))) return join(root, dir);
|
|
77
|
+
}
|
|
78
|
+
return root;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const root = findProjectRoot();
|
|
82
|
+
if (!root) {
|
|
83
|
+
// Silent exit during postinstall if we can't find the project
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const framework = detectFramework(root);
|
|
88
|
+
|
|
89
|
+
if (!framework) {
|
|
90
|
+
// Silent exit during postinstall if framework is unknown
|
|
91
|
+
if (process.env.npm_lifecycle_event === "postinstall") process.exit(0);
|
|
92
|
+
console.log("Could not detect framework from package.json.");
|
|
93
|
+
console.log("Run this command from your project root.");
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (framework === "vue" || framework === "svelte" || framework === "angular") {
|
|
98
|
+
if (process.env.npm_lifecycle_event !== "postinstall") {
|
|
99
|
+
console.log(`Detected ${framework}. No type declaration needed - types work automatically.`);
|
|
100
|
+
}
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const template = templates[framework];
|
|
105
|
+
const srcDir = findSrcDir(root);
|
|
106
|
+
const outPath = join(srcDir, FILENAME);
|
|
107
|
+
|
|
108
|
+
if (existsSync(outPath)) {
|
|
109
|
+
if (process.env.npm_lifecycle_event !== "postinstall") {
|
|
110
|
+
console.log(`${outPath} already exists. Skipping.`);
|
|
111
|
+
}
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
writeFileSync(outPath, template);
|
|
116
|
+
console.log(`phantom-ui: created ${outPath} (${framework} JSX types)`);
|