strata-css 1.0.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -9
- package/bin/strata.js +310 -115
- package/package.json +22 -12
- package/src/components/modules/chart/src/chart.ts +1131 -0
- package/src/components/modules/chart/src/three-global.d.ts +188 -0
- package/src/components/modules/chart/tsconfig.json +19 -0
- package/src/components/modules/init.js +7 -0
- package/src/components/strata.manifest.js +19 -15
- package/src/layers/base.js +577 -571
- package/src/registry/registry.js +3256 -2897
- package/src/components/modules/modal.js +0 -123
- package/src/components/modules/skeleton.js +0 -334
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Strata Modal Component
|
|
3
|
-
*
|
|
4
|
-
* Usage:
|
|
5
|
-
* Trigger: <button data-st-toggle="modal" data-st-target="#myModal">Open</button>
|
|
6
|
-
* Dismiss: <button data-st-dismiss="modal">Close</button>
|
|
7
|
-
* Static: <div class="modal" data-st-backdrop="static" ...>
|
|
8
|
-
* API: Strata.Modal.open('#myModal') / Strata.Modal.close()
|
|
9
|
-
*
|
|
10
|
-
* Events fired on document:
|
|
11
|
-
* st:modal:open — detail: { modal }
|
|
12
|
-
* st:modal:close — detail: { modal }
|
|
13
|
-
*/
|
|
14
|
-
;(function (win, doc) {
|
|
15
|
-
'use strict'
|
|
16
|
-
|
|
17
|
-
var currentModal = null
|
|
18
|
-
var backdrop = null
|
|
19
|
-
|
|
20
|
-
function ensureBackdrop() {
|
|
21
|
-
if (!backdrop) {
|
|
22
|
-
backdrop = doc.createElement('div')
|
|
23
|
-
backdrop.className = 'modal-backdrop'
|
|
24
|
-
doc.body.appendChild(backdrop)
|
|
25
|
-
}
|
|
26
|
-
return backdrop
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function lockScroll() {
|
|
30
|
-
var sbw = win.innerWidth - doc.documentElement.clientWidth
|
|
31
|
-
doc.body.style.setProperty('--st-scrollbar-width', sbw + 'px')
|
|
32
|
-
doc.body.classList.add('modal-open')
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function unlockScroll() {
|
|
36
|
-
doc.body.classList.remove('modal-open')
|
|
37
|
-
doc.body.style.removeProperty('--st-scrollbar-width')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function openModal(modal) {
|
|
41
|
-
if (currentModal === modal) return
|
|
42
|
-
if (currentModal) closeModal()
|
|
43
|
-
|
|
44
|
-
currentModal = modal
|
|
45
|
-
modal.setAttribute('data-st-visible', 'true')
|
|
46
|
-
modal.removeAttribute('aria-hidden')
|
|
47
|
-
modal.setAttribute('aria-modal', 'true')
|
|
48
|
-
|
|
49
|
-
var bd = ensureBackdrop()
|
|
50
|
-
void bd.offsetHeight // force reflow so transition plays
|
|
51
|
-
bd.setAttribute('data-st-visible', 'true')
|
|
52
|
-
|
|
53
|
-
lockScroll()
|
|
54
|
-
|
|
55
|
-
var focusTarget = modal.querySelector('[autofocus]') ||
|
|
56
|
-
modal.querySelector('.modal-content')
|
|
57
|
-
if (focusTarget) setTimeout(function() { focusTarget.focus() }, 50)
|
|
58
|
-
|
|
59
|
-
doc.dispatchEvent(new CustomEvent('st:modal:open', { detail: { modal: modal } }))
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function closeModal() {
|
|
63
|
-
if (!currentModal) return
|
|
64
|
-
|
|
65
|
-
var modal = currentModal
|
|
66
|
-
currentModal = null
|
|
67
|
-
|
|
68
|
-
modal.setAttribute('data-st-visible', 'false')
|
|
69
|
-
modal.setAttribute('aria-hidden', 'true')
|
|
70
|
-
modal.removeAttribute('aria-modal')
|
|
71
|
-
|
|
72
|
-
if (backdrop) backdrop.setAttribute('data-st-visible', 'false')
|
|
73
|
-
|
|
74
|
-
unlockScroll()
|
|
75
|
-
|
|
76
|
-
doc.dispatchEvent(new CustomEvent('st:modal:close', { detail: { modal: modal } }))
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
doc.addEventListener('click', function(e) {
|
|
80
|
-
var trigger = e.target.closest('[data-st-toggle="modal"]')
|
|
81
|
-
if (trigger) {
|
|
82
|
-
var sel = trigger.getAttribute('data-st-target') || trigger.getAttribute('href')
|
|
83
|
-
if (sel) {
|
|
84
|
-
var target = doc.querySelector(sel)
|
|
85
|
-
if (target) openModal(target)
|
|
86
|
-
}
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (e.target.closest('[data-st-dismiss="modal"]')) {
|
|
91
|
-
closeModal()
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (currentModal && e.target === currentModal) {
|
|
96
|
-
var isStatic = currentModal.getAttribute('data-st-backdrop') === 'static'
|
|
97
|
-
if (isStatic) {
|
|
98
|
-
currentModal.classList.add('modal-static')
|
|
99
|
-
var m = currentModal
|
|
100
|
-
setTimeout(function() { m.classList.remove('modal-static') }, 300)
|
|
101
|
-
} else {
|
|
102
|
-
closeModal()
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
doc.addEventListener('keydown', function(e) {
|
|
108
|
-
if (e.key === 'Escape' && currentModal) {
|
|
109
|
-
var isStatic = currentModal.getAttribute('data-st-backdrop') === 'static'
|
|
110
|
-
if (!isStatic) closeModal()
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
win.Strata = win.Strata || {}
|
|
115
|
-
win.Strata.Modal = {
|
|
116
|
-
open: function(selector) {
|
|
117
|
-
var el = typeof selector === 'string' ? doc.querySelector(selector) : selector
|
|
118
|
-
if (el) openModal(el)
|
|
119
|
-
},
|
|
120
|
-
close: closeModal
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
}(window, document))
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Strata Skeleton JS Utility
|
|
3
|
-
* Version: 1.0.0
|
|
4
|
-
*
|
|
5
|
-
* Smart detection and lifecycle management for skeleton loaders.
|
|
6
|
-
* JS handles detection and toggling only.
|
|
7
|
-
* CSS handles all visual rendering via @layer st-skeleton.
|
|
8
|
-
*
|
|
9
|
-
* Four attribute states:
|
|
10
|
-
* "true" — element shimmers (CSS applies ::before overlay)
|
|
11
|
-
* "false" — element revealed (no shimmer)
|
|
12
|
-
* "null" — JS managed parent (no overlay, children shimmer individually)
|
|
13
|
-
*
|
|
14
|
-
* Key fix: replaced elements (img, video, iframe) cannot have ::before
|
|
15
|
-
* pseudo-elements in browsers. JS marks their WRAPPER div with "true"
|
|
16
|
-
* instead so the wrapper's ::before overlay covers the replaced content.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
;(function (global) {
|
|
20
|
-
'use strict'
|
|
21
|
-
|
|
22
|
-
// ─── Element type sets ───────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
// Content elements that support ::before — become individual skeleton bars
|
|
25
|
-
const CONTENT_TAGS = new Set([
|
|
26
|
-
'P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
|
|
27
|
-
'SPAN', 'A', 'LABEL', 'STRONG', 'EM', 'SMALL',
|
|
28
|
-
'BUTTON', 'INPUT', 'TEXTAREA', 'SELECT',
|
|
29
|
-
'LI', 'DT', 'DD', 'BLOCKQUOTE', 'FIGCAPTION'
|
|
30
|
-
// NOTE: IMG, VIDEO, IFRAME are NOT here —
|
|
31
|
-
// replaced elements cannot have ::before.
|
|
32
|
-
// Their parent WRAPPER is marked instead.
|
|
33
|
-
])
|
|
34
|
-
|
|
35
|
-
// Replaced elements — ::before not supported by browsers
|
|
36
|
-
// JS marks their wrapper parent instead
|
|
37
|
-
const REPLACED_TAGS = new Set([
|
|
38
|
-
'IMG', 'VIDEO', 'IFRAME', 'PICTURE', 'CANVAS', 'SVG'
|
|
39
|
-
])
|
|
40
|
-
|
|
41
|
-
// Structural elements — skipped, children scanned
|
|
42
|
-
// UNLESS they contain only replaced elements, in which case
|
|
43
|
-
// the structural element itself becomes the skeleton bar
|
|
44
|
-
const STRUCTURAL_TAGS = new Set([
|
|
45
|
-
'DIV', 'SECTION', 'ARTICLE', 'MAIN', 'ASIDE',
|
|
46
|
-
'HEADER', 'FOOTER', 'NAV', 'UL', 'OL', 'DL',
|
|
47
|
-
'FIGURE', 'FORM', 'FIELDSET'
|
|
48
|
-
])
|
|
49
|
-
|
|
50
|
-
// Tags to ignore completely
|
|
51
|
-
const IGNORE_TAGS = new Set([
|
|
52
|
-
'SCRIPT', 'STYLE', 'NOSCRIPT', 'META', 'LINK',
|
|
53
|
-
'HEAD', 'HTML', 'BODY', 'BR', 'HR', 'WBR'
|
|
54
|
-
])
|
|
55
|
-
|
|
56
|
-
// ─── Registry ────────────────────────────────────────────────────────
|
|
57
|
-
|
|
58
|
-
const registry = new Map()
|
|
59
|
-
|
|
60
|
-
// ─── Smart Detection ─────────────────────────────────────────────────
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Check if an element has direct text nodes (not just whitespace)
|
|
64
|
-
*/
|
|
65
|
-
function hasDirectText(el) {
|
|
66
|
-
return Array.from(el.childNodes).some(
|
|
67
|
-
node => node.nodeType === Node.TEXT_NODE
|
|
68
|
-
&& node.textContent.trim().length > 0
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Check if a structural element contains ONLY replaced elements.
|
|
74
|
-
* If so, the structural element itself becomes the skeleton bar
|
|
75
|
-
* because its ::before overlay covers the replaced content.
|
|
76
|
-
*
|
|
77
|
-
* Example: <div class="card-img-wrap"><img src="..."></div>
|
|
78
|
-
* → card-img-wrap gets "true", its ::before covers the img
|
|
79
|
-
*/
|
|
80
|
-
function hasOnlyReplacedChildren(el) {
|
|
81
|
-
const meaningful = Array.from(el.children).filter(
|
|
82
|
-
c => !IGNORE_TAGS.has(c.tagName)
|
|
83
|
-
)
|
|
84
|
-
return meaningful.length > 0
|
|
85
|
-
&& meaningful.every(c => REPLACED_TAGS.has(c.tagName))
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Walk the DOM from a parent and find all leaf nodes.
|
|
90
|
-
*
|
|
91
|
-
* Rules:
|
|
92
|
-
* - Content tags (p, h3, button, a etc.) → mark as skeleton bar
|
|
93
|
-
* - Structural wrapper containing ONLY replaced elements (img, video) →
|
|
94
|
-
* mark the WRAPPER as skeleton bar (its ::before covers the media)
|
|
95
|
-
* - Structural wrapper with direct text → mark as skeleton bar
|
|
96
|
-
* - Structural wrapper with mixed children → recurse
|
|
97
|
-
* - Replaced elements (img, video) as direct children → skip
|
|
98
|
-
* (handled by their parent wrapper rule above)
|
|
99
|
-
* - data-st-skeleton="false" → skip element and all descendants
|
|
100
|
-
* - data-st-skeleton="null" → skip (another managed parent)
|
|
101
|
-
*/
|
|
102
|
-
function detectLeaves(parent) {
|
|
103
|
-
const leaves = []
|
|
104
|
-
|
|
105
|
-
function walk(el) {
|
|
106
|
-
// Skip opted-out elements entirely
|
|
107
|
-
if (el.getAttribute('data-st-skeleton') === 'false') return
|
|
108
|
-
|
|
109
|
-
// Skip other managed JS parents
|
|
110
|
-
if (el !== parent && el.getAttribute('data-st-skeleton') === 'null') return
|
|
111
|
-
|
|
112
|
-
// Skip ignored tags
|
|
113
|
-
if (IGNORE_TAGS.has(el.tagName)) return
|
|
114
|
-
|
|
115
|
-
const tag = el.tagName
|
|
116
|
-
|
|
117
|
-
// Content element — supports ::before, mark as leaf
|
|
118
|
-
if (CONTENT_TAGS.has(tag)) {
|
|
119
|
-
leaves.push(el)
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Replaced element as direct child of managed parent
|
|
124
|
-
// Cannot mark it — no ::before support. Skip it.
|
|
125
|
-
// Ideally developer wraps their images in a div.
|
|
126
|
-
if (REPLACED_TAGS.has(tag)) {
|
|
127
|
-
return
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Structural element — decide whether to mark or recurse
|
|
131
|
-
if (STRUCTURAL_TAGS.has(tag)) {
|
|
132
|
-
|
|
133
|
-
// CASE 1: Contains only replaced elements (img, video etc.)
|
|
134
|
-
// Mark THIS structural element — its ::before covers the media
|
|
135
|
-
if (hasOnlyReplacedChildren(el)) {
|
|
136
|
-
leaves.push(el)
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// CASE 2: Has direct text — mark as leaf
|
|
141
|
-
if (hasDirectText(el)) {
|
|
142
|
-
leaves.push(el)
|
|
143
|
-
return
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// CASE 3: Mixed children — recurse into children
|
|
147
|
-
Array.from(el.children).forEach(child => walk(child))
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Unknown tag — recurse into children
|
|
152
|
-
Array.from(el.children).forEach(child => walk(child))
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Walk children — not the parent itself
|
|
156
|
-
Array.from(parent.children).forEach(child => walk(child))
|
|
157
|
-
|
|
158
|
-
return leaves
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ─── Core ─────────────────────────────────────────────────────────────
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Register a parent for JS management.
|
|
165
|
-
* Sets parent to "null" — neutral container, no parent overlay.
|
|
166
|
-
* Runs smart detection and marks leaf children with "true".
|
|
167
|
-
*/
|
|
168
|
-
function manage(parent, options = {}) {
|
|
169
|
-
if (!parent || registry.has(parent)) return
|
|
170
|
-
|
|
171
|
-
const leaves = detectLeaves(parent)
|
|
172
|
-
registry.set(parent, { leaves, options })
|
|
173
|
-
|
|
174
|
-
// Set parent to "null" — removes parent ::before overlay
|
|
175
|
-
// so only individual leaf shimmers show
|
|
176
|
-
parent.setAttribute('data-st-skeleton', 'null')
|
|
177
|
-
|
|
178
|
-
// Mark detected leaves as skeleton
|
|
179
|
-
leaves.forEach(leaf => leaf.setAttribute('data-st-skeleton', 'true'))
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Show skeleton — parent to "null", leaves to "true"
|
|
184
|
-
*/
|
|
185
|
-
function show(selector) {
|
|
186
|
-
const targets = resolveTargets(selector)
|
|
187
|
-
targets.forEach(parent => {
|
|
188
|
-
const entry = registry.get(parent)
|
|
189
|
-
if (!entry) return
|
|
190
|
-
parent.setAttribute('data-st-skeleton', 'null')
|
|
191
|
-
entry.leaves.forEach(leaf => leaf.setAttribute('data-st-skeleton', 'true'))
|
|
192
|
-
})
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Reveal content — parent to "false", leaves to "false"
|
|
197
|
-
* Attribute stays in DOM — only value changes.
|
|
198
|
-
*/
|
|
199
|
-
function reveal(selector, options = {}) {
|
|
200
|
-
if (typeof selector === 'object' && !isSelector(selector)) {
|
|
201
|
-
options = selector
|
|
202
|
-
selector = null
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const targets = resolveTargets(selector)
|
|
206
|
-
const stagger = options.stagger || 0
|
|
207
|
-
const onReveal = options.onReveal || null
|
|
208
|
-
|
|
209
|
-
targets.forEach((parent, index) => {
|
|
210
|
-
const entry = registry.get(parent)
|
|
211
|
-
if (!entry) return
|
|
212
|
-
|
|
213
|
-
setTimeout(() => {
|
|
214
|
-
parent.setAttribute('data-st-skeleton', 'false')
|
|
215
|
-
entry.leaves.forEach(leaf => leaf.setAttribute('data-st-skeleton', 'false'))
|
|
216
|
-
|
|
217
|
-
if (onReveal && index === targets.length - 1) {
|
|
218
|
-
setTimeout(onReveal, 0)
|
|
219
|
-
}
|
|
220
|
-
}, stagger * index)
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Toggle between skeleton and revealed
|
|
226
|
-
*/
|
|
227
|
-
function toggle(selector) {
|
|
228
|
-
const targets = resolveTargets(selector)
|
|
229
|
-
targets.forEach(parent => {
|
|
230
|
-
const val = parent.getAttribute('data-st-skeleton')
|
|
231
|
-
val === 'false' ? show(parent) : reveal(parent)
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Reveal one element by index within a selector group
|
|
237
|
-
*/
|
|
238
|
-
function revealAt(selector, index) {
|
|
239
|
-
const targets = resolveTargets(selector)
|
|
240
|
-
const parent = targets[index]
|
|
241
|
-
if (parent) reveal(parent)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Check if element is currently in skeleton state
|
|
246
|
-
*/
|
|
247
|
-
function isSkeleton(el) {
|
|
248
|
-
const val = el.getAttribute('data-st-skeleton')
|
|
249
|
-
return val === 'true' || val === 'null'
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ─── Init ─────────────────────────────────────────────────────────────
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Initialise the skeleton utility.
|
|
256
|
-
*
|
|
257
|
-
* No selector: auto-discovers all [data-st-skeleton="true"] top-level parents.
|
|
258
|
-
* With selector: manages all matching elements.
|
|
259
|
-
*/
|
|
260
|
-
function init(selector, options = {}) {
|
|
261
|
-
let parents
|
|
262
|
-
|
|
263
|
-
if (!selector) {
|
|
264
|
-
// Auto-discover top-level skeleton parents
|
|
265
|
-
// Filter out elements that are children of another skeleton parent
|
|
266
|
-
parents = Array.from(
|
|
267
|
-
document.querySelectorAll('[data-st-skeleton="true"]')
|
|
268
|
-
).filter(el => !el.parentElement.closest('[data-st-skeleton]'))
|
|
269
|
-
|
|
270
|
-
} else {
|
|
271
|
-
parents = resolveRaw(selector)
|
|
272
|
-
|
|
273
|
-
// Set initial attribute if missing
|
|
274
|
-
parents.forEach(parent => {
|
|
275
|
-
if (!parent.hasAttribute('data-st-skeleton')) {
|
|
276
|
-
parent.setAttribute('data-st-skeleton', 'true')
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
parents.forEach(parent => manage(parent, options))
|
|
282
|
-
return api
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
286
|
-
|
|
287
|
-
function resolveTargets(selector) {
|
|
288
|
-
if (!selector) return Array.from(registry.keys())
|
|
289
|
-
if (selector instanceof Element) return registry.has(selector) ? [selector] : []
|
|
290
|
-
if (selector instanceof NodeList || Array.isArray(selector)) {
|
|
291
|
-
return Array.from(selector).filter(el => registry.has(el))
|
|
292
|
-
}
|
|
293
|
-
if (typeof selector === 'string') {
|
|
294
|
-
return Array.from(document.querySelectorAll(selector))
|
|
295
|
-
.filter(el => registry.has(el))
|
|
296
|
-
}
|
|
297
|
-
return []
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function resolveRaw(selector) {
|
|
301
|
-
if (!selector) return []
|
|
302
|
-
if (selector instanceof Element) return [selector]
|
|
303
|
-
if (selector instanceof NodeList || Array.isArray(selector)) return Array.from(selector)
|
|
304
|
-
if (typeof selector === 'string') return Array.from(document.querySelectorAll(selector))
|
|
305
|
-
return []
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function isSelector(val) {
|
|
309
|
-
return typeof val === 'string'
|
|
310
|
-
|| val instanceof Element
|
|
311
|
-
|| val instanceof NodeList
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// ─── Public API ───────────────────────────────────────────────────────
|
|
315
|
-
|
|
316
|
-
const api = {
|
|
317
|
-
init,
|
|
318
|
-
show,
|
|
319
|
-
reveal,
|
|
320
|
-
toggle,
|
|
321
|
-
revealAt,
|
|
322
|
-
isSkeleton,
|
|
323
|
-
manage: (selector, options) => {
|
|
324
|
-
resolveRaw(selector).forEach(el => manage(el, options))
|
|
325
|
-
return api
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// ─── Export ───────────────────────────────────────────────────────────
|
|
330
|
-
|
|
331
|
-
if (!global.Strata) global.Strata = {}
|
|
332
|
-
global.Strata.skeleton = api
|
|
333
|
-
|
|
334
|
-
})(window)
|