@servlyadmin/runtime-core 0.1.36 → 0.1.38
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 +137 -98
- package/dist/chunk-5O72RMSO.js +305 -0
- package/dist/index.cjs +203 -17
- package/dist/index.js +45 -8
- package/dist/{tailwind-DMUQ7TOT.js → tailwind-4IK6HZC6.js} +15 -3
- package/package.json +1 -1
- package/dist/chunk-SMHCCKAZ.js +0 -157
- package/dist/index.d.cts +0 -1973
- package/dist/index.d.ts +0 -1973
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ pnpm add @servlyadmin/runtime-core
|
|
|
17
17
|
```typescript
|
|
18
18
|
import { render, fetchComponent } from '@servlyadmin/runtime-core';
|
|
19
19
|
|
|
20
|
-
// Fetch a component from the registry
|
|
21
|
-
const { data } = await fetchComponent('my-component'
|
|
20
|
+
// Fetch a component from the registry (uses Servly's default registry)
|
|
21
|
+
const { data } = await fetchComponent('my-component-id');
|
|
22
22
|
|
|
23
23
|
// Render to a container
|
|
24
24
|
const result = render({
|
|
@@ -42,6 +42,47 @@ result.update({
|
|
|
42
42
|
result.destroy();
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
## Registry Configuration
|
|
46
|
+
|
|
47
|
+
By default, components are fetched from Servly's production registry:
|
|
48
|
+
- **Default URL**: `https://core-api.servly.app/v1/views/registry`
|
|
49
|
+
|
|
50
|
+
You can override this if you have a custom registry:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { setRegistryUrl } from '@servlyadmin/runtime-core';
|
|
54
|
+
|
|
55
|
+
// Use a custom registry
|
|
56
|
+
setRegistryUrl('https://your-api.com/v1/views/registry');
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Cache Strategies
|
|
60
|
+
|
|
61
|
+
The runtime supports three caching strategies to optimize component loading:
|
|
62
|
+
|
|
63
|
+
| Strategy | Description | Persistence | Best For |
|
|
64
|
+
|----------|-------------|-------------|----------|
|
|
65
|
+
| `localStorage` | Persists across browser sessions | Yes | Production apps (default) |
|
|
66
|
+
| `memory` | In-memory cache, cleared on page refresh | No | Development, SSR |
|
|
67
|
+
| `none` | No caching, always fetches fresh | No | Testing, debugging |
|
|
68
|
+
|
|
69
|
+
**Default**: `localStorage` - Components are cached in the browser's localStorage for fast subsequent loads.
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Use default localStorage caching
|
|
73
|
+
const { data } = await fetchComponent('my-component');
|
|
74
|
+
|
|
75
|
+
// Explicitly set cache strategy
|
|
76
|
+
const { data } = await fetchComponent('my-component', {
|
|
77
|
+
cacheStrategy: 'memory', // or 'localStorage' or 'none'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Force refresh (bypass cache)
|
|
81
|
+
const { data } = await fetchComponent('my-component', {
|
|
82
|
+
forceRefresh: true,
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
45
86
|
## Core Concepts
|
|
46
87
|
|
|
47
88
|
### Layout Elements
|
|
@@ -51,14 +92,15 @@ Components are defined as a tree of layout elements:
|
|
|
51
92
|
```typescript
|
|
52
93
|
interface LayoutElement {
|
|
53
94
|
i: string; // Unique identifier
|
|
54
|
-
|
|
95
|
+
componentId: string; // Element type (container, text, button, etc.)
|
|
55
96
|
configuration?: { // Element configuration
|
|
56
|
-
|
|
97
|
+
classNames?: string;
|
|
57
98
|
style?: Record<string, any>;
|
|
58
|
-
|
|
99
|
+
text?: string;
|
|
59
100
|
// ... other attributes
|
|
60
101
|
};
|
|
61
|
-
children?:
|
|
102
|
+
children?: string[]; // Child element IDs
|
|
103
|
+
parent?: string; // Parent element ID
|
|
62
104
|
}
|
|
63
105
|
```
|
|
64
106
|
|
|
@@ -82,10 +124,10 @@ Use `{{path}}` syntax to bind data:
|
|
|
82
124
|
const elements = [
|
|
83
125
|
{
|
|
84
126
|
i: 'greeting',
|
|
85
|
-
|
|
127
|
+
componentId: 'text',
|
|
86
128
|
configuration: {
|
|
87
|
-
|
|
88
|
-
|
|
129
|
+
text: 'Hello, {{props.name}}!',
|
|
130
|
+
classNames: '{{props.className}}',
|
|
89
131
|
},
|
|
90
132
|
},
|
|
91
133
|
];
|
|
@@ -118,18 +160,33 @@ interface RenderResult {
|
|
|
118
160
|
Fetches a component from the registry.
|
|
119
161
|
|
|
120
162
|
```typescript
|
|
121
|
-
const { data, fromCache } = await fetchComponent('component-id', {
|
|
122
|
-
version: 'latest',
|
|
123
|
-
cacheStrategy: '
|
|
124
|
-
forceRefresh: false,
|
|
125
|
-
timeout: 30000, // Request timeout in ms
|
|
163
|
+
const { data, fromCache, version } = await fetchComponent('component-id', {
|
|
164
|
+
version: 'latest', // Version specifier (default: 'latest')
|
|
165
|
+
cacheStrategy: 'localStorage', // 'localStorage' | 'memory' | 'none' (default: 'localStorage')
|
|
166
|
+
forceRefresh: false, // Bypass cache (default: false)
|
|
126
167
|
retryConfig: {
|
|
127
|
-
maxRetries: 3,
|
|
128
|
-
|
|
168
|
+
maxRetries: 3, // Number of retry attempts (default: 3)
|
|
169
|
+
initialDelay: 1000, // Initial retry delay in ms (default: 1000)
|
|
170
|
+
maxDelay: 10000, // Maximum retry delay in ms (default: 10000)
|
|
171
|
+
backoffMultiplier: 2, // Exponential backoff multiplier (default: 2)
|
|
129
172
|
},
|
|
130
173
|
});
|
|
131
174
|
```
|
|
132
175
|
|
|
176
|
+
### setRegistryUrl(url)
|
|
177
|
+
|
|
178
|
+
Configure a custom registry URL.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { setRegistryUrl, DEFAULT_REGISTRY_URL } from '@servlyadmin/runtime-core';
|
|
182
|
+
|
|
183
|
+
// Use custom registry
|
|
184
|
+
setRegistryUrl('https://your-api.com/v1/views/registry');
|
|
185
|
+
|
|
186
|
+
// Reset to default
|
|
187
|
+
setRegistryUrl(DEFAULT_REGISTRY_URL);
|
|
188
|
+
```
|
|
189
|
+
|
|
133
190
|
### StateManager
|
|
134
191
|
|
|
135
192
|
Manages component state with subscriptions.
|
|
@@ -140,25 +197,17 @@ import { StateManager } from '@servlyadmin/runtime-core';
|
|
|
140
197
|
const stateManager = new StateManager({ count: 0 });
|
|
141
198
|
|
|
142
199
|
// Get/set state
|
|
143
|
-
stateManager.
|
|
144
|
-
stateManager.
|
|
145
|
-
stateManager.
|
|
200
|
+
stateManager.set('count', 1);
|
|
201
|
+
stateManager.get('count'); // 1
|
|
202
|
+
stateManager.set('user.name', 'John');
|
|
146
203
|
|
|
147
204
|
// Subscribe to changes
|
|
148
|
-
const unsubscribe = stateManager.subscribe((
|
|
149
|
-
console.log('State changed:',
|
|
205
|
+
const unsubscribe = stateManager.subscribe((event) => {
|
|
206
|
+
console.log('State changed:', event.path, event.value);
|
|
150
207
|
});
|
|
151
208
|
|
|
152
|
-
//
|
|
153
|
-
stateManager.
|
|
154
|
-
console.log('Count changed:', value);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Batch updates
|
|
158
|
-
stateManager.batch(() => {
|
|
159
|
-
stateManager.setValue('a', 1);
|
|
160
|
-
stateManager.setValue('b', 2);
|
|
161
|
-
}); // Only one notification
|
|
209
|
+
// Cleanup
|
|
210
|
+
stateManager.clear();
|
|
162
211
|
```
|
|
163
212
|
|
|
164
213
|
### EventSystem
|
|
@@ -166,59 +215,52 @@ stateManager.batch(() => {
|
|
|
166
215
|
Handles events with plugin-based actions.
|
|
167
216
|
|
|
168
217
|
```typescript
|
|
169
|
-
import { EventSystem } from '@servlyadmin/runtime-core';
|
|
218
|
+
import { EventSystem, getEventSystem } from '@servlyadmin/runtime-core';
|
|
170
219
|
|
|
171
|
-
const eventSystem =
|
|
220
|
+
const eventSystem = getEventSystem();
|
|
172
221
|
|
|
173
222
|
// Register custom plugin
|
|
174
|
-
eventSystem.registerPlugin('my-action', async (
|
|
175
|
-
console.log('Action executed with:', config);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// Create event handler
|
|
179
|
-
const handler = eventSystem.createHandler([
|
|
180
|
-
{ key: 'prevent-default', config: {} },
|
|
181
|
-
{ key: 'set-state', config: { path: 'clicked', value: true } },
|
|
182
|
-
{ key: 'my-action', config: { message: 'Button clicked!' } },
|
|
183
|
-
]);
|
|
184
|
-
|
|
185
|
-
// Use with element
|
|
186
|
-
button.addEventListener('click', (e) => {
|
|
187
|
-
handler(e, 'button-id', bindingContext);
|
|
223
|
+
eventSystem.registerPlugin('my-action', async (action, context) => {
|
|
224
|
+
console.log('Action executed with:', action.config);
|
|
188
225
|
});
|
|
189
226
|
```
|
|
190
227
|
|
|
191
228
|
#### Built-in Plugins
|
|
192
229
|
|
|
193
|
-
- `
|
|
194
|
-
- `
|
|
230
|
+
- `executeCode` - Execute arbitrary JavaScript code
|
|
231
|
+
- `state-setState` - Update state values
|
|
232
|
+
- `navigateTo` - Navigate to URL
|
|
233
|
+
- `localStorage-set/get/remove` - LocalStorage operations
|
|
234
|
+
- `sessionStorage-set/get` - SessionStorage operations
|
|
235
|
+
- `alert` - Show alert dialog
|
|
236
|
+
- `console-log` - Log to console
|
|
237
|
+
- `clipboard-copy` - Copy text to clipboard
|
|
238
|
+
- `scrollTo` - Scroll to element
|
|
239
|
+
- `focus/blur` - Focus/blur elements
|
|
240
|
+
- `addClass/removeClass/toggleClass` - CSS class manipulation
|
|
241
|
+
- `setAttribute/removeAttribute` - Attribute manipulation
|
|
242
|
+
- `dispatchEvent` - Dispatch custom events
|
|
195
243
|
- `delay` - Add delay between actions
|
|
196
|
-
- `prevent-default` - Call event.preventDefault()
|
|
197
|
-
- `stop-propagation` - Call event.stopPropagation()
|
|
198
244
|
|
|
199
|
-
### Cache
|
|
200
|
-
|
|
201
|
-
Component caching with multiple strategies.
|
|
245
|
+
### Cache Management
|
|
202
246
|
|
|
203
247
|
```typescript
|
|
204
|
-
import {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
248
|
+
import {
|
|
249
|
+
clearAllCaches,
|
|
250
|
+
clearMemoryCache,
|
|
251
|
+
clearLocalStorageCache,
|
|
252
|
+
getMemoryCacheSize
|
|
253
|
+
} from '@servlyadmin/runtime-core';
|
|
210
254
|
|
|
211
|
-
//
|
|
212
|
-
|
|
255
|
+
// Clear all caches
|
|
256
|
+
clearAllCaches();
|
|
213
257
|
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
cache.invalidateComponent('comp-id');
|
|
258
|
+
// Clear specific cache
|
|
259
|
+
clearMemoryCache();
|
|
260
|
+
clearLocalStorageCache();
|
|
218
261
|
|
|
219
|
-
//
|
|
220
|
-
const
|
|
221
|
-
console.log(`Hit rate: ${stats.hitRate * 100}%`);
|
|
262
|
+
// Get cache size
|
|
263
|
+
const size = getMemoryCacheSize();
|
|
222
264
|
```
|
|
223
265
|
|
|
224
266
|
### Bindings
|
|
@@ -226,7 +268,7 @@ console.log(`Hit rate: ${stats.hitRate * 100}%`);
|
|
|
226
268
|
Template resolution utilities.
|
|
227
269
|
|
|
228
270
|
```typescript
|
|
229
|
-
import { resolveTemplate,
|
|
271
|
+
import { resolveTemplate, hasTemplateSyntax } from '@servlyadmin/runtime-core';
|
|
230
272
|
|
|
231
273
|
const context = {
|
|
232
274
|
props: { name: 'World', count: 42 },
|
|
@@ -237,16 +279,9 @@ const context = {
|
|
|
237
279
|
// Resolve single template
|
|
238
280
|
resolveTemplate('Hello, {{props.name}}!', context); // "Hello, World!"
|
|
239
281
|
|
|
240
|
-
// Check if string
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Resolve all bindings in an object
|
|
245
|
-
resolveBindings({
|
|
246
|
-
title: '{{props.name}}',
|
|
247
|
-
subtitle: 'Count: {{props.count}}',
|
|
248
|
-
}, context);
|
|
249
|
-
// { title: 'World', subtitle: 'Count: 42' }
|
|
282
|
+
// Check if string has template syntax
|
|
283
|
+
hasTemplateSyntax('{{props.name}}'); // true
|
|
284
|
+
hasTemplateSyntax('static text'); // false
|
|
250
285
|
```
|
|
251
286
|
|
|
252
287
|
## Slots
|
|
@@ -257,24 +292,25 @@ Components can define slots for content injection:
|
|
|
257
292
|
const elements = [
|
|
258
293
|
{
|
|
259
294
|
i: 'card',
|
|
260
|
-
|
|
261
|
-
configuration: {
|
|
262
|
-
children: [
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
295
|
+
componentId: 'container',
|
|
296
|
+
configuration: { classNames: 'card' },
|
|
297
|
+
children: ['header-slot', 'content-slot'],
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
i: 'header-slot',
|
|
301
|
+
componentId: 'slot',
|
|
302
|
+
configuration: {
|
|
303
|
+
slotName: 'header',
|
|
304
|
+
},
|
|
305
|
+
parent: 'card',
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
i: 'content-slot',
|
|
309
|
+
componentId: 'slot',
|
|
310
|
+
configuration: {
|
|
311
|
+
slotName: 'default',
|
|
312
|
+
},
|
|
313
|
+
parent: 'card',
|
|
278
314
|
},
|
|
279
315
|
];
|
|
280
316
|
```
|
|
@@ -290,9 +326,12 @@ import type {
|
|
|
290
326
|
LayoutElement,
|
|
291
327
|
BindingContext,
|
|
292
328
|
RenderResult,
|
|
329
|
+
RenderOptions,
|
|
293
330
|
ComponentData,
|
|
294
331
|
CacheStrategy,
|
|
295
332
|
RetryConfig,
|
|
333
|
+
FetchOptions,
|
|
334
|
+
FetchResult,
|
|
296
335
|
} from '@servlyadmin/runtime-core';
|
|
297
336
|
```
|
|
298
337
|
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// src/tailwind.ts
|
|
2
|
+
var DEFAULT_TAILWIND_CDN = "https://cdn.tailwindcss.com";
|
|
3
|
+
var TAILWIND_CACHE_KEY = "servly-tailwind-loaded";
|
|
4
|
+
var tailwindInjected = false;
|
|
5
|
+
var tailwindScript = null;
|
|
6
|
+
var foucStyleElement = null;
|
|
7
|
+
var tailwindReadyPromise = null;
|
|
8
|
+
var tailwindReadyResolve = null;
|
|
9
|
+
var FOUC_PREVENTION_CSS = `
|
|
10
|
+
.servly-component:not(.servly-ready),
|
|
11
|
+
[data-servly-id]:not(.servly-ready) {
|
|
12
|
+
visibility: hidden !important;
|
|
13
|
+
}
|
|
14
|
+
.servly-fouc-hidden {
|
|
15
|
+
visibility: hidden !important;
|
|
16
|
+
}
|
|
17
|
+
`;
|
|
18
|
+
function wasTailwindPreviouslyLoaded() {
|
|
19
|
+
if (typeof localStorage === "undefined") return false;
|
|
20
|
+
try {
|
|
21
|
+
return localStorage.getItem(TAILWIND_CACHE_KEY) === "true";
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function markTailwindAsLoaded() {
|
|
27
|
+
if (typeof localStorage === "undefined") return;
|
|
28
|
+
try {
|
|
29
|
+
localStorage.setItem(TAILWIND_CACHE_KEY, "true");
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function preloadTailwind(cdnUrl, usePlayCdn) {
|
|
34
|
+
if (typeof document === "undefined") return;
|
|
35
|
+
if (document.querySelector("link[data-servly-tailwind-preload]")) return;
|
|
36
|
+
const url = cdnUrl || DEFAULT_TAILWIND_CDN;
|
|
37
|
+
const fullUrl = usePlayCdn ? `${url}?plugins=forms,typography,aspect-ratio` : url;
|
|
38
|
+
const link = document.createElement("link");
|
|
39
|
+
link.rel = "preload";
|
|
40
|
+
link.as = "script";
|
|
41
|
+
link.href = fullUrl;
|
|
42
|
+
link.setAttribute("data-servly-tailwind-preload", "true");
|
|
43
|
+
if (document.head.firstChild) {
|
|
44
|
+
document.head.insertBefore(link, document.head.firstChild);
|
|
45
|
+
} else {
|
|
46
|
+
document.head.appendChild(link);
|
|
47
|
+
}
|
|
48
|
+
const dnsPrefetch = document.createElement("link");
|
|
49
|
+
dnsPrefetch.rel = "dns-prefetch";
|
|
50
|
+
dnsPrefetch.href = "https://cdn.tailwindcss.com";
|
|
51
|
+
document.head.appendChild(dnsPrefetch);
|
|
52
|
+
}
|
|
53
|
+
function preventFOUC() {
|
|
54
|
+
if (typeof document === "undefined") return;
|
|
55
|
+
if (foucStyleElement) return;
|
|
56
|
+
const wasLoaded = wasTailwindPreviouslyLoaded();
|
|
57
|
+
foucStyleElement = document.createElement("style");
|
|
58
|
+
foucStyleElement.id = "servly-fouc-prevention";
|
|
59
|
+
foucStyleElement.textContent = FOUC_PREVENTION_CSS;
|
|
60
|
+
if (document.head.firstChild) {
|
|
61
|
+
document.head.insertBefore(foucStyleElement, document.head.firstChild);
|
|
62
|
+
} else {
|
|
63
|
+
document.head.appendChild(foucStyleElement);
|
|
64
|
+
}
|
|
65
|
+
if (wasLoaded) {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (window.tailwind) {
|
|
68
|
+
removeFOUCPrevention();
|
|
69
|
+
}
|
|
70
|
+
}, 100);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function removeFOUCPrevention() {
|
|
74
|
+
if (foucStyleElement && foucStyleElement.parentNode) {
|
|
75
|
+
foucStyleElement.parentNode.removeChild(foucStyleElement);
|
|
76
|
+
foucStyleElement = null;
|
|
77
|
+
}
|
|
78
|
+
if (typeof document !== "undefined") {
|
|
79
|
+
document.querySelectorAll(".servly-component, [data-servly-id]").forEach((el) => {
|
|
80
|
+
el.classList.add("servly-ready");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function markElementReady(element) {
|
|
85
|
+
element.classList.add("servly-ready");
|
|
86
|
+
element.classList.remove("servly-fouc-hidden");
|
|
87
|
+
}
|
|
88
|
+
function isTailwindReady() {
|
|
89
|
+
if (typeof window === "undefined") return false;
|
|
90
|
+
return tailwindInjected && !!window.tailwind;
|
|
91
|
+
}
|
|
92
|
+
function waitForTailwind() {
|
|
93
|
+
if (isTailwindReady()) {
|
|
94
|
+
return Promise.resolve();
|
|
95
|
+
}
|
|
96
|
+
if (tailwindReadyPromise) {
|
|
97
|
+
return tailwindReadyPromise;
|
|
98
|
+
}
|
|
99
|
+
tailwindReadyPromise = new Promise((resolve) => {
|
|
100
|
+
tailwindReadyResolve = resolve;
|
|
101
|
+
});
|
|
102
|
+
return tailwindReadyPromise;
|
|
103
|
+
}
|
|
104
|
+
function injectTailwind(config = {}) {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
if (tailwindInjected && tailwindScript) {
|
|
107
|
+
resolve();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (typeof document === "undefined") {
|
|
111
|
+
resolve();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const {
|
|
115
|
+
cdnUrl = DEFAULT_TAILWIND_CDN,
|
|
116
|
+
config: tailwindConfig,
|
|
117
|
+
plugins = [],
|
|
118
|
+
usePlayCdn = false,
|
|
119
|
+
onReady,
|
|
120
|
+
onError,
|
|
121
|
+
preventFOUC: shouldPreventFOUC = true,
|
|
122
|
+
enablePreload = true
|
|
123
|
+
} = config;
|
|
124
|
+
if (shouldPreventFOUC) {
|
|
125
|
+
preventFOUC();
|
|
126
|
+
}
|
|
127
|
+
if (enablePreload) {
|
|
128
|
+
preloadTailwind(cdnUrl, usePlayCdn);
|
|
129
|
+
}
|
|
130
|
+
if (window.tailwind) {
|
|
131
|
+
tailwindInjected = true;
|
|
132
|
+
markTailwindAsLoaded();
|
|
133
|
+
if (shouldPreventFOUC) {
|
|
134
|
+
removeFOUCPrevention();
|
|
135
|
+
}
|
|
136
|
+
if (tailwindReadyResolve) {
|
|
137
|
+
tailwindReadyResolve();
|
|
138
|
+
tailwindReadyResolve = null;
|
|
139
|
+
}
|
|
140
|
+
config.onReady?.();
|
|
141
|
+
resolve();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const script = document.createElement("script");
|
|
145
|
+
script.src = usePlayCdn ? `${cdnUrl}?plugins=forms,typography,aspect-ratio` : cdnUrl;
|
|
146
|
+
script.async = true;
|
|
147
|
+
script.crossOrigin = "anonymous";
|
|
148
|
+
script.onload = () => {
|
|
149
|
+
tailwindInjected = true;
|
|
150
|
+
tailwindScript = script;
|
|
151
|
+
markTailwindAsLoaded();
|
|
152
|
+
if (tailwindConfig && window.tailwind) {
|
|
153
|
+
window.tailwind.config = tailwindConfig;
|
|
154
|
+
}
|
|
155
|
+
const delay = wasTailwindPreviouslyLoaded() ? 10 : 50;
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
if (shouldPreventFOUC) {
|
|
158
|
+
removeFOUCPrevention();
|
|
159
|
+
}
|
|
160
|
+
if (tailwindReadyResolve) {
|
|
161
|
+
tailwindReadyResolve();
|
|
162
|
+
tailwindReadyResolve = null;
|
|
163
|
+
}
|
|
164
|
+
onReady?.();
|
|
165
|
+
resolve();
|
|
166
|
+
}, delay);
|
|
167
|
+
};
|
|
168
|
+
script.onerror = (event) => {
|
|
169
|
+
const error = new Error(`Failed to load Tailwind CSS from ${cdnUrl}`);
|
|
170
|
+
if (shouldPreventFOUC) {
|
|
171
|
+
removeFOUCPrevention();
|
|
172
|
+
}
|
|
173
|
+
if (tailwindReadyResolve) {
|
|
174
|
+
tailwindReadyResolve();
|
|
175
|
+
tailwindReadyResolve = null;
|
|
176
|
+
}
|
|
177
|
+
onError?.(error);
|
|
178
|
+
reject(error);
|
|
179
|
+
};
|
|
180
|
+
document.head.appendChild(script);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function removeTailwind() {
|
|
184
|
+
if (tailwindScript && tailwindScript.parentNode) {
|
|
185
|
+
tailwindScript.parentNode.removeChild(tailwindScript);
|
|
186
|
+
tailwindScript = null;
|
|
187
|
+
tailwindInjected = false;
|
|
188
|
+
tailwindReadyPromise = null;
|
|
189
|
+
tailwindReadyResolve = null;
|
|
190
|
+
delete window.tailwind;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function isTailwindLoaded() {
|
|
194
|
+
return tailwindInjected || !!window.tailwind;
|
|
195
|
+
}
|
|
196
|
+
function getTailwind() {
|
|
197
|
+
return window.tailwind;
|
|
198
|
+
}
|
|
199
|
+
function updateTailwindConfig(config) {
|
|
200
|
+
if (window.tailwind) {
|
|
201
|
+
window.tailwind.config = {
|
|
202
|
+
...window.tailwind.config,
|
|
203
|
+
...config
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function addCustomStyles(css, id) {
|
|
208
|
+
if (typeof document === "undefined") {
|
|
209
|
+
throw new Error("addCustomStyles can only be used in browser environment");
|
|
210
|
+
}
|
|
211
|
+
const styleId = id || `servly-custom-styles-${Date.now()}`;
|
|
212
|
+
let existingStyle = document.getElementById(styleId);
|
|
213
|
+
if (existingStyle) {
|
|
214
|
+
existingStyle.textContent = css;
|
|
215
|
+
return existingStyle;
|
|
216
|
+
}
|
|
217
|
+
const style = document.createElement("style");
|
|
218
|
+
style.id = styleId;
|
|
219
|
+
style.textContent = css;
|
|
220
|
+
document.head.appendChild(style);
|
|
221
|
+
return style;
|
|
222
|
+
}
|
|
223
|
+
function removeCustomStyles(id) {
|
|
224
|
+
if (typeof document === "undefined") return;
|
|
225
|
+
const style = document.getElementById(id);
|
|
226
|
+
if (style && style.parentNode) {
|
|
227
|
+
style.parentNode.removeChild(style);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
var DEFAULT_SERVLY_TAILWIND_CONFIG = {
|
|
231
|
+
theme: {
|
|
232
|
+
extend: {
|
|
233
|
+
// Add any Servly-specific theme extensions here
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
// Safelist common dynamic classes
|
|
237
|
+
safelist: [
|
|
238
|
+
// Spacing
|
|
239
|
+
{ pattern: /^(p|m|gap)-/ },
|
|
240
|
+
// Sizing
|
|
241
|
+
{ pattern: /^(w|h|min-w|min-h|max-w|max-h)-/ },
|
|
242
|
+
// Flexbox
|
|
243
|
+
{ pattern: /^(flex|justify|items|self)-/ },
|
|
244
|
+
// Grid
|
|
245
|
+
{ pattern: /^(grid|col|row)-/ },
|
|
246
|
+
// Colors
|
|
247
|
+
{ pattern: /^(bg|text|border|ring)-/ },
|
|
248
|
+
// Typography
|
|
249
|
+
{ pattern: /^(font|text|leading|tracking)-/ },
|
|
250
|
+
// Borders
|
|
251
|
+
{ pattern: /^(rounded|border)-/ },
|
|
252
|
+
// Effects
|
|
253
|
+
{ pattern: /^(shadow|opacity|blur)-/ },
|
|
254
|
+
// Transforms
|
|
255
|
+
{ pattern: /^(scale|rotate|translate|skew)-/ },
|
|
256
|
+
// Transitions
|
|
257
|
+
{ pattern: /^(transition|duration|ease|delay)-/ }
|
|
258
|
+
]
|
|
259
|
+
};
|
|
260
|
+
async function initServlyTailwind(customConfig) {
|
|
261
|
+
const config = customConfig ? { ...DEFAULT_SERVLY_TAILWIND_CONFIG, ...customConfig } : DEFAULT_SERVLY_TAILWIND_CONFIG;
|
|
262
|
+
await injectTailwind({
|
|
263
|
+
config,
|
|
264
|
+
usePlayCdn: true
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
var injectTailwindStyles = initServlyTailwind;
|
|
268
|
+
var tailwind_default = {
|
|
269
|
+
injectTailwind,
|
|
270
|
+
injectTailwindStyles,
|
|
271
|
+
removeTailwind,
|
|
272
|
+
isTailwindLoaded,
|
|
273
|
+
isTailwindReady,
|
|
274
|
+
waitForTailwind,
|
|
275
|
+
getTailwind,
|
|
276
|
+
updateTailwindConfig,
|
|
277
|
+
addCustomStyles,
|
|
278
|
+
removeCustomStyles,
|
|
279
|
+
initServlyTailwind,
|
|
280
|
+
preventFOUC,
|
|
281
|
+
removeFOUCPrevention,
|
|
282
|
+
markElementReady,
|
|
283
|
+
preloadTailwind,
|
|
284
|
+
DEFAULT_SERVLY_TAILWIND_CONFIG
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export {
|
|
288
|
+
preloadTailwind,
|
|
289
|
+
preventFOUC,
|
|
290
|
+
removeFOUCPrevention,
|
|
291
|
+
markElementReady,
|
|
292
|
+
isTailwindReady,
|
|
293
|
+
waitForTailwind,
|
|
294
|
+
injectTailwind,
|
|
295
|
+
removeTailwind,
|
|
296
|
+
isTailwindLoaded,
|
|
297
|
+
getTailwind,
|
|
298
|
+
updateTailwindConfig,
|
|
299
|
+
addCustomStyles,
|
|
300
|
+
removeCustomStyles,
|
|
301
|
+
DEFAULT_SERVLY_TAILWIND_CONFIG,
|
|
302
|
+
initServlyTailwind,
|
|
303
|
+
injectTailwindStyles,
|
|
304
|
+
tailwind_default
|
|
305
|
+
};
|