@ghostly-ui/core 0.2.3 → 0.2.4

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 +73 -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,81 @@
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
+ /* Inline elements need inline-block to respect min-height */
73
71
  [data-ghostly] :where(
74
72
  span, a, em, strong, small, mark, code, abbr,
75
73
  cite, q, sub, sup, del, ins, time, label
76
74
  ) {
77
- --_ghostly-display: inline-block;
75
+ display: inline-block;
78
76
  }
79
77
 
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
- }
78
+ /* ============================================================
79
+ 3. PREVENT TEXT COLLAPSE Invisible pseudo-content
80
+ When text is empty (''), elements collapse to 0 width.
81
+ We inject invisible non-breaking spaces via ::before so the
82
+ element has natural width. Works with ANY layout system.
83
+ ============================================================ */
98
84
 
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
- }
85
+ /* Block text: inject wide invisible content */
86
+ [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'; }
87
+ [data-ghostly] :where(h2)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
88
+ [data-ghostly] :where(h3)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
89
+ [data-ghostly] :where(h4, h5, h6)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
90
+ [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
91
 
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
- }
92
+ /* Inline text: shorter content */
93
+ [data-ghostly] :where(span, a, em, strong, small, label, time, code)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0'; }
111
94
 
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; }
95
+ /* Interactive: reasonable widths */
96
+ [data-ghostly] :where(button)::before { content: '\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0'; }
115
97
  [data-ghostly] :where(input, textarea, select) { min-width: 6rem; }
116
98
 
99
+ /* All pseudo-content inherits transparent color so it's invisible */
100
+ [data-ghostly] :where(h1, h2, h3, h4, h5, h6, p, span, a, li,
101
+ em, strong, small, mark, code, label, time, button)::before {
102
+ color: transparent !important;
103
+ visibility: hidden;
104
+ display: inline;
105
+ height: 0;
106
+ }
107
+
117
108
  /* Headings: proportional min-height */
118
109
  [data-ghostly] :where(h1) { min-height: 1.75em; }
119
110
  [data-ghostly] :where(h2) { min-height: 1.5em; }
120
111
  [data-ghostly] :where(h3) { min-height: 1.3em; }
121
112
  [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 */
113
+ [data-ghostly] :where(p) { min-height: 1em; }
127
114
  [data-ghostly] :where(pre) { min-height: 5em; }
128
-
129
- /* Interactive elements: reasonable minimums */
130
115
  [data-ghostly] :where(input, textarea, select) { min-height: 2.5rem; }
131
116
  [data-ghostly] :where(button) { min-height: 2.25rem; }
132
117
 
133
118
  /* ============================================================
134
- 3. MEDIA ELEMENTSHide content, show skeleton block
135
- Preserves ALL existing dimensions and border-radius.
119
+ 4. FLEX/GRID LAYOUT FIX Surgical, using :has()
120
+ Only expand containers that CONTAIN text elements.
121
+ Empty divs (avatars, icons) are left alone.
122
+ This is safe for sidebars, fixed columns, etc.
123
+ ============================================================ */
124
+
125
+ /* Containers holding text elements should grow in flex layouts.
126
+ :has() ensures we ONLY target divs that wrap text, not fixed-size ones. */
127
+ [data-ghostly] :where(
128
+ div, section, article, aside, header, footer, nav, main
129
+ ):has(> :where(h1, h2, h3, h4, h5, h6, p, span, a, button, input, label)) {
130
+ flex-grow: 1;
131
+ min-width: 0;
132
+ }
133
+
134
+ /* Empty containers (avatar placeholders etc): NEVER grow */
135
+ [data-ghostly] :where(div:empty, span:empty) {
136
+ flex-grow: 0 !important;
137
+ flex-shrink: 0 !important;
138
+ }
139
+
140
+ /* ============================================================
141
+ 5. MEDIA ELEMENTS — Hide content, preserve dimensions
136
142
  ============================================================ */
137
143
 
138
144
  [data-ghostly] :where(img, svg, video, canvas, picture, iframe) {
@@ -141,7 +147,6 @@
141
147
  box-shadow: none !important;
142
148
  }
143
149
 
144
- /* Hide image content without changing dimensions */
145
150
  [data-ghostly] :where(img) {
146
151
  color: transparent !important;
147
152
  object-position: -9999px !important;
@@ -151,23 +156,19 @@
151
156
  opacity: 0;
152
157
  }
153
158
 
154
- /* SVG: hide strokes/fills but keep size */
155
159
  [data-ghostly] :where(svg) {
156
160
  color: transparent !important;
157
161
  fill: transparent !important;
158
162
  stroke: transparent !important;
159
163
  }
160
164
 
161
- /* Only add min-size for SVG icons that might be empty */
162
165
  [data-ghostly] :where(svg:empty) {
163
166
  min-height: 1.5rem;
164
167
  min-width: 1.5rem;
165
168
  }
166
169
 
167
170
  /* ============================================================
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.
171
+ 6. EMPTY CONTAINERS — Skeleton background on placeholders
171
172
  ============================================================ */
172
173
 
173
174
  [data-ghostly] :where(div:empty) {
@@ -175,12 +176,9 @@
175
176
  }
176
177
 
177
178
  /* ============================================================
178
- 5. BORDER RADIUS — Use computed value, fallback to default
179
- Never override existing border-radius from the component.
179
+ 7. BORDER RADIUS — Lowest specificity, component CSS wins
180
180
  ============================================================ */
181
181
 
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
182
  @layer ghostly-defaults {
185
183
  [data-ghostly] h1, [data-ghostly] h2, [data-ghostly] h3,
186
184
  [data-ghostly] h4, [data-ghostly] h5, [data-ghostly] h6,
@@ -196,7 +194,7 @@
196
194
  }
197
195
 
198
196
  /* ============================================================
199
- 6. CUSTOM LINE COUNT — data-ghostly-lines="N"
197
+ 8. CUSTOM LINE COUNT — data-ghostly-lines="N"
200
198
  ============================================================ */
201
199
 
202
200
  [data-ghostly] :where([data-ghostly-lines="1"]) { min-height: 1em; }
@@ -209,7 +207,7 @@
209
207
  [data-ghostly] :where([data-ghostly-lines="8"]) { min-height: 8em; }
210
208
 
211
209
  /* ============================================================
212
- 7. CUSTOM ASPECT RATIO — data-ghostly-ratio
210
+ 9. CUSTOM ASPECT RATIO — data-ghostly-ratio
213
211
  ============================================================ */
214
212
 
215
213
  [data-ghostly] :where([data-ghostly-ratio="1/1"]) { aspect-ratio: 1/1 !important; }
@@ -220,7 +218,7 @@
220
218
  [data-ghostly] :where([data-ghostly-ratio="9/16"]) { aspect-ratio: 9/16 !important; }
221
219
 
222
220
  /* ============================================================
223
- 8. DECORATIVE — Strip visual noise
221
+ 10. DECORATIVE — Strip visual noise
224
222
  ============================================================ */
225
223
 
226
224
  [data-ghostly] :where(hr) {
@@ -228,7 +226,7 @@
228
226
  }
229
227
 
230
228
  /* ============================================================
231
- 9. EXCLUSIONS — data-ghostly-ignore restores original styles
229
+ 11. EXCLUSIONS — data-ghostly-ignore
232
230
  ============================================================ */
233
231
 
234
232
  [data-ghostly] [data-ghostly-ignore],
@@ -245,11 +243,15 @@
245
243
  animation: none !important;
246
244
  }
247
245
 
246
+ [data-ghostly] [data-ghostly-ignore]::before {
247
+ content: none !important;
248
+ }
249
+
248
250
  /* ============================================================
249
- 10. ANIMATIONS
251
+ 12. ANIMATIONS
250
252
  ============================================================ */
251
253
 
252
- /* --- Shimmer — gradient sweep left to right --- */
254
+ /* --- Shimmer --- */
253
255
 
254
256
  [data-ghostly='shimmer'] :where(
255
257
  h1, h2, h3, h4, h5, h6,
@@ -279,7 +281,7 @@
279
281
  100% { background-position: -100% 0; }
280
282
  }
281
283
 
282
- /* --- Pulse — opacity fade in/out --- */
284
+ /* --- Pulse --- */
283
285
 
284
286
  [data-ghostly='pulse'] :where(
285
287
  h1, h2, h3, h4, h5, h6,
@@ -300,7 +302,7 @@
300
302
  50% { opacity: 0.4; }
301
303
  }
302
304
 
303
- /* --- Wave — cascading pulse with stagger --- */
305
+ /* --- Wave --- */
304
306
 
305
307
  [data-ghostly='wave'] :where(
306
308
  h1, h2, h3, h4, h5, h6,
@@ -324,7 +326,6 @@
324
326
  100% { opacity: 1; }
325
327
  }
326
328
 
327
- /* Stagger direct children for cascading effect */
328
329
  [data-ghostly='wave'] > :nth-child(1) { animation-delay: 0ms !important; }
329
330
  [data-ghostly='wave'] > :nth-child(2) { animation-delay: 80ms !important; }
330
331
  [data-ghostly='wave'] > :nth-child(3) { animation-delay: 160ms !important; }
@@ -340,7 +341,7 @@
340
341
  [data-ghostly='wave'] > :nth-child(n+13) { animation-delay: 960ms !important; }
341
342
 
342
343
  /* ============================================================
343
- 11. SMOOTH TRANSITION — fade out when loading ends
344
+ 13. SMOOTH TRANSITION
344
345
  ============================================================ */
345
346
 
346
347
  [data-ghostly-smooth] :where(
@@ -361,7 +362,7 @@
361
362
  }
362
363
 
363
364
  /* ============================================================
364
- 12. ACCESSIBILITY
365
+ 14. ACCESSIBILITY
365
366
  ============================================================ */
366
367
 
367
368
  @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.4",
4
4
  "description": "Zero-config skeleton loaders. Wrap your component, done.",
5
5
  "license": "MIT",
6
6
  "type": "module",