skeleton-loader 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +401 -0
- data/app/assets/javascripts/skeleton_loader.js +2 -0
- data/app/assets/javascripts/skeleton_loader.js.LICENSE.txt +1 -0
- data/app/assets/stylesheets/skeleton_loader.css +83 -0
- data/app/controllers/skeleton_loader/skeleton_loader_controller.rb +60 -0
- data/app/javascript/client_skeleton_loader.js +234 -0
- data/app/javascript/server_skeleton_loader.js +113 -0
- data/app/javascript/skeleton_loader.js +6 -0
- data/config/routes.rb +5 -0
- data/lib/generators/skeleton_loader/add_templates_generator.rb +16 -0
- data/lib/generators/skeleton_loader/reset_templates_generator.rb +16 -0
- data/lib/generators/skeleton_loader/templates/_card.html.erb +31 -0
- data/lib/generators/skeleton_loader/templates/_comment.html.erb +61 -0
- data/lib/generators/skeleton_loader/templates/_default.html.erb +23 -0
- data/lib/generators/skeleton_loader/templates/_gallery.html.erb +19 -0
- data/lib/generators/skeleton_loader/templates/_paragraph.html.erb +28 -0
- data/lib/generators/skeleton_loader/templates/_product.html.erb +49 -0
- data/lib/generators/skeleton_loader/templates/_profile.html.erb +28 -0
- data/lib/skeleton-loader.rb +7 -0
- data/lib/skeleton_loader/configuration.rb +68 -0
- data/lib/skeleton_loader/engine.rb +42 -0
- data/lib/skeleton_loader/skeleton_element_generator.rb +49 -0
- data/lib/skeleton_loader/template_path_finder.rb +24 -0
- data/lib/skeleton_loader/template_renderer.rb +41 -0
- data/lib/skeleton_loader/version.rb +5 -0
- data/lib/skeleton_loader/view_helpers.rb +19 -0
- data/lib/skeleton_loader.rb +36 -0
- data/lib/tasks/skeleton_loader_tasks.rake +23 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c1f3870a383b99e72e3c7536797fba096526f8b9d72659f09f718c4a7790c16f
|
4
|
+
data.tar.gz: 19e17acfcf43058cba8ce8acc1040be050e078e14a73ee8bf2d30c0b6bb67ed3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d0c4b8979f8984131fb39a49fb95e459b090911cf96f78fd72dfa9e64cd9eaf6d8993900be5eccbb0a341200a586bb6e83c6f319b7f68eaf47ce7f8614c48f03
|
7
|
+
data.tar.gz: c8bbf8deb35d75bed0d5ed768a0ca5e8f36a411e85667c1cf594ba1edc93f386ec39f52ad52d9876374c09c2fab00f13f4bf1b73ffa08114b63ae039fbb75473
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Emad Rahimi
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,401 @@
|
|
1
|
+
# Skeleton Loader
|
2
|
+
|
3
|
+

|
4
|
+
[](https://dl.circleci.com/status-badge/redirect/circleci/8MamMcAVAVNWTcUqkjQk7R/Sh2DQkMWqqCv4MFvAmYWDL/tree/main)
|
5
|
+

|
6
|
+
[](https://opensource.org/licenses/MIT)
|
7
|
+
|
8
|
+
**Skeleton Loader** is a Ruby on Rails gem for creating animated placeholders that enhance loading states. Whether rendered through Rails views or dynamically with JavaScript, these skeletons provide a seamless visual experience while content loads.
|
9
|
+
|
10
|
+
**⚠ Note:** Skeleton Loader is an experimental gem currently in early development, exploring the possibilities in the magical world of Rails gems. While loaders are ideally handled client-side, this gem aims to make it easy to add placeholders directly within the Rails realm.
|
11
|
+
|
12
|
+
---
|
13
|
+
|
14
|
+
## Table of Contents
|
15
|
+
|
16
|
+
- [Features](#features)
|
17
|
+
- [Installation](#installation)
|
18
|
+
- [Setup](#setup)
|
19
|
+
- [Rails Integration](#rails-integration)
|
20
|
+
- [JavaScript Integration](#javascript-integration)
|
21
|
+
- [Configuration](#configuration)
|
22
|
+
- [Code Quality](#code-quality)
|
23
|
+
- [Roadmap](#roadmap)
|
24
|
+
- [Contributing](#contributing)
|
25
|
+
- [License](#license)
|
26
|
+
|
27
|
+
---
|
28
|
+
|
29
|
+

|
30
|
+
|
31
|
+
## Features
|
32
|
+
|
33
|
+
- 🚀 **Seamless Rails integration:** Enhances loading transitions with minimal setup.
|
34
|
+
- 🔨 **Universal skeletons:** Creates loading states in Rails views & JavaScript.
|
35
|
+
- ⚙️ **Consistency:** Unified templates & options across both environments.
|
36
|
+
- ⚡ **Lightweight & fast:** Designed to be fast and easy to implement with minimal dependencies.
|
37
|
+
|
38
|
+
---
|
39
|
+
|
40
|
+
## Installation
|
41
|
+
|
42
|
+
Add this line to your application’s Gemfile:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
gem "skeleton-loader"
|
46
|
+
```
|
47
|
+
|
48
|
+
Then, run:
|
49
|
+
|
50
|
+
```bash
|
51
|
+
bundle install
|
52
|
+
```
|
53
|
+
|
54
|
+
#### Requirements
|
55
|
+
|
56
|
+
- **Ruby** 2.5 or higher
|
57
|
+
- **Rails** 5.0 or higher
|
58
|
+
- **Asset Pipeline** (Required for CSS handling)
|
59
|
+
|
60
|
+
---
|
61
|
+
|
62
|
+
## Setup
|
63
|
+
|
64
|
+
### CSS Assets
|
65
|
+
|
66
|
+
The gem's CSS is always managed via the Asset Pipeline:
|
67
|
+
```javascript
|
68
|
+
/* app/assets/stylesheets/application.css */
|
69
|
+
*= require skeleton_loader
|
70
|
+
```
|
71
|
+
|
72
|
+
### JavaScript Assets
|
73
|
+
|
74
|
+
Include the JavaScript functionality using Asset Pipeline, Webpack, or Importmap, depending on your setup:
|
75
|
+
|
76
|
+
|
77
|
+
#### **Option 1: Asset Pipeline (Default for Rails 5)**
|
78
|
+
|
79
|
+
In `app/assets/javascripts/application.js`, add:
|
80
|
+
|
81
|
+
```javascript
|
82
|
+
//= require skeleton_loader
|
83
|
+
```
|
84
|
+
|
85
|
+
#### **Option 2: Webpack (Default for Rails 6)**
|
86
|
+
|
87
|
+
1. Install the package using Yarn:
|
88
|
+
|
89
|
+
```bash
|
90
|
+
yarn add "@ersync/skeleton-loader"
|
91
|
+
```
|
92
|
+
|
93
|
+
```javascript
|
94
|
+
import SkeletonLoader from "skeleton-loader"
|
95
|
+
```
|
96
|
+
|
97
|
+
#### **Option 3: Importmap (Default for Rails 7)**
|
98
|
+
|
99
|
+
1. Pin the package in `config/importmap.rb`:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
pin "skeleton-loader", to: "skeleton_loader.js"
|
103
|
+
```
|
104
|
+
|
105
|
+
```javascript
|
106
|
+
import SkeletonLoader from "skeleton-loader"
|
107
|
+
```
|
108
|
+
|
109
|
+
### Install Templates
|
110
|
+
|
111
|
+
To install predefined templates, run:
|
112
|
+
|
113
|
+
```bash
|
114
|
+
rails generate skeleton_loader:add_templates
|
115
|
+
```
|
116
|
+
|
117
|
+
This adds templates to `app/views/skeleton_loader/`. Customize them as needed.
|
118
|
+
|
119
|
+
To restore defaults later:
|
120
|
+
|
121
|
+
```bash
|
122
|
+
rails generate skeleton_loader:reset_templates
|
123
|
+
```
|
124
|
+
|
125
|
+
---
|
126
|
+
|
127
|
+
## Rails Integration
|
128
|
+
|
129
|
+
The gem provides a primary view helper, `skeleton_loader`, which generates a skeleton that is automatically replaced with content once loading completes. You can use it with predefined templates or define your own HTML blocks.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
skeleton_loader(
|
133
|
+
content_id:, # Required. Target element ID for replacement
|
134
|
+
**options, # Optional. Customize the template
|
135
|
+
&block # Optional. Define custom skeleton HTML (excludes type/options)
|
136
|
+
)
|
137
|
+
```
|
138
|
+
|
139
|
+
### 1. Pre-defined Templates
|
140
|
+
|
141
|
+
Specify `content_id` and, optionally, a template `type` and customization `options`.
|
142
|
+
|
143
|
+
```erb
|
144
|
+
<%= skeleton_loader(content_id: 'content-element',
|
145
|
+
type: "card",
|
146
|
+
count: 5,
|
147
|
+
scale: 1.2,
|
148
|
+
animation_type: 'animation-pulse') %>
|
149
|
+
```
|
150
|
+
|
151
|
+
### 2. Custom Skeletons with a Block
|
152
|
+
|
153
|
+
Define your own HTML structure in a block. Note that `type` and `options` are not used.
|
154
|
+
|
155
|
+
```erb
|
156
|
+
<%= skeleton_loader(content_id: 'content-element') do %>
|
157
|
+
<div class="custom-skeleton">
|
158
|
+
<div class="avatar skeleton-circle"></div>
|
159
|
+
<div class="text-lines"></div>
|
160
|
+
</div>
|
161
|
+
<% end %>
|
162
|
+
```
|
163
|
+
|
164
|
+
See [Configuration](#configuration) for all available options.
|
165
|
+
|
166
|
+
---
|
167
|
+
|
168
|
+
## JavaScript Integration
|
169
|
+
|
170
|
+
Skeleton Loader also provides a JavaScript API to dynamically create skeletons independently of Rails views.
|
171
|
+
|
172
|
+
<details>
|
173
|
+
<summary>💡 You only need this API if you're loading content asynchronously i.e. AJAX calls. For standard Rails views, use the Rails helper method instead.</summary>
|
174
|
+
|
175
|
+
### 1. Basic Setup
|
176
|
+
|
177
|
+
First, initialize the SkeletonLoader in your JavaScript:
|
178
|
+
|
179
|
+
```javascript
|
180
|
+
const skeleton = new SkeletonLoader();
|
181
|
+
```
|
182
|
+
|
183
|
+
### 2. Creating Skeletons
|
184
|
+
|
185
|
+
There are two methods for creating skeletons using JavaScript:
|
186
|
+
|
187
|
+
#### Using Pre-defined Templates
|
188
|
+
|
189
|
+
```javascript
|
190
|
+
const loader = skeleton.render({
|
191
|
+
contentId, // Required. ID of the element to replace with skeleton
|
192
|
+
...options // Optional. See configuration section for available options
|
193
|
+
});
|
194
|
+
```
|
195
|
+
|
196
|
+
#### Creating Custom Skeletons
|
197
|
+
|
198
|
+
```javascript
|
199
|
+
const loader = skeleton.renderCustom({
|
200
|
+
contentId, // Required. ID of the element to replace with skeleton
|
201
|
+
markup // Required. Custom HTML string for skeleton content
|
202
|
+
});
|
203
|
+
```
|
204
|
+
|
205
|
+
### 3. Managing Loading States
|
206
|
+
|
207
|
+
Each skeleton instance (both `render()` and `renderCustom()`) returns an object with these methods:
|
208
|
+
|
209
|
+
- `isLoading()`: Returns the current loading state
|
210
|
+
- `reveal()`: Removes skeleton and displays content
|
211
|
+
|
212
|
+
```javascript
|
213
|
+
// Check if still loading
|
214
|
+
if (loader.isLoading()) {
|
215
|
+
// Do something while content loads
|
216
|
+
}
|
217
|
+
|
218
|
+
// Remove skeleton and show content
|
219
|
+
loader.reveal();
|
220
|
+
```
|
221
|
+
|
222
|
+
<details>
|
223
|
+
<summary><strong>Practical Examples</strong></summary>
|
224
|
+
|
225
|
+
Here's some example showing how to use the skeleton loader with an API call:
|
226
|
+
|
227
|
+
```javascript
|
228
|
+
async function loadUserProfile(userId) {
|
229
|
+
// Create skeleton while loading
|
230
|
+
const loader = skeleton.render({
|
231
|
+
contentId: 'content-element',
|
232
|
+
type: 'profile'
|
233
|
+
});
|
234
|
+
|
235
|
+
try {
|
236
|
+
// Fetch your data
|
237
|
+
const response = await fetch(`/api/users/${userId}`);
|
238
|
+
const userData = await response.json();
|
239
|
+
|
240
|
+
// Update the DOM with your content
|
241
|
+
document.getElementById('content-element').innerHTML = createUserProfile(userData);
|
242
|
+
|
243
|
+
// Remove the skeleton
|
244
|
+
loader.reveal();
|
245
|
+
} catch (error) {
|
246
|
+
console.error('Failed to load user profile:', error);
|
247
|
+
loader.reveal(); // Always cleanup the skeleton
|
248
|
+
}
|
249
|
+
}
|
250
|
+
```
|
251
|
+
|
252
|
+
```javascript
|
253
|
+
async function loadDashboard() {
|
254
|
+
// Create multiple skeletons
|
255
|
+
const loaders = {
|
256
|
+
profile: skeleton.render({ contentId: 'profile', type: 'profile', scale:1.2, width:250 }),
|
257
|
+
stats: skeleton.render({ contentId: 'stats', type: 'card', scale:1.3, animationType:"sl-flow" }),
|
258
|
+
activities: skeleton.render({ contentId: 'activities', type: 'list' })
|
259
|
+
};
|
260
|
+
|
261
|
+
// Load your data
|
262
|
+
const [profileData, statsData, activitiesData] = await Promise.all([
|
263
|
+
fetchProfile(),
|
264
|
+
fetchStats(),
|
265
|
+
fetchActivities()
|
266
|
+
]);
|
267
|
+
|
268
|
+
// Update content and reveal each section
|
269
|
+
updateProfile(profileData);
|
270
|
+
loaders.profile.reveal();
|
271
|
+
|
272
|
+
updateStats(statsData);
|
273
|
+
loaders.stats.reveal();
|
274
|
+
|
275
|
+
updateActivities(activitiesData);
|
276
|
+
loaders.activities.reveal();
|
277
|
+
}
|
278
|
+
```
|
279
|
+
</details>
|
280
|
+
</details>
|
281
|
+
|
282
|
+
---
|
283
|
+
|
284
|
+
## Configuration
|
285
|
+
|
286
|
+
### Options
|
287
|
+
|
288
|
+
The following options can be passed to both the Rails view helper and JavaScript API. Note that Rails uses snake_case (e.g., `animation_type`), while JavaScript uses camelCase (e.g., `animationType`).
|
289
|
+
|
290
|
+
|
291
|
+
| Option | Type | Description | Default | Example |
|
292
|
+
|--------|------|-------------|---------|---------|
|
293
|
+
| `content_id` | String | Unique identifier for the skeleton loader container | `nil` | `"content-element"` |
|
294
|
+
| `type` | String | Predefined template type (e.g., "product", "profile") | `"default"` | `"product"` |
|
295
|
+
| `width` | Integer | Base width of a single skeleton item in pixels | Depends on `type` | `250` |
|
296
|
+
| `count` | Integer | Number of skeleton items to render | Depends on `type` | `6` |
|
297
|
+
| `per_row` | Integer | Number of skeleton items displayed per row | Depends on `type` | `4` |
|
298
|
+
| `scale` | Float | Size multiplier for all skeleton item dimensions | `1.0` | `1.2` |
|
299
|
+
| `animation_type` | String | Type of loading animation | `"sl-gradient"` | `"sl-glow"` |
|
300
|
+
|
301
|
+
**Notes:**
|
302
|
+
- `type` determines default layout and styling
|
303
|
+
- `scale` affects width and spacing proportionally
|
304
|
+
- `animation_type` supports different loading effect styles
|
305
|
+
|
306
|
+
### Available Templates
|
307
|
+
|
308
|
+
Skeleton Loader comes with several pre-built templates, each with their **default** configurations:
|
309
|
+
|
310
|
+
| Template Type | Default Width | Count | Items Per Row |
|
311
|
+
|--------------|---------------|-------|---------------|
|
312
|
+
| `card` | 200px | 3 | 3 |
|
313
|
+
| `comment` | 900px | 2 | 1 |
|
314
|
+
| `default` | 900px | 1 | 1 |
|
315
|
+
| `gallery` | 320px | 3 | 3 |
|
316
|
+
| `paragraph` | 900px | 1 | 1 |
|
317
|
+
| `product` | 320px | 3 | 3 |
|
318
|
+
| `profile` | 320px | 3 | 3 |
|
319
|
+
|
320
|
+
For an interactive preview of available templates and animations, visit the [Live Demo](https://ersync.github.io/skeleton-loader/).
|
321
|
+
|
322
|
+
### Available Animations
|
323
|
+
|
324
|
+
Choose from several animation styles to match your design:
|
325
|
+
- `sl-gradient` (default): Smooth gradient movement
|
326
|
+
- `sl-shine`: Shimmer effect
|
327
|
+
- `sl-pulse`: Fade in/out
|
328
|
+
- `sl-flow`: Continuous flow
|
329
|
+
- `sl-neon`: Subtle glow
|
330
|
+
- `sl-breathing`: Gentle scaling
|
331
|
+
|
332
|
+
### Application Defaults
|
333
|
+
|
334
|
+
To set application-wide defaults, create `config/initializers/skeleton_loader.rb`:
|
335
|
+
|
336
|
+
```ruby
|
337
|
+
SkeletonLoader.configure do |config|
|
338
|
+
# Override default template settings
|
339
|
+
config.templates[:product] = {
|
340
|
+
width: 400,
|
341
|
+
count: 6,
|
342
|
+
per_row: 3
|
343
|
+
}
|
344
|
+
# Global settings
|
345
|
+
config.scale = 1.0 # Default: 1.0
|
346
|
+
config.animation_type = "sl-gradient" # Default: "sl-gradient"
|
347
|
+
end
|
348
|
+
```
|
349
|
+
|
350
|
+
### Advanced Configuration
|
351
|
+
|
352
|
+
For applications requiring custom HTML elements or styles:
|
353
|
+
|
354
|
+
```ruby
|
355
|
+
SkeletonLoader.configure do |config|
|
356
|
+
config.additional_allowed_tags = [] # Default: []
|
357
|
+
config.additional_allowed_attributes = {} # Default: {}
|
358
|
+
config.additional_allowed_css_properties = [] # Default: []
|
359
|
+
end
|
360
|
+
```
|
361
|
+
|
362
|
+
---
|
363
|
+
|
364
|
+
## Code Quality
|
365
|
+
|
366
|
+
Skeleton Loader maintains code quality through:
|
367
|
+
|
368
|
+
- **RSpec**: Comprehensive tests covering core functionality.
|
369
|
+
- **CircleCI**: Continuous integration ensures all new changes meet quality standards.
|
370
|
+
- **Rubocop**: Consistent linting aligns code with Ruby community conventions.
|
371
|
+
|
372
|
+
---
|
373
|
+
|
374
|
+
## Roadmap
|
375
|
+
|
376
|
+
Here are some features I'd like to add when I have time:
|
377
|
+
|
378
|
+
- **New Templates & Animations**: Expanding template and animation options.
|
379
|
+
- **Turbo & Stimulus Support**: Enhancing compatibility with Rails Turbo and StimulusJS.
|
380
|
+
- **Builder Helper for Custom Skeletons:**: Introducing a helper to easily create custom skeletons (e.g., circles, rectangles, etc.) with customizable options, simplifying the process of designing custom skeletons.
|
381
|
+
|
382
|
+
Suggestions and contributions are welcome!
|
383
|
+
|
384
|
+
---
|
385
|
+
|
386
|
+
## Contributing
|
387
|
+
|
388
|
+
To contribute:
|
389
|
+
|
390
|
+
1. Fork the repository.
|
391
|
+
2. Create a new feature branch.
|
392
|
+
3. Add tests for new features.
|
393
|
+
4. Commit your changes and submit a pull request.
|
394
|
+
|
395
|
+
Please follow the [Code of Conduct](https://github.com/ersync/skeleton-loader/blob/main/CODE_OF_CONDUCT.md) for all contributions.
|
396
|
+
|
397
|
+
---
|
398
|
+
|
399
|
+
## License
|
400
|
+
|
401
|
+
Skeleton Loader is licensed under the MIT License. See [LICENSE](https://github.com/ersync/skeleton-loader/blob/main/LICENSE) for details.
|
@@ -0,0 +1,2 @@
|
|
1
|
+
/*! For license information please see skeleton_loader.js.LICENSE.txt */
|
2
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SkeletonLoader=e():t.SkeletonLoader=e()}(this,(()=>(()=>{"use strict";var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};t.d(e,{default:()=>E});var r=["contentId","type"],n=["contentId","mode","markup","type"],o=["contentId","mode","markup","type"];function i(t){return i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i(t)}function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r<e;r++)n[r]=t[r];return n}function c(){c=function(){return e};var t,e={},r=Object.prototype,n=r.hasOwnProperty,o=Object.defineProperty||function(t,e,r){t[e]=r.value},a="function"==typeof Symbol?Symbol:{},u=a.iterator||"@@iterator",l=a.asyncIterator||"@@asyncIterator",s=a.toStringTag||"@@toStringTag";function f(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{f({},"")}catch(t){f=function(t,e,r){return t[e]=r}}function h(t,e,r,n){var i=e&&e.prototype instanceof b?e:b,a=Object.create(i.prototype),c=new I(n||[]);return o(a,"_invoke",{value:T(t,r,c)}),a}function p(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}e.wrap=h;var y="suspendedStart",d="suspendedYield",v="executing",m="completed",g={};function b(){}function w(){}function S(){}var k={};f(k,u,(function(){return this}));var E=Object.getPrototypeOf,L=E&&E(E(A([])));L&&L!==r&&n.call(L,u)&&(k=L);var O=S.prototype=b.prototype=Object.create(k);function x(t){["next","throw","return"].forEach((function(e){f(t,e,(function(t){return this._invoke(e,t)}))}))}function j(t,e){function r(o,a,c,u){var l=p(t[o],t,a);if("throw"!==l.type){var s=l.arg,f=s.value;return f&&"object"==i(f)&&n.call(f,"__await")?e.resolve(f.__await).then((function(t){r("next",t,c,u)}),(function(t){r("throw",t,c,u)})):e.resolve(f).then((function(t){s.value=t,c(s)}),(function(t){return r("throw",t,c,u)}))}u(l.arg)}var a;o(this,"_invoke",{value:function(t,n){function o(){return new e((function(e,o){r(t,n,e,o)}))}return a=a?a.then(o,o):o()}})}function T(e,r,n){var o=y;return function(i,a){if(o===v)throw Error("Generator is already running");if(o===m){if("throw"===i)throw a;return{value:t,done:!0}}for(n.method=i,n.arg=a;;){var c=n.delegate;if(c){var u=_(c,n);if(u){if(u===g)continue;return u}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(o===y)throw o=m,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o=v;var l=p(e,r,n);if("normal"===l.type){if(o=n.done?m:d,l.arg===g)continue;return{value:l.arg,done:n.done}}"throw"===l.type&&(o=m,n.method="throw",n.arg=l.arg)}}}function _(e,r){var n=r.method,o=e.iterator[n];if(o===t)return r.delegate=null,"throw"===n&&e.iterator.return&&(r.method="return",r.arg=t,_(e,r),"throw"===r.method)||"return"!==n&&(r.method="throw",r.arg=new TypeError("The iterator does not provide a '"+n+"' method")),g;var i=p(o,e.iterator,r.arg);if("throw"===i.type)return r.method="throw",r.arg=i.arg,r.delegate=null,g;var a=i.arg;return a?a.done?(r[e.resultName]=a.value,r.next=e.nextLoc,"return"!==r.method&&(r.method="next",r.arg=t),r.delegate=null,g):a:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,g)}function P(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function C(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function I(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(P,this),this.reset(!0)}function A(e){if(e||""===e){var r=e[u];if(r)return r.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var o=-1,a=function r(){for(;++o<e.length;)if(n.call(e,o))return r.value=e[o],r.done=!1,r;return r.value=t,r.done=!0,r};return a.next=a}}throw new TypeError(i(e)+" is not iterable")}return w.prototype=S,o(O,"constructor",{value:S,configurable:!0}),o(S,"constructor",{value:w,configurable:!0}),w.displayName=f(S,s,"GeneratorFunction"),e.isGeneratorFunction=function(t){var e="function"==typeof t&&t.constructor;return!!e&&(e===w||"GeneratorFunction"===(e.displayName||e.name))},e.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,S):(t.__proto__=S,f(t,s,"GeneratorFunction")),t.prototype=Object.create(O),t},e.awrap=function(t){return{__await:t}},x(j.prototype),f(j.prototype,l,(function(){return this})),e.AsyncIterator=j,e.async=function(t,r,n,o,i){void 0===i&&(i=Promise);var a=new j(h(t,r,n,o),i);return e.isGeneratorFunction(r)?a:a.next().then((function(t){return t.done?t.value:a.next()}))},x(O),f(O,s,"Generator"),f(O,u,(function(){return this})),f(O,"toString",(function(){return"[object Generator]"})),e.keys=function(t){var e=Object(t),r=[];for(var n in e)r.push(n);return r.reverse(),function t(){for(;r.length;){var n=r.pop();if(n in e)return t.value=n,t.done=!1,t}return t.done=!0,t}},e.values=A,I.prototype={constructor:I,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=t,this.done=!1,this.delegate=null,this.method="next",this.arg=t,this.tryEntries.forEach(C),!e)for(var r in this)"t"===r.charAt(0)&&n.call(this,r)&&!isNaN(+r.slice(1))&&(this[r]=t)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(e){if(this.done)throw e;var r=this;function o(n,o){return c.type="throw",c.arg=e,r.next=n,o&&(r.method="next",r.arg=t),!!o}for(var i=this.tryEntries.length-1;i>=0;--i){var a=this.tryEntries[i],c=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),l=n.call(a,"finallyLoc");if(u&&l){if(this.prev<a.catchLoc)return o(a.catchLoc,!0);if(this.prev<a.finallyLoc)return o(a.finallyLoc)}else if(u){if(this.prev<a.catchLoc)return o(a.catchLoc,!0)}else{if(!l)throw Error("try statement without catch or finally");if(this.prev<a.finallyLoc)return o(a.finallyLoc)}}}},abrupt:function(t,e){for(var r=this.tryEntries.length-1;r>=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev<o.finallyLoc){var i=o;break}}i&&("break"===t||"continue"===t)&&i.tryLoc<=e&&e<=i.finallyLoc&&(i=null);var a=i?i.completion:{};return a.type=t,a.arg=e,i?(this.method="next",this.next=i.finallyLoc,g):this.complete(a)},complete:function(t,e){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&e&&(this.next=e),g},finish:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),C(r),g}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;C(r)}return o}}throw Error("illegal catch attempt")},delegateYield:function(e,r,n){return this.delegate={iterator:A(e),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=t),g}},e}function u(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,n)}return r}function l(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{};e%2?u(Object(r),!0).forEach((function(e){y(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):u(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}function s(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if({}.hasOwnProperty.call(t,n)){if(e.includes(n))continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n<i.length;n++)r=i[n],e.includes(r)||{}.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function f(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void r(t)}c.done?e(u):Promise.resolve(u).then(n,o)}function h(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var i=t.apply(e,r);function a(t){f(i,n,o,a,c,"next",t)}function c(t){f(i,n,o,a,c,"throw",t)}a(void 0)}))}}function p(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,d(n.key),n)}}function y(t,e,r){return(e=d(e))in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}function d(t){var e=function(t){if("object"!=i(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var r=e.call(t,"string");if("object"!=i(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==i(e)?e:e+""}var v=function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.defaultDisplayStyles={},this.skeletonClass="skeleton-loader--client",this.contentRefAttr="data-content-id",this.apiEndpoint="/skeleton_loader/templates"}return e=t,i=[{key:"getSkeletonInstance",value:function(e){return t.activeSkeletons.has(e)||t.activeSkeletons.set(e,this.createSkeletonInstance(e)),t.activeSkeletons.get(e)}},{key:"createSkeletonInstance",value:function(e){var r=this;return{isLoading:function(){return t.loadingStates.get(e)||!1},reveal:function(){var t=document.querySelector("[".concat(r.contentRefAttr,'="').concat(e,'"]')),n=document.getElementById(e);t&&n&&r.revealContent(t,n)}}}},{key:"render",value:(m=h(c().mark((function t(){var e,n,o,i,a,u=arguments;return c().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return n=(e=u.length>0&&void 0!==u[0]?u[0]:{}).contentId,o=e.type,i=void 0===o?"default":o,a=s(e,r),t.abrupt("return",this.processRender(l({contentId:n,mode:"predefined",type:i},a)));case 2:case"end":return t.stop()}}),t,this)}))),function(){return m.apply(this,arguments)})},{key:"renderCustom",value:(v=h(c().mark((function t(){var e,r,n,o=arguments;return c().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(r=(e=o.length>0&&void 0!==o[0]?o[0]:{}).contentId,n=e.markup){t.next=3;break}return t.abrupt("return",this.logError("Custom markup is required"));case 3:return t.abrupt("return",this.processRender({contentId:r,mode:"custom",markup:n}));case 4:case"end":return t.stop()}}),t,this)}))),function(){return v.apply(this,arguments)})},{key:"processRender",value:(d=h(c().mark((function t(e){var r,n,o,i;return c().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:if(r=e.contentId){t.next=3;break}return t.abrupt("return",this.logError("contentId is required"));case 3:if(!(n=this.getSkeletonInstance(r)).isLoading()){t.next=7;break}return console.warn("Skeleton loading already in progress for ".concat(r)),t.abrupt("return",n);case 7:return t.prev=7,t.next=10,this.initializeLoading(r);case 10:if(o=t.sent){t.next=13;break}return t.abrupt("return",n);case 13:return t.next=15,this.createSkeletonElement(e);case 15:return(i=t.sent)&&this.showSkeleton(i,o),t.abrupt("return",n);case 20:return t.prev=20,t.t0=t.catch(7),console.error("[SkeletonLoader] Render failed:",t.t0),t.abrupt("return",n);case 24:case"end":return t.stop()}}),t,this,[[7,20]])}))),function(t){return d.apply(this,arguments)})},{key:"initializeLoading",value:(y=h(c().mark((function e(r){return c().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return t.loadingStates.set(r,!0),e.abrupt("return",this.hideContent(r));case 2:case"end":return e.stop()}}),e,this)}))),function(t){return y.apply(this,arguments)})},{key:"hideContent",value:function(t){var e=document.getElementById(t);if(!e)return console.error("Content not found: #".concat(t)),null;var r=getComputedStyle(e).display;return"none"!==r&&(this.defaultDisplayStyles[t]=r),e.style.display="none",e}},{key:"revealContent",value:function(e,r){var n=r.id;t.loadingStates.get(n)&&(e.remove(),r.style.display=this.defaultDisplayStyles[n],t.loadingStates.set(n,!1))}},{key:"showSkeleton",value:function(t,e){this.clearPreviousSkeleton(e.id),t.setAttribute(this.contentRefAttr,e.id),t.style.display="block",e.parentNode.insertBefore(t,e)}},{key:"clearPreviousSkeleton",value:function(t){document.querySelectorAll("[".concat(this.contentRefAttr,'="').concat(t,'"]')).forEach((function(t){return t.remove()}))}},{key:"createSkeletonElement",value:(f=h(c().mark((function t(e){var r,o,i,a,u,f,h,p;return c().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return r=e.contentId,o=e.mode,i=e.markup,a=void 0===i?null:i,u=e.type,f=s(e,n),h=this.buildParams(l({contentId:r,mode:o,markup:a,type:u},f)),t.prev=2,t.next=5,this.fetchTemplate(h);case 5:return p=t.sent,t.abrupt("return",this.parseTemplateToElement(p,r));case 9:return t.prev=9,t.t0=t.catch(2),console.error("Error creating skeleton:",t.t0),t.abrupt("return",null);case 13:case"end":return t.stop()}}),t,this,[[2,9]])}))),function(t){return f.apply(this,arguments)})},{key:"fetchTemplate",value:(u=h(c().mark((function t(e){var r;return c().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.next=2,fetch("".concat(this.apiEndpoint,"?").concat(e.toString()),{method:"GET",headers:{"X-Requested-With":"XMLHttpRequest"}});case 2:if((r=t.sent).ok){t.next=5;break}throw new Error("Failed to fetch skeleton template");case 5:return t.abrupt("return",r.text());case 6:case"end":return t.stop()}}),t,this)}))),function(t){return u.apply(this,arguments)})},{key:"parseTemplateToElement",value:function(t,e){return(new DOMParser).parseFromString(t,"text/html").body.firstElementChild||(console.error("Invalid skeleton template for contentId: ".concat(e)),null)}},{key:"buildParams",value:function(t){var e=t.contentId,r=t.mode,n=t.markup,i=t.type,a=s(t,o),c=new URLSearchParams(l({content_id:e,mode:r,type:i||"default"},this.formatOptions(a)));return n&&c.set("markup",n),c}},{key:"formatOptions",value:function(t){var e=this;return Object.fromEntries(Object.entries(t).map((function(t){var r=function(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,c=[],u=!0,l=!1;try{if(i=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;u=!1}else for(;!(u=(n=i.call(r)).done)&&(c.push(n.value),c.length!==e);u=!0);}catch(t){l=!0,o=t}finally{try{if(!u&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return c}}(t,e)||function(t,e){if(t){if("string"==typeof t)return a(t,e);var r={}.toString.call(t).slice(8,-1);return"Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r?Array.from(t):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?a(t,e):void 0}}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}(t,2),n=r[0],o=r[1];return[e.toSnakeCase(n),o]})))}},{key:"toSnakeCase",value:function(t){return t.replace(/([A-Z])/g,"_$1").toLowerCase()}},{key:"logError",value:function(t){return console.error("[SkeletonLoader] ".concat(t)),null}}],i&&p(e.prototype,i),Object.defineProperty(e,"prototype",{writable:!1}),e;var e,i,u,f,y,d,v,m}();function m(t){return m="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},m(t)}function g(){g=function(){return e};var t,e={},r=Object.prototype,n=r.hasOwnProperty,o=Object.defineProperty||function(t,e,r){t[e]=r.value},i="function"==typeof Symbol?Symbol:{},a=i.iterator||"@@iterator",c=i.asyncIterator||"@@asyncIterator",u=i.toStringTag||"@@toStringTag";function l(t,e,r){return Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{l({},"")}catch(t){l=function(t,e,r){return t[e]=r}}function s(t,e,r,n){var i=e&&e.prototype instanceof b?e:b,a=Object.create(i.prototype),c=new I(n||[]);return o(a,"_invoke",{value:T(t,r,c)}),a}function f(t,e,r){try{return{type:"normal",arg:t.call(e,r)}}catch(t){return{type:"throw",arg:t}}}e.wrap=s;var h="suspendedStart",p="suspendedYield",y="executing",d="completed",v={};function b(){}function w(){}function S(){}var k={};l(k,a,(function(){return this}));var E=Object.getPrototypeOf,L=E&&E(E(A([])));L&&L!==r&&n.call(L,a)&&(k=L);var O=S.prototype=b.prototype=Object.create(k);function x(t){["next","throw","return"].forEach((function(e){l(t,e,(function(t){return this._invoke(e,t)}))}))}function j(t,e){function r(o,i,a,c){var u=f(t[o],t,i);if("throw"!==u.type){var l=u.arg,s=l.value;return s&&"object"==m(s)&&n.call(s,"__await")?e.resolve(s.__await).then((function(t){r("next",t,a,c)}),(function(t){r("throw",t,a,c)})):e.resolve(s).then((function(t){l.value=t,a(l)}),(function(t){return r("throw",t,a,c)}))}c(u.arg)}var i;o(this,"_invoke",{value:function(t,n){function o(){return new e((function(e,o){r(t,n,e,o)}))}return i=i?i.then(o,o):o()}})}function T(e,r,n){var o=h;return function(i,a){if(o===y)throw Error("Generator is already running");if(o===d){if("throw"===i)throw a;return{value:t,done:!0}}for(n.method=i,n.arg=a;;){var c=n.delegate;if(c){var u=_(c,n);if(u){if(u===v)continue;return u}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if(o===h)throw o=d,n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);o=y;var l=f(e,r,n);if("normal"===l.type){if(o=n.done?d:p,l.arg===v)continue;return{value:l.arg,done:n.done}}"throw"===l.type&&(o=d,n.method="throw",n.arg=l.arg)}}}function _(e,r){var n=r.method,o=e.iterator[n];if(o===t)return r.delegate=null,"throw"===n&&e.iterator.return&&(r.method="return",r.arg=t,_(e,r),"throw"===r.method)||"return"!==n&&(r.method="throw",r.arg=new TypeError("The iterator does not provide a '"+n+"' method")),v;var i=f(o,e.iterator,r.arg);if("throw"===i.type)return r.method="throw",r.arg=i.arg,r.delegate=null,v;var a=i.arg;return a?a.done?(r[e.resultName]=a.value,r.next=e.nextLoc,"return"!==r.method&&(r.method="next",r.arg=t),r.delegate=null,v):a:(r.method="throw",r.arg=new TypeError("iterator result is not an object"),r.delegate=null,v)}function P(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function C(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function I(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(P,this),this.reset(!0)}function A(e){if(e||""===e){var r=e[a];if(r)return r.call(e);if("function"==typeof e.next)return e;if(!isNaN(e.length)){var o=-1,i=function r(){for(;++o<e.length;)if(n.call(e,o))return r.value=e[o],r.done=!1,r;return r.value=t,r.done=!0,r};return i.next=i}}throw new TypeError(m(e)+" is not iterable")}return w.prototype=S,o(O,"constructor",{value:S,configurable:!0}),o(S,"constructor",{value:w,configurable:!0}),w.displayName=l(S,u,"GeneratorFunction"),e.isGeneratorFunction=function(t){var e="function"==typeof t&&t.constructor;return!!e&&(e===w||"GeneratorFunction"===(e.displayName||e.name))},e.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,S):(t.__proto__=S,l(t,u,"GeneratorFunction")),t.prototype=Object.create(O),t},e.awrap=function(t){return{__await:t}},x(j.prototype),l(j.prototype,c,(function(){return this})),e.AsyncIterator=j,e.async=function(t,r,n,o,i){void 0===i&&(i=Promise);var a=new j(s(t,r,n,o),i);return e.isGeneratorFunction(r)?a:a.next().then((function(t){return t.done?t.value:a.next()}))},x(O),l(O,u,"Generator"),l(O,a,(function(){return this})),l(O,"toString",(function(){return"[object Generator]"})),e.keys=function(t){var e=Object(t),r=[];for(var n in e)r.push(n);return r.reverse(),function t(){for(;r.length;){var n=r.pop();if(n in e)return t.value=n,t.done=!1,t}return t.done=!0,t}},e.values=A,I.prototype={constructor:I,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=t,this.done=!1,this.delegate=null,this.method="next",this.arg=t,this.tryEntries.forEach(C),!e)for(var r in this)"t"===r.charAt(0)&&n.call(this,r)&&!isNaN(+r.slice(1))&&(this[r]=t)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(e){if(this.done)throw e;var r=this;function o(n,o){return c.type="throw",c.arg=e,r.next=n,o&&(r.method="next",r.arg=t),!!o}for(var i=this.tryEntries.length-1;i>=0;--i){var a=this.tryEntries[i],c=a.completion;if("root"===a.tryLoc)return o("end");if(a.tryLoc<=this.prev){var u=n.call(a,"catchLoc"),l=n.call(a,"finallyLoc");if(u&&l){if(this.prev<a.catchLoc)return o(a.catchLoc,!0);if(this.prev<a.finallyLoc)return o(a.finallyLoc)}else if(u){if(this.prev<a.catchLoc)return o(a.catchLoc,!0)}else{if(!l)throw Error("try statement without catch or finally");if(this.prev<a.finallyLoc)return o(a.finallyLoc)}}}},abrupt:function(t,e){for(var r=this.tryEntries.length-1;r>=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev<o.finallyLoc){var i=o;break}}i&&("break"===t||"continue"===t)&&i.tryLoc<=e&&e<=i.finallyLoc&&(i=null);var a=i?i.completion:{};return a.type=t,a.arg=e,i?(this.method="next",this.next=i.finallyLoc,v):this.complete(a)},complete:function(t,e){if("throw"===t.type)throw t.arg;return"break"===t.type||"continue"===t.type?this.next=t.arg:"return"===t.type?(this.rval=this.arg=t.arg,this.method="return",this.next="end"):"normal"===t.type&&e&&(this.next=e),v},finish:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.finallyLoc===t)return this.complete(r.completion,r.afterLoc),C(r),v}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var r=this.tryEntries[e];if(r.tryLoc===t){var n=r.completion;if("throw"===n.type){var o=n.arg;C(r)}return o}}throw Error("illegal catch attempt")},delegateYield:function(e,r,n){return this.delegate={iterator:A(e),resultName:r,nextLoc:n},"next"===this.method&&(this.arg=t),v}},e}function b(t,e,r,n,o,i,a){try{var c=t[i](a),u=c.value}catch(t){return void r(t)}c.done?e(u):Promise.resolve(u).then(n,o)}function w(t){return function(){var e=this,r=arguments;return new Promise((function(n,o){var i=t.apply(e,r);function a(t){b(i,n,o,a,c,"next",t)}function c(t){b(i,n,o,a,c,"throw",t)}a(void 0)}))}}function S(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,k(n.key),n)}}function k(t){var e=function(t){if("object"!=m(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var r=e.call(t,"string");if("object"!=m(r))return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==m(e)?e:e+""}y(v,"loadingStates",new Map),y(v,"activeSkeletons",new Map),(new(function(){function t(){!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.SKELETON_CLASS="skeleton-loader--server",this.CONTENT_ID_ATTR="data-content-id",this.contentsDisplayStyles={}}return e=t,r=[{key:"start",value:function(){return this.setupInitialLoading(),this.setupContentsSwap(),this}},{key:"setupInitialLoading",value:function(){var t=this;document.addEventListener("DOMContentLoaded",(function(){t.captureContentsDisplayStyles(),t.hideContents(),t.showSkeletons()}))}},{key:"setupContentsSwap",value:function(){var t=this;window.addEventListener("load",w(g().mark((function e(){return g().wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return e.next=2,t.revealContent();case 2:return e.abrupt("return",e.sent);case 3:case"end":return e.stop()}}),e)}))))}},{key:"captureContentsDisplayStyles",value:function(){var e=this;document.querySelectorAll(".".concat(t.SKELETON_CLASS)).forEach((function(r){var n=r.getAttribute(t.CONTENT_ID_ATTR),o=document.getElementById(n);if(o){var i=getComputedStyle(o).display||"block";e.contentsDisplayStyles[n]=i}else console.warn('Content element with id "'.concat(n,'" not found'))}))}},{key:"showSkeletons",value:function(){document.querySelectorAll(".".concat(t.SKELETON_CLASS)).forEach((function(t){t.style.display="block"}))}},{key:"revealContent",value:(o=w(g().mark((function t(){return g().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:this.hideSkeletons(),this.showContents();case 2:case"end":return t.stop()}}),t,this)}))),function(){return o.apply(this,arguments)})},{key:"hideSkeletons",value:function(){document.querySelectorAll(".".concat(t.SKELETON_CLASS)).forEach((function(t){t.style.display="none"}))}},{key:"showContents",value:function(){var e=this;document.querySelectorAll(".".concat(t.SKELETON_CLASS)).forEach((function(r){var n=r.getAttribute(t.CONTENT_ID_ATTR),o=document.getElementById(n);if(o){var i=e.contentsDisplayStyles[n]||"block";o.style.display=i,o.style.visibility="visible"}else console.warn('Content element with id "'.concat(n,'" not found'))}))}},{key:"hideContents",value:function(){document.querySelectorAll(".".concat(t.SKELETON_CLASS)).forEach((function(e){var r=e.getAttribute(t.CONTENT_ID_ATTR),n=document.getElementById(r);n?n.style.display="none":console.warn('Content element with id "'.concat(r,'" not found'))}))}}],n=[{key:"SKELETON_CLASS",get:function(){return"skeleton-loader--server"}},{key:"CONTENT_ID_ATTR",get:function(){return"data-content-id"}}],r&&S(e.prototype,r),n&&S(e,n),Object.defineProperty(e,"prototype",{writable:!1}),e;var e,r,n,o}())).start();const E=v;return e.default})()));
|
@@ -0,0 +1 @@
|
|
1
|
+
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
@@ -0,0 +1,83 @@
|
|
1
|
+
.skeleton-loading {
|
2
|
+
background-color: #e0e0e0;
|
3
|
+
border-radius: 10px;
|
4
|
+
overflow: hidden;
|
5
|
+
}
|
6
|
+
|
7
|
+
/* Animation Keyframes */
|
8
|
+
@keyframes skeleton-pulse {
|
9
|
+
0%, 100% { opacity: 1; }
|
10
|
+
50% { opacity: 0.5; }
|
11
|
+
}
|
12
|
+
|
13
|
+
@keyframes skeleton-flow {
|
14
|
+
0% { opacity: 0.6; }
|
15
|
+
50% { opacity: 0.3; }
|
16
|
+
100% { opacity: 0.6; }
|
17
|
+
}
|
18
|
+
|
19
|
+
@keyframes skeleton-shine {
|
20
|
+
0% { background-position: -201% center; }
|
21
|
+
100% { background-position: 198% center; }
|
22
|
+
}
|
23
|
+
|
24
|
+
@keyframes skeleton-gradient {
|
25
|
+
0% { background-position: 0% 50%; }
|
26
|
+
50% { background-position: 100% 50%; }
|
27
|
+
100% { background-position: 0% 50%; }
|
28
|
+
}
|
29
|
+
|
30
|
+
@keyframes skeleton-neon {
|
31
|
+
0% {
|
32
|
+
background: #e0e0e0;
|
33
|
+
box-shadow: 0 0 5px rgba(224, 224, 224, 0.5);
|
34
|
+
}
|
35
|
+
50% {
|
36
|
+
background: #e8e8e8;
|
37
|
+
box-shadow: 0 0 15px rgba(224, 224, 224, 0.8);
|
38
|
+
}
|
39
|
+
100% {
|
40
|
+
background: #e0e0e0;
|
41
|
+
box-shadow: 0 0 5px rgba(224, 224, 224, 0.5);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
@keyframes skeleton-breathing {
|
46
|
+
0%, 100% {
|
47
|
+
transform: scale(1);
|
48
|
+
opacity: 0.7;
|
49
|
+
}
|
50
|
+
50% {
|
51
|
+
transform: scale(1.02);
|
52
|
+
opacity: 0.9;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
/* Animation Classes */
|
57
|
+
.sl-pulse {
|
58
|
+
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
59
|
+
}
|
60
|
+
|
61
|
+
.sl-flow {
|
62
|
+
animation: skeleton-flow 2s ease-in-out infinite;
|
63
|
+
}
|
64
|
+
|
65
|
+
.sl-shine {
|
66
|
+
background: linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 76%);
|
67
|
+
background-size: 200% 100%;
|
68
|
+
animation: skeleton-shine 1.5s ease-in-out infinite;
|
69
|
+
}
|
70
|
+
|
71
|
+
.sl-gradient {
|
72
|
+
background: linear-gradient(-45deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%);
|
73
|
+
background-size: 400% 400%;
|
74
|
+
animation: skeleton-gradient 2s ease-in-out infinite;
|
75
|
+
}
|
76
|
+
|
77
|
+
.sl-neon {
|
78
|
+
animation: skeleton-neon 2s ease-in-out infinite;
|
79
|
+
}
|
80
|
+
|
81
|
+
.sl-breathing {
|
82
|
+
animation: skeleton-breathing 2s ease-in-out infinite;
|
83
|
+
}
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SkeletonLoader
|
4
|
+
# Handles async requests to generate and render loading skeletons
|
5
|
+
class SkeletonLoaderController < ActionController::Base
|
6
|
+
def show
|
7
|
+
content_id = params[:content_id]
|
8
|
+
mode = params[:mode]
|
9
|
+
wrapped_content = mode == "custom" ? handle_custom_template(content_id) : handle_predefined_template(content_id)
|
10
|
+
render html: wrapped_content
|
11
|
+
rescue StandardError => e
|
12
|
+
handle_error(e)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def handle_predefined_template(content_id)
|
18
|
+
options = process_params
|
19
|
+
SkeletonElementGenerator.generate(
|
20
|
+
content_id: content_id,
|
21
|
+
options: options,
|
22
|
+
context: :controller
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_custom_template(content_id)
|
27
|
+
markup = params[:markup]
|
28
|
+
SkeletonElementGenerator.generate(
|
29
|
+
content_id: content_id,
|
30
|
+
context: :controller
|
31
|
+
) { markup }
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_params
|
35
|
+
params_to_process = params.to_unsafe_h
|
36
|
+
.except("controller", "action", "format", "content_id", "mode", "markup")
|
37
|
+
.transform_keys(&:to_sym)
|
38
|
+
|
39
|
+
convert_numeric_params(params_to_process)
|
40
|
+
params_to_process
|
41
|
+
end
|
42
|
+
|
43
|
+
def convert_numeric_params(params)
|
44
|
+
%i[scale count width per_row].each do |key|
|
45
|
+
params[key] = params[key].to_f if key == :scale && params[key]
|
46
|
+
params[key] = params[key].to_i if %i[count width per_row].include?(key) && params[key]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_error(error)
|
51
|
+
Rails.logger.error "SkeletonLoader Error: #{error.message}"
|
52
|
+
Rails.logger.error error.backtrace.join("\n")
|
53
|
+
|
54
|
+
render json: {
|
55
|
+
error: error.message,
|
56
|
+
backtrace: Rails.env.development? ? error.backtrace : []
|
57
|
+
}, status: :unprocessable_entity
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|