@ghostly-ui/core 0.2.3 → 0.2.5

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.
Files changed (2) hide show
  1. package/dist/ghostly.css +79 -72
  2. package/package.json +1 -1
package/dist/ghostly.css CHANGED
@@ -1,12 +1,14 @@
1
1
  /* ============================================================
2
2
  GHOSTLY — Zero-config skeleton loaders
3
3
 
4
- Philosophy: NEVER override layout properties. Only hide content
5
- and add skeleton background. The component's own CSS defines
6
- all sizing, spacing, and border-radius.
4
+ Philosophy:
5
+ 1. NEVER override layout properties blindly
6
+ 2. Only hide content + add skeleton background
7
+ 3. Use :has() to surgically fix collapsing flex children
8
+ 4. Component's own CSS defines sizing, spacing, and radius
7
9
  ============================================================ */
8
10
 
9
- /* --- Custom properties (override these to customize) --- */
11
+ /* --- Custom properties --- */
10
12
 
11
13
  :root {
12
14
  --ghostly-color: hsl(220 13% 87%);
@@ -43,7 +45,6 @@
43
45
 
44
46
  /* ============================================================
45
47
  2. TEXT ELEMENTS — Hide text, show skeleton background
46
- Only override visual properties. NEVER touch layout.
47
48
  ============================================================ */
48
49
 
49
50
  [data-ghostly] :where(
@@ -63,76 +64,87 @@
63
64
  text-decoration: none !important;
64
65
  text-shadow: none !important;
65
66
  outline: none !important;
66
- /* Prevent empty elements from collapsing vertically */
67
67
  min-height: 1em;
68
- /* Inline elements need display block/inline-block to respect min-height */
69
- display: var(--_ghostly-display, revert);
70
68
  }
71
69
 
72
- /* Make inline elements (span, a, em, etc.) respect min-height/width */
70
+ /* Hide placeholders and ::before pseudo-content visibility */
71
+ [data-ghostly] :where(input, textarea)::placeholder {
72
+ color: transparent !important;
73
+ opacity: 0 !important;
74
+ }
75
+
76
+ /* Inline elements need inline-block to respect min-height */
73
77
  [data-ghostly] :where(
74
78
  span, a, em, strong, small, mark, code, abbr,
75
79
  cite, q, sub, sup, del, ins, time, label
76
80
  ) {
77
- --_ghostly-display: inline-block;
81
+ display: inline-block;
78
82
  }
79
83
 
80
- /* ── Layout preservation ──
81
- The critical problem: when text is empty, flex children collapse to 0 width.
82
- Solution: force non-leaf containers to grow, and text elements to fill.
83
- Uses !important to beat utility-class frameworks (Tailwind, etc). */
84
-
85
- /* Non-leaf containers (divs wrapping text/images) must expand in flex/grid.
86
- flex-grow:1 makes them fill available space instead of collapsing. */
87
- [data-ghostly] div,
88
- [data-ghostly] section,
89
- [data-ghostly] article,
90
- [data-ghostly] main,
91
- [data-ghostly] aside,
92
- [data-ghostly] header,
93
- [data-ghostly] footer,
94
- [data-ghostly] nav {
95
- flex-grow: 1 !important;
96
- min-width: 0 !important;
97
- }
84
+ /* ============================================================
85
+ 3. PREVENT TEXT COLLAPSE Invisible pseudo-content
86
+ When text is empty (''), elements collapse to 0 width.
87
+ We inject invisible non-breaking spaces via ::before so the
88
+ element has natural width. Works with ANY layout system.
89
+ ============================================================ */
98
90
 
99
- /* Leaf containers (empty divs used as placeholders like avatars)
100
- should NOT grow they have their own explicit dimensions. */
101
- [data-ghostly] div:empty {
102
- flex-grow: 0 !important;
103
- flex-shrink: 0 !important;
104
- }
91
+ /* Block text: inject wide invisible content */
92
+ [data-ghostly] :where(h1)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
93
+ [data-ghostly] :where(h2)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
94
+ [data-ghostly] :where(h3)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
95
+ [data-ghostly] :where(h4, h5, h6)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
96
+ [data-ghostly] :where(p)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
105
97
 
106
- /* Text elements: fill full width of their parent */
107
- [data-ghostly] :where(h1, h2, h3, h4, h5, h6, p, pre,
108
- blockquote, figcaption, caption, summary, address) {
109
- width: 100%;
110
- }
98
+ /* Inline text: shorter content */
99
+ [data-ghostly] :where(span, a, em, strong, small, label, time, code)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0'; }
111
100
 
112
- /* Inline-turned-block: reasonable minimums */
113
- [data-ghostly] :where(span, a, em, strong, small, label, time, code) { min-width: 3rem; }
114
- [data-ghostly] :where(button) { min-width: 5rem; }
101
+ /* Interactive: reasonable widths */
102
+ [data-ghostly] :where(button)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
115
103
  [data-ghostly] :where(input, textarea, select) { min-width: 6rem; }
116
104
 
105
+ /* All pseudo-content inherits transparent color so it's invisible */
106
+ [data-ghostly] :where(h1, h2, h3, h4, h5, h6, p, span, a, li,
107
+ em, strong, small, mark, code, label, time, button)::before {
108
+ color: transparent !important;
109
+ visibility: hidden;
110
+ display: inline;
111
+ height: 0;
112
+ }
113
+
117
114
  /* Headings: proportional min-height */
118
115
  [data-ghostly] :where(h1) { min-height: 1.75em; }
119
116
  [data-ghostly] :where(h2) { min-height: 1.5em; }
120
117
  [data-ghostly] :where(h3) { min-height: 1.3em; }
121
118
  [data-ghostly] :where(h4, h5, h6) { min-height: 1.15em; }
122
-
123
- /* Paragraphs: taller to simulate multi-line text */
124
- [data-ghostly] :where(p) { min-height: 3em; }
125
-
126
- /* Code blocks: taller */
119
+ [data-ghostly] :where(p) { min-height: 1em; }
127
120
  [data-ghostly] :where(pre) { min-height: 5em; }
128
-
129
- /* Interactive elements: reasonable minimums */
130
121
  [data-ghostly] :where(input, textarea, select) { min-height: 2.5rem; }
131
122
  [data-ghostly] :where(button) { min-height: 2.25rem; }
132
123
 
133
124
  /* ============================================================
134
- 3. MEDIA ELEMENTSHide content, show skeleton block
135
- Preserves ALL existing dimensions and border-radius.
125
+ 4. FLEX/GRID LAYOUT FIX Surgical, using :has()
126
+ Only expand containers that CONTAIN text elements.
127
+ Empty divs (avatars, icons) are left alone.
128
+ This is safe for sidebars, fixed columns, etc.
129
+ ============================================================ */
130
+
131
+ /* Containers holding text elements should grow in flex layouts.
132
+ :has() ensures we ONLY target divs that wrap text, not fixed-size ones. */
133
+ [data-ghostly] :where(
134
+ div, section, article, aside, header, footer, nav, main
135
+ ):has(> :where(h1, h2, h3, h4, h5, h6, p, span, a, button, input, label)) {
136
+ flex-grow: 1;
137
+ min-width: 0;
138
+ }
139
+
140
+ /* Empty containers (avatar placeholders etc): NEVER grow */
141
+ [data-ghostly] :where(div:empty, span:empty) {
142
+ flex-grow: 0 !important;
143
+ flex-shrink: 0 !important;
144
+ }
145
+
146
+ /* ============================================================
147
+ 5. MEDIA ELEMENTS — Hide content, preserve dimensions
136
148
  ============================================================ */
137
149
 
138
150
  [data-ghostly] :where(img, svg, video, canvas, picture, iframe) {
@@ -141,7 +153,6 @@
141
153
  box-shadow: none !important;
142
154
  }
143
155
 
144
- /* Hide image content without changing dimensions */
145
156
  [data-ghostly] :where(img) {
146
157
  color: transparent !important;
147
158
  object-position: -9999px !important;
@@ -151,23 +162,19 @@
151
162
  opacity: 0;
152
163
  }
153
164
 
154
- /* SVG: hide strokes/fills but keep size */
155
165
  [data-ghostly] :where(svg) {
156
166
  color: transparent !important;
157
167
  fill: transparent !important;
158
168
  stroke: transparent !important;
159
169
  }
160
170
 
161
- /* Only add min-size for SVG icons that might be empty */
162
171
  [data-ghostly] :where(svg:empty) {
163
172
  min-height: 1.5rem;
164
173
  min-width: 1.5rem;
165
174
  }
166
175
 
167
176
  /* ============================================================
168
- 4. EMPTY CONTAINERS — Style divs that act as placeholders
169
- When a component renders an empty div instead of an img
170
- (e.g. avatar placeholder), it should still show skeleton.
177
+ 6. EMPTY CONTAINERS — Skeleton background on placeholders
171
178
  ============================================================ */
172
179
 
173
180
  [data-ghostly] :where(div:empty) {
@@ -175,12 +182,9 @@
175
182
  }
176
183
 
177
184
  /* ============================================================
178
- 5. BORDER RADIUS — Use computed value, fallback to default
179
- Never override existing border-radius from the component.
185
+ 7. BORDER RADIUS — Lowest specificity, component CSS wins
180
186
  ============================================================ */
181
187
 
182
- /* Apply default radius ONLY to elements that don't have one set.
183
- We use a very low specificity so any component CSS wins. */
184
188
  @layer ghostly-defaults {
185
189
  [data-ghostly] h1, [data-ghostly] h2, [data-ghostly] h3,
186
190
  [data-ghostly] h4, [data-ghostly] h5, [data-ghostly] h6,
@@ -196,7 +200,7 @@
196
200
  }
197
201
 
198
202
  /* ============================================================
199
- 6. CUSTOM LINE COUNT — data-ghostly-lines="N"
203
+ 8. CUSTOM LINE COUNT — data-ghostly-lines="N"
200
204
  ============================================================ */
201
205
 
202
206
  [data-ghostly] :where([data-ghostly-lines="1"]) { min-height: 1em; }
@@ -209,7 +213,7 @@
209
213
  [data-ghostly] :where([data-ghostly-lines="8"]) { min-height: 8em; }
210
214
 
211
215
  /* ============================================================
212
- 7. CUSTOM ASPECT RATIO — data-ghostly-ratio
216
+ 9. CUSTOM ASPECT RATIO — data-ghostly-ratio
213
217
  ============================================================ */
214
218
 
215
219
  [data-ghostly] :where([data-ghostly-ratio="1/1"]) { aspect-ratio: 1/1 !important; }
@@ -220,7 +224,7 @@
220
224
  [data-ghostly] :where([data-ghostly-ratio="9/16"]) { aspect-ratio: 9/16 !important; }
221
225
 
222
226
  /* ============================================================
223
- 8. DECORATIVE — Strip visual noise
227
+ 10. DECORATIVE — Strip visual noise
224
228
  ============================================================ */
225
229
 
226
230
  [data-ghostly] :where(hr) {
@@ -228,7 +232,7 @@
228
232
  }
229
233
 
230
234
  /* ============================================================
231
- 9. EXCLUSIONS — data-ghostly-ignore restores original styles
235
+ 11. EXCLUSIONS — data-ghostly-ignore
232
236
  ============================================================ */
233
237
 
234
238
  [data-ghostly] [data-ghostly-ignore],
@@ -245,11 +249,15 @@
245
249
  animation: none !important;
246
250
  }
247
251
 
252
+ [data-ghostly] [data-ghostly-ignore]::before {
253
+ content: none !important;
254
+ }
255
+
248
256
  /* ============================================================
249
- 10. ANIMATIONS
257
+ 12. ANIMATIONS
250
258
  ============================================================ */
251
259
 
252
- /* --- Shimmer — gradient sweep left to right --- */
260
+ /* --- Shimmer --- */
253
261
 
254
262
  [data-ghostly='shimmer'] :where(
255
263
  h1, h2, h3, h4, h5, h6,
@@ -279,7 +287,7 @@
279
287
  100% { background-position: -100% 0; }
280
288
  }
281
289
 
282
- /* --- Pulse — opacity fade in/out --- */
290
+ /* --- Pulse --- */
283
291
 
284
292
  [data-ghostly='pulse'] :where(
285
293
  h1, h2, h3, h4, h5, h6,
@@ -300,7 +308,7 @@
300
308
  50% { opacity: 0.4; }
301
309
  }
302
310
 
303
- /* --- Wave — cascading pulse with stagger --- */
311
+ /* --- Wave --- */
304
312
 
305
313
  [data-ghostly='wave'] :where(
306
314
  h1, h2, h3, h4, h5, h6,
@@ -324,7 +332,6 @@
324
332
  100% { opacity: 1; }
325
333
  }
326
334
 
327
- /* Stagger direct children for cascading effect */
328
335
  [data-ghostly='wave'] > :nth-child(1) { animation-delay: 0ms !important; }
329
336
  [data-ghostly='wave'] > :nth-child(2) { animation-delay: 80ms !important; }
330
337
  [data-ghostly='wave'] > :nth-child(3) { animation-delay: 160ms !important; }
@@ -340,7 +347,7 @@
340
347
  [data-ghostly='wave'] > :nth-child(n+13) { animation-delay: 960ms !important; }
341
348
 
342
349
  /* ============================================================
343
- 11. SMOOTH TRANSITION — fade out when loading ends
350
+ 13. SMOOTH TRANSITION
344
351
  ============================================================ */
345
352
 
346
353
  [data-ghostly-smooth] :where(
@@ -361,7 +368,7 @@
361
368
  }
362
369
 
363
370
  /* ============================================================
364
- 12. ACCESSIBILITY
371
+ 14. ACCESSIBILITY
365
372
  ============================================================ */
366
373
 
367
374
  @media (prefers-reduced-motion: reduce) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostly-ui/core",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Zero-config skeleton loaders. Wrap your component, done.",
5
5
  "license": "MIT",
6
6
  "type": "module",