mnfst 0.5.225 → 0.5.226
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/lib/manifest.appwrite.auth.js +69 -12
- package/lib/manifest.appwrite.data.js +1 -1
- package/lib/manifest.d.ts +1 -1
- package/lib/manifest.data.js +103 -36
- package/lib/manifest.integrity.json +4 -5
- package/lib/manifest.sw.js +1 -1
- package/package.json +3 -2
- package/lib/manifest.resize.css +0 -194
- package/lib/manifest.resize.js +0 -488
|
@@ -255,6 +255,19 @@ function initializeAuthStore() {
|
|
|
255
255
|
'deviceBrand', 'deviceModel', 'countryCode', 'countryName'
|
|
256
256
|
];
|
|
257
257
|
|
|
258
|
+
// User id in a mirrored state ('' / null when signed out)
|
|
259
|
+
function mirroredUserId(raw) {
|
|
260
|
+
try { const saved = JSON.parse(raw || 'null'); return saved?.isAuthenticated ? (saved.user?.$id || null) : null; } catch { return null; }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Identity this tab is showing data for: while unverified, the last one mirrored
|
|
264
|
+
function shownUserId(store, mirrored) {
|
|
265
|
+
if (store.user?.$id) return store.user.$id;
|
|
266
|
+
if (!store._sessionUnverified) return null;
|
|
267
|
+
if (mirrored === undefined) { try { mirrored = localStorage.getItem(STORAGE_KEY); } catch { mirrored = null; } }
|
|
268
|
+
return mirroredUserId(mirrored);
|
|
269
|
+
}
|
|
270
|
+
|
|
258
271
|
function sanitizeSessionForStorage(session) {
|
|
259
272
|
if (!session || typeof session !== 'object') return session;
|
|
260
273
|
const safe = {};
|
|
@@ -271,7 +284,8 @@ function initializeAuthStore() {
|
|
|
271
284
|
const state = JSON.parse(e.newValue);
|
|
272
285
|
const store = Alpine.store('auth');
|
|
273
286
|
if (store) {
|
|
274
|
-
const prevId = store.
|
|
287
|
+
const prevId = shownUserId(store, e.oldValue);
|
|
288
|
+
const wasAuthenticated = store.isAuthenticated === true;
|
|
275
289
|
// Update store state from other tab
|
|
276
290
|
store._authEpoch++;
|
|
277
291
|
if (state.isAuthenticated) markVerified(store);
|
|
@@ -283,6 +297,8 @@ function initializeAuthStore() {
|
|
|
283
297
|
store.magicLinkExpired = state.magicLinkExpired || false;
|
|
284
298
|
store.otpSent = state.otpSent || false;
|
|
285
299
|
store.otpExpired = state.otpExpired || false;
|
|
300
|
+
// A code row shown here must be verifiable here
|
|
301
|
+
if (state.otpSent && state.otpUserId) store._otpUserId = state.otpUserId;
|
|
286
302
|
store.error = state.error;
|
|
287
303
|
// Identity ended or changed in another tab: scoped data must drop this tab's rows
|
|
288
304
|
const nextId = state.isAuthenticated ? (state.user?.$id || null) : null;
|
|
@@ -292,6 +308,9 @@ function initializeAuthStore() {
|
|
|
292
308
|
store.currentTeam = null;
|
|
293
309
|
}
|
|
294
310
|
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
311
|
+
} else if (nextId && !wasAuthenticated) {
|
|
312
|
+
// Signed in from another tab: this tab's identity-bound data reloads
|
|
313
|
+
window.dispatchEvent(new CustomEvent(state.isAnonymous ? 'manifest:auth:anonymous' : 'manifest:auth:login', { detail: { user: state.user, crossTab: true } }));
|
|
295
314
|
}
|
|
296
315
|
}
|
|
297
316
|
} catch (error) {
|
|
@@ -314,6 +333,7 @@ function initializeAuthStore() {
|
|
|
314
333
|
magicLinkExpired: store.magicLinkExpired,
|
|
315
334
|
otpSent: store.otpSent,
|
|
316
335
|
otpExpired: store.otpExpired,
|
|
336
|
+
otpUserId: store.otpSent ? store._otpUserId : null,
|
|
317
337
|
error: store.error
|
|
318
338
|
};
|
|
319
339
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
@@ -335,6 +355,7 @@ function initializeAuthStore() {
|
|
|
335
355
|
otpExpired: false, // OTP (email/phone): the entered code was wrong/expired
|
|
336
356
|
otpPhrase: null, // Email OTP: security phrase to display (when enabled; phone has none)
|
|
337
357
|
_otpUserId: null, // OTP: userId returned by createEmailToken/createPhoneToken, used by verifyOTP
|
|
358
|
+
_otpMigrationTicket: null, // OTP: { ticket, userId } guest-migration ticket, kept across failed verify attempts
|
|
338
359
|
teams: [], // List of user's teams
|
|
339
360
|
currentTeam: null, // Currently selected/active team
|
|
340
361
|
_teamsPollInterval: null, // Interval ID for teams polling (deprecated, use realtime instead)
|
|
@@ -713,9 +734,12 @@ function initializeAuthStore() {
|
|
|
713
734
|
} catch (error) {
|
|
714
735
|
if (!current()) return;
|
|
715
736
|
if (!isNoSession(error)) { scheduleRecheck(this); return; }
|
|
737
|
+
const prevId = shownUserId(this);
|
|
716
738
|
this._clearIdentity();
|
|
717
739
|
markVerified(this);
|
|
718
740
|
syncStateToStorage(this);
|
|
741
|
+
// Rows shown for the last-known user drop
|
|
742
|
+
if (prevId) window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
719
743
|
if (this._guestAuto && this._createAnonymousSession) await this._createAnonymousSession();
|
|
720
744
|
return;
|
|
721
745
|
} finally {
|
|
@@ -907,6 +931,9 @@ function initializeAuthStore() {
|
|
|
907
931
|
return { success: false, error: 'Appwrite not configured' };
|
|
908
932
|
}
|
|
909
933
|
|
|
934
|
+
// A ticket kept from a failed guest OTP must not outlive an explicit logout
|
|
935
|
+
this._otpMigrationTicket = null;
|
|
936
|
+
|
|
910
937
|
// If not authenticated, nothing to logout from
|
|
911
938
|
if (!this.isAuthenticated) {
|
|
912
939
|
return { success: true };
|
|
@@ -1080,12 +1107,18 @@ function initializeAuthStore() {
|
|
|
1080
1107
|
return this.user;
|
|
1081
1108
|
} catch (error) {
|
|
1082
1109
|
if (!isNoSession(error)) throw error;
|
|
1110
|
+
const prevId = shownUserId(this);
|
|
1083
1111
|
markVerified(this);
|
|
1084
1112
|
this.isAuthenticated = false;
|
|
1085
1113
|
this.isAnonymous = false;
|
|
1086
1114
|
this.user = null;
|
|
1087
1115
|
this.session = null;
|
|
1088
1116
|
syncStateToStorage(this);
|
|
1117
|
+
if (prevId) {
|
|
1118
|
+
this.teams = [];
|
|
1119
|
+
this.currentTeam = null;
|
|
1120
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared'));
|
|
1121
|
+
}
|
|
1089
1122
|
throw error;
|
|
1090
1123
|
}
|
|
1091
1124
|
}
|
|
@@ -6474,26 +6507,33 @@ function initializeEmailOTP() {
|
|
|
6474
6507
|
|
|
6475
6508
|
this.inProgress = true;
|
|
6476
6509
|
this.error = null;
|
|
6510
|
+
let guestEnded = false;
|
|
6477
6511
|
|
|
6478
6512
|
try {
|
|
6479
6513
|
const appwriteConfig = await config.getAppwriteConfig();
|
|
6480
6514
|
const wasGuest = this.isAnonymous;
|
|
6481
6515
|
|
|
6482
|
-
// Guest team carryover: issue the migration ticket
|
|
6483
|
-
//
|
|
6484
|
-
|
|
6516
|
+
// Guest team carryover: issue the migration ticket while the guest session
|
|
6517
|
+
// still authenticates. Kept on the store, bound to this code's account, so a
|
|
6518
|
+
// retry after a wrong code redeems it and no other sign-in can.
|
|
6485
6519
|
if (wasGuest && appwriteConfig?.guestMigrationFunctionId && this._callGuestMigration) {
|
|
6486
6520
|
const prep = await this._callGuestMigration('/prepare', {});
|
|
6487
|
-
if (prep?.ok && prep.ticket)
|
|
6521
|
+
if (prep?.ok && prep.ticket) {
|
|
6522
|
+
this._otpMigrationTicket = { ticket: prep.ticket, userId: this._otpUserId };
|
|
6523
|
+
}
|
|
6488
6524
|
}
|
|
6525
|
+
const kept = this._otpMigrationTicket;
|
|
6526
|
+
const migrationTicket = kept?.userId === this._otpUserId ? kept.ticket : null;
|
|
6489
6527
|
|
|
6490
|
-
// Appwrite
|
|
6491
|
-
//
|
|
6528
|
+
// Appwrite refuses createSession while any session is active, before it
|
|
6529
|
+
// checks the code, so the guest session must go first.
|
|
6492
6530
|
if (this.session && this.isAnonymous) {
|
|
6493
6531
|
try {
|
|
6494
6532
|
await this._appwrite.account.deleteSession(this.session.$id);
|
|
6533
|
+
guestEnded = true;
|
|
6495
6534
|
} catch (deleteError) {
|
|
6496
|
-
|
|
6535
|
+
const status = deleteError?.code || deleteError?.statusCode;
|
|
6536
|
+
guestEnded = status === 401 || status === 404;
|
|
6497
6537
|
}
|
|
6498
6538
|
}
|
|
6499
6539
|
|
|
@@ -6516,6 +6556,7 @@ function initializeEmailOTP() {
|
|
|
6516
6556
|
|
|
6517
6557
|
// Redeem the migration ticket as the new account to carry teams over.
|
|
6518
6558
|
// Best-effort; failure never blocks the already-successful sign-in.
|
|
6559
|
+
this._otpMigrationTicket = null;
|
|
6519
6560
|
if (migrationTicket && this._callGuestMigration) {
|
|
6520
6561
|
await this._callGuestMigration('/commit', { ticket: migrationTicket });
|
|
6521
6562
|
}
|
|
@@ -6541,7 +6582,7 @@ function initializeEmailOTP() {
|
|
|
6541
6582
|
} catch (error) {
|
|
6542
6583
|
const errorMessage = error.message || '';
|
|
6543
6584
|
const errorCode = error.code || error.statusCode || '';
|
|
6544
|
-
const isExpiredOrInvalid = errorMessage && (
|
|
6585
|
+
const isExpiredOrInvalid = error.type !== 'user_session_already_exists' && errorMessage && (
|
|
6545
6586
|
errorMessage.includes('expired') ||
|
|
6546
6587
|
errorMessage.includes('Invalid token') ||
|
|
6547
6588
|
errorMessage.includes('invalid') ||
|
|
@@ -6551,14 +6592,30 @@ function initializeEmailOTP() {
|
|
|
6551
6592
|
|
|
6552
6593
|
this.otpExpired = !!isExpiredOrInvalid;
|
|
6553
6594
|
this.error = isExpiredOrInvalid ? null : error.message;
|
|
6554
|
-
|
|
6555
|
-
|
|
6595
|
+
|
|
6596
|
+
// Guest session is gone though sign-in failed: drop its identity and teams.
|
|
6597
|
+
// otpSent, the code's userId and the migration ticket stay for a retry.
|
|
6598
|
+
if (guestEnded) {
|
|
6599
|
+
this.isAuthenticated = false;
|
|
6600
|
+
this.isAnonymous = false;
|
|
6601
|
+
this.user = null;
|
|
6602
|
+
this.session = null;
|
|
6603
|
+
if (this._resetTeamsState) {
|
|
6604
|
+
this._resetTeamsState();
|
|
6605
|
+
}
|
|
6606
|
+
}
|
|
6556
6607
|
|
|
6557
6608
|
if (this._syncStateToStorage) {
|
|
6558
6609
|
this._syncStateToStorage(this);
|
|
6559
6610
|
}
|
|
6560
6611
|
|
|
6561
|
-
|
|
6612
|
+
if (guestEnded) {
|
|
6613
|
+
window.dispatchEvent(new CustomEvent('manifest:auth:session-cleared', {
|
|
6614
|
+
detail: { reason: 'otp-failed' }
|
|
6615
|
+
}));
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6618
|
+
return { success: false, error: error.message, guestEnded };
|
|
6562
6619
|
} finally {
|
|
6563
6620
|
this.inProgress = false;
|
|
6564
6621
|
}
|
package/lib/manifest.d.ts
CHANGED
|
@@ -434,7 +434,7 @@ export interface ManifestTextEdit {
|
|
|
434
434
|
export type ManifestDirective =
|
|
435
435
|
| 'x-route' | 'x-anchors'
|
|
436
436
|
| 'x-icon' | 'x-svg' | 'x-markdown' | 'x-code' | 'x-code-group'
|
|
437
|
-
| 'x-toast' | 'x-tooltip' | 'x-carousel' | 'x-virtual' | 'x-
|
|
437
|
+
| 'x-toast' | 'x-tooltip' | 'x-carousel' | 'x-virtual' | 'x-computed' | 'x-defer'
|
|
438
438
|
| 'x-chart' | 'x-date' | 'x-color' | 'x-colorpicker' | 'x-combobox'
|
|
439
439
|
| 'x-export' | 'x-import' | 'x-pay' | 'x-files' | 'x-data-files'
|
|
440
440
|
| 'x-edit' | 'x-text-edit';
|
package/lib/manifest.data.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
|
|
5
|
-
const MANIFEST_BUILD_VERSION = '0.5.
|
|
5
|
+
const MANIFEST_BUILD_VERSION = '0.5.226';
|
|
6
6
|
|
|
7
7
|
/* Manifest Data Sources - Configuration */
|
|
8
8
|
|
|
@@ -1593,6 +1593,7 @@ window.ManifestDataStore = {
|
|
|
1593
1593
|
scope: '',
|
|
1594
1594
|
generation: 0,
|
|
1595
1595
|
pending: new Map(), // source -> time its debounced write is due
|
|
1596
|
+
landedIdentity: new Map(), // source -> identity its pending rows were read under
|
|
1596
1597
|
writeTimer: null,
|
|
1597
1598
|
hydrated: new Set(), // sources hydrated (or attempted) this generation
|
|
1598
1599
|
hydratedIdentity: null, // identity auth-dependent hydration last ran under
|
|
@@ -1609,10 +1610,10 @@ window.ManifestDataStore = {
|
|
|
1609
1610
|
const liveLocale = () => (typeof document !== 'undefined' && document.documentElement?.lang)
|
|
1610
1611
|
|| (typeof Alpine !== 'undefined' && Alpine.store?.('locale')?.current) || 'en';
|
|
1611
1612
|
|
|
1612
|
-
// Auth-dependent sources (scope / $auth. queries) carry the identity they were read under
|
|
1613
|
+
// Auth-dependent sources (Appwrite tables/buckets, scope / $auth. queries) carry the identity they were read under
|
|
1613
1614
|
function isAuthDependent(source) {
|
|
1614
1615
|
if (!source || typeof source !== 'object') return false;
|
|
1615
|
-
if (source.scope) return true;
|
|
1616
|
+
if (source.scope || source.appwriteTableId || source.appwriteBucketId) return true;
|
|
1616
1617
|
try { return JSON.stringify(source.queries || '').includes('$auth.'); } catch { return false; }
|
|
1617
1618
|
}
|
|
1618
1619
|
|
|
@@ -1629,6 +1630,19 @@ window.ManifestDataStore = {
|
|
|
1629
1630
|
return auth?._sessionUnverified === true;
|
|
1630
1631
|
}
|
|
1631
1632
|
|
|
1633
|
+
// Auth's last recorded identity (cross-tab mirror): '' signed out, null unknown
|
|
1634
|
+
function lastKnownIdentity() {
|
|
1635
|
+
try {
|
|
1636
|
+
const saved = JSON.parse((typeof window !== 'undefined' && window.localStorage?.getItem('manifest:auth:state')) || 'null');
|
|
1637
|
+
if (!saved || typeof saved !== 'object') return null;
|
|
1638
|
+
return saved.isAuthenticated ? (String(saved.user?.$id || saved.user?.id || '') || null) : '';
|
|
1639
|
+
} catch { return null; }
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// Unconfirmed (booting / offline): hydration trusts the last recorded identity; the data reset covers a mismatch
|
|
1643
|
+
function unconfirmed() { return authIdentity() === null || authUnverified(); }
|
|
1644
|
+
function hydrationIdentity() { return unconfirmed() ? lastKnownIdentity() : authIdentity(); }
|
|
1645
|
+
|
|
1632
1646
|
function warnOnce(key, message, error) {
|
|
1633
1647
|
if (state.warned.has(key)) return;
|
|
1634
1648
|
state.warned.add(key);
|
|
@@ -1735,6 +1749,7 @@ window.ManifestDataStore = {
|
|
|
1735
1749
|
function register(source, config) {
|
|
1736
1750
|
const cfg = normalizeConfig(config === undefined ? true : config);
|
|
1737
1751
|
if (!cfg) { state.sources.delete(source); return false; }
|
|
1752
|
+
cfg.authDependent = isAuthDependent(state.manifest?.data?.[source] || state.manifest?.appwrite?.[source]);
|
|
1738
1753
|
state.sources.set(source, cfg);
|
|
1739
1754
|
state.hydrated.delete(source);
|
|
1740
1755
|
state.fetchKicked.delete(source);
|
|
@@ -1819,12 +1834,22 @@ window.ManifestDataStore = {
|
|
|
1819
1834
|
if (next !== state.scope) changeScope(next);
|
|
1820
1835
|
}
|
|
1821
1836
|
|
|
1837
|
+
// Pending saves of rows read under an identity that has since changed never land
|
|
1838
|
+
function dropStaleWrites() {
|
|
1839
|
+
if (unconfirmed()) return;
|
|
1840
|
+
const identity = authIdentity();
|
|
1841
|
+
for (const [source, landed] of [...state.landedIdentity]) {
|
|
1842
|
+
if (landed !== null && landed !== identity) cancelWrites(source);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1822
1846
|
function watchScope() {
|
|
1823
1847
|
if (state.watching || typeof window === 'undefined') return;
|
|
1824
1848
|
state.watching = true;
|
|
1825
1849
|
for (const type of AUTH_EVENTS) {
|
|
1826
1850
|
window.addEventListener(type, () => {
|
|
1827
1851
|
if (!state.enabled) return;
|
|
1852
|
+
dropStaleWrites();
|
|
1828
1853
|
if (WIPE_EVENTS.has(type)) deleteScope(state.scope);
|
|
1829
1854
|
refreshScope();
|
|
1830
1855
|
if (!state.scopePending) hydrate(bootSources()).catch(() => { /* disabled */ });
|
|
@@ -1998,14 +2023,18 @@ window.ManifestDataStore = {
|
|
|
1998
2023
|
return null;
|
|
1999
2024
|
}
|
|
2000
2025
|
|
|
2001
|
-
function snapshotRecord(source) {
|
|
2026
|
+
function snapshotRecord(source, landed) {
|
|
2002
2027
|
const cfg = state.sources.get(source);
|
|
2003
2028
|
const ds = dataStore();
|
|
2004
2029
|
if (!cfg || !ds) return null;
|
|
2005
2030
|
const raw = ds.getRawData(source);
|
|
2006
2031
|
if (raw === null || raw === undefined) return null;
|
|
2007
|
-
|
|
2008
|
-
if (
|
|
2032
|
+
let identity;
|
|
2033
|
+
if (cfg.authDependent) {
|
|
2034
|
+
// Stamped with the identity the rows were read under; skipped if it changed since or was never known
|
|
2035
|
+
identity = authUnverified() ? null : authIdentity();
|
|
2036
|
+
if (identity === null || landed === null || (landed !== undefined && landed !== identity)) return null;
|
|
2037
|
+
}
|
|
2009
2038
|
try {
|
|
2010
2039
|
const snapshot = snapshotOf(source, raw, cfg);
|
|
2011
2040
|
if (snapshot === null) return null;
|
|
@@ -2028,9 +2057,10 @@ window.ManifestDataStore = {
|
|
|
2028
2057
|
|
|
2029
2058
|
// Every source whose debounce has elapsed is written in ONE transaction
|
|
2030
2059
|
function flush(sources) {
|
|
2031
|
-
|
|
2060
|
+
const landedIdentity = new Map(sources.map(source => [source, state.landedIdentity.get(source)]));
|
|
2061
|
+
for (const source of sources) { state.pending.delete(source); state.landedIdentity.delete(source); }
|
|
2032
2062
|
if (!state.enabled || state.disabled) return Promise.resolve();
|
|
2033
|
-
const records = sources.map(snapshotRecord).filter(Boolean);
|
|
2063
|
+
const records = sources.map(source => snapshotRecord(source, landedIdentity.get(source))).filter(Boolean);
|
|
2034
2064
|
if (!records.length) return Promise.resolve();
|
|
2035
2065
|
const generation = state.generation;
|
|
2036
2066
|
return withStore('readwrite', (store) => { for (const record of records) store.put(record); }).then(() => {
|
|
@@ -2062,8 +2092,9 @@ window.ManifestDataStore = {
|
|
|
2062
2092
|
}
|
|
2063
2093
|
|
|
2064
2094
|
function cancelWrites(source) {
|
|
2065
|
-
if (source !== undefined) { state.pending.delete(source); return; }
|
|
2095
|
+
if (source !== undefined) { state.pending.delete(source); state.landedIdentity.delete(source); return; }
|
|
2066
2096
|
state.pending.clear();
|
|
2097
|
+
state.landedIdentity.clear();
|
|
2067
2098
|
if (state.writeTimer !== null) { clearTimeout(state.writeTimer); state.writeTimer = null; }
|
|
2068
2099
|
}
|
|
2069
2100
|
|
|
@@ -2071,7 +2102,12 @@ window.ManifestDataStore = {
|
|
|
2071
2102
|
function onLanded(sources) {
|
|
2072
2103
|
if (!state.enabled || state.disabled || state.scopePending) return; // nothing is keyed under an unresolved scope
|
|
2073
2104
|
const at = Date.now() + WRITE_DEBOUNCE_MS;
|
|
2074
|
-
|
|
2105
|
+
const identity = hydrationIdentity();
|
|
2106
|
+
for (const source of sources) {
|
|
2107
|
+
if (!state.sources.has(source)) continue;
|
|
2108
|
+
state.pending.set(source, at);
|
|
2109
|
+
if (state.sources.get(source).authDependent) state.landedIdentity.set(source, identity);
|
|
2110
|
+
}
|
|
2075
2111
|
if (state.writeTimer !== null) { clearTimeout(state.writeTimer); state.writeTimer = null; }
|
|
2076
2112
|
scheduleWrites();
|
|
2077
2113
|
}
|
|
@@ -2085,8 +2121,11 @@ window.ManifestDataStore = {
|
|
|
2085
2121
|
if (majorMinor(record.frameworkVersion) !== majorMinor(state.frameworkVersion)) return { ok: false, drop: true };
|
|
2086
2122
|
if (record.locale && record.locale !== liveLocale()) return { ok: false };
|
|
2087
2123
|
// Another identity's (or an unstamped) snapshot never hydrates: the scope alone may be '' for everyone
|
|
2088
|
-
//
|
|
2089
|
-
if (cfg.authDependent
|
|
2124
|
+
// Before the session is confirmed (booting / offline) a mismatch is kept for the check to come back
|
|
2125
|
+
if (cfg.authDependent) {
|
|
2126
|
+
const identity = hydrationIdentity();
|
|
2127
|
+
if (identity === null || record.identity !== identity) return unconfirmed() ? { ok: false } : { ok: false, drop: true };
|
|
2128
|
+
}
|
|
2090
2129
|
return { ok: true };
|
|
2091
2130
|
}
|
|
2092
2131
|
|
|
@@ -2095,7 +2134,7 @@ window.ManifestDataStore = {
|
|
|
2095
2134
|
async function hydrate(sources) {
|
|
2096
2135
|
const ds = dataStore();
|
|
2097
2136
|
// Auth-dependent sources wait for a known identity (re-run on auth events), and retry when it changes
|
|
2098
|
-
const identity =
|
|
2137
|
+
const identity = hydrationIdentity();
|
|
2099
2138
|
const known = identity !== null;
|
|
2100
2139
|
if (known && identity !== state.hydratedIdentity) {
|
|
2101
2140
|
for (const [source, cfg] of state.sources) if (cfg.authDependent) state.hydrated.delete(source);
|
|
@@ -8666,10 +8705,8 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8666
8705
|
}
|
|
8667
8706
|
return null;
|
|
8668
8707
|
} else if (method === '$remove') {
|
|
8669
|
-
//
|
|
8708
|
+
// Delete file(s), drop them from the source, unlink from table entries
|
|
8670
8709
|
// Usage: $x.assets.$remove(fileId)
|
|
8671
|
-
// Automatically reloads the entire assets bucket and unlinks from table entries
|
|
8672
|
-
// This is an alias for $delete with the same auto-reload functionality
|
|
8673
8710
|
const fileId = args[0];
|
|
8674
8711
|
if (!fileId) {
|
|
8675
8712
|
throw new Error('[Manifest Data] $remove requires file ID');
|
|
@@ -8687,20 +8724,15 @@ function createAppwriteMethodsHandler(dataSourceName, reloadDataSource) {
|
|
|
8687
8724
|
// Continue with delete even if unlink fails
|
|
8688
8725
|
}
|
|
8689
8726
|
|
|
8690
|
-
|
|
8691
|
-
const deletePromise = Array.isArray(fileId)
|
|
8692
|
-
? Promise.all(fileId.map(id => window.ManifestDataAppwrite.deleteFile(bucketId, id.$id || id)))
|
|
8693
|
-
: window.ManifestDataAppwrite.deleteFile(bucketId, actualFileId);
|
|
8694
|
-
|
|
8727
|
+
const deletedIds = (Array.isArray(fileId) ? fileId : [fileId]).map(id => id.$id || id);
|
|
8695
8728
|
const generation = window.ManifestDataStore?.sourceGeneration?.(dataSourceName);
|
|
8696
|
-
const
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
const [result, reloadedData] = await Promise.all([deletePromise, reloadPromise]);
|
|
8729
|
+
const result = Array.isArray(fileId)
|
|
8730
|
+
? await Promise.all(deletedIds.map(id => window.ManifestDataAppwrite.deleteFile(bucketId, id)))
|
|
8731
|
+
: await window.ManifestDataAppwrite.deleteFile(bucketId, actualFileId);
|
|
8700
8732
|
|
|
8701
|
-
//
|
|
8702
|
-
if (
|
|
8703
|
-
await window.ManifestDataStore.
|
|
8733
|
+
// Drop the deleted file(s) locally: a relisting would bypass the source's queries and scope
|
|
8734
|
+
if (window.ManifestDataStore?.landRemove) {
|
|
8735
|
+
await window.ManifestDataStore.landRemove(dataSourceName, deletedIds, { generation });
|
|
8704
8736
|
}
|
|
8705
8737
|
|
|
8706
8738
|
// Reload affected table data sources so fileIds arrays and counters
|
|
@@ -11489,15 +11521,37 @@ function authSettledUnresolvable(manifest) {
|
|
|
11489
11521
|
return auth._initialized === true && auth.isAuthenticated === true && teamsSettled(auth, manifest);
|
|
11490
11522
|
}
|
|
11491
11523
|
|
|
11492
|
-
|
|
11524
|
+
// Rows depend on who reads them: scope / $auth. queries, or any Appwrite table/bucket (row/file permissions).
|
|
11525
|
+
// Reset on identity change only; whether a read needs an identity is buildAppwriteQueries' call (null = wait).
|
|
11526
|
+
function isIdentityBound(config) {
|
|
11493
11527
|
if (!config || typeof config !== 'object') return false;
|
|
11528
|
+
if (window.ManifestDataConfig?.isAppwriteCollection?.(config)) return true;
|
|
11494
11529
|
if (window.ManifestDataConfig?.getScope?.(config)) return true;
|
|
11495
11530
|
try { return JSON.stringify(config.queries || '').includes('$auth.'); } catch { return false; }
|
|
11496
11531
|
}
|
|
11497
11532
|
|
|
11498
|
-
//
|
|
11533
|
+
// Auth's last recorded identity (cross-tab mirror): '' signed out, null unknown
|
|
11534
|
+
function lastKnownIdentity() {
|
|
11535
|
+
try {
|
|
11536
|
+
const saved = JSON.parse(window.localStorage?.getItem('manifest:auth:state') || 'null');
|
|
11537
|
+
if (!saved || typeof saved !== 'object') return null;
|
|
11538
|
+
return saved.isAuthenticated ? (String(saved.user?.$id || saved.user?.id || '') || null) : '';
|
|
11539
|
+
} catch { return null; }
|
|
11540
|
+
}
|
|
11541
|
+
|
|
11542
|
+
// Confirmed identity: '' signed out (or no auth plugin), null while booting or offline-unverified
|
|
11543
|
+
function confirmedIdentity() {
|
|
11544
|
+
const auth = currentAuth();
|
|
11545
|
+
if (!auth) return '';
|
|
11546
|
+
if (auth._initialized !== true || (auth._sessionUnverified === true && auth.isAuthenticated !== true)) return null;
|
|
11547
|
+
return auth.isAuthenticated === true ? (authUserId(auth) || '') : '';
|
|
11548
|
+
}
|
|
11549
|
+
|
|
11550
|
+
// Identity change (sign-in, sign-out, account switch): identity-bound sources reload for the new identity.
|
|
11551
|
+
// A previous user's rows drop at once; rows read signed out stay live until the reload lands.
|
|
11499
11552
|
function setupAuthResetListener() {
|
|
11500
|
-
|
|
11553
|
+
let bound = (currentAuth() ? confirmedIdentity() : null) ?? lastKnownIdentity();
|
|
11554
|
+
const reset = async (clear) => {
|
|
11501
11555
|
const manifest = await window.ManifestDataConfig?.ensureManifest?.();
|
|
11502
11556
|
const ds = window.ManifestDataStore;
|
|
11503
11557
|
const store = typeof Alpine !== 'undefined' ? Alpine.store('data') : null;
|
|
@@ -11506,14 +11560,27 @@ function setupAuthResetListener() {
|
|
|
11506
11560
|
const configs = { ...(manifest.appwrite || {}), ...(manifest.data || {}) };
|
|
11507
11561
|
const locale = liveLocale();
|
|
11508
11562
|
for (const [name, config] of Object.entries(configs)) {
|
|
11509
|
-
if (!
|
|
11510
|
-
|
|
11511
|
-
|
|
11563
|
+
if (!isIdentityBound(config) || raw[`_${name}_state`] === undefined) continue;
|
|
11564
|
+
if (clear) {
|
|
11565
|
+
window.ManifestDataRealtime?.unsubscribeFromDataSource?.(name);
|
|
11566
|
+
ds.resetSource(name);
|
|
11567
|
+
}
|
|
11512
11568
|
loadDataSource(name, locale, { reload: true });
|
|
11513
11569
|
}
|
|
11514
11570
|
};
|
|
11515
|
-
|
|
11516
|
-
|
|
11571
|
+
const onAuth = (type) => {
|
|
11572
|
+
const ended = type === 'manifest:auth:logout' || type === 'manifest:auth:session-cleared';
|
|
11573
|
+
const next = confirmedIdentity() ?? (ended ? '' : null);
|
|
11574
|
+
if (next === null) return;
|
|
11575
|
+
const prev = bound;
|
|
11576
|
+
bound = next;
|
|
11577
|
+
if (prev === next) return;
|
|
11578
|
+
// Unknown before: rows so far were read under this same session (a sign-out still drops them)
|
|
11579
|
+
if (prev === null && !ended) return;
|
|
11580
|
+
reset(prev !== '');
|
|
11581
|
+
};
|
|
11582
|
+
['manifest:auth:logout', 'manifest:auth:session-cleared', 'manifest:auth:login', 'manifest:auth:anonymous', 'manifest:auth:initialized']
|
|
11583
|
+
.forEach(type => window.addEventListener(type, () => onAuth(type)));
|
|
11517
11584
|
}
|
|
11518
11585
|
|
|
11519
11586
|
// Client-side scope filter: Appwrite returns all accessible files, so narrow to
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"manifest.appwrite.auth.js": "sha384-
|
|
3
|
-
"manifest.appwrite.data.js": "sha384-
|
|
2
|
+
"manifest.appwrite.auth.js": "sha384-Ek2fHprBtcIFHAkOHtELD40gjfME3lWdmIQGB7h8+gcm7hn5QJzt9DRpo9huz4BE",
|
|
3
|
+
"manifest.appwrite.data.js": "sha384-bwfWutqQz9CnVgvXIKCR9VZrRQ3ytswJ6zvKOevhLobHOaryZazSAr4tFRurawiZ",
|
|
4
4
|
"manifest.appwrite.presences.js": "sha384-JyVDWFT69b7Svq8J8p9tNWe2v9PZNQBY5UyhtOl1vnuHFAX0CRQ3MA9SiKq5wzNh",
|
|
5
5
|
"manifest.bindings.js": "sha384-lPtMf8Ykaic9gNVZKq8Scia1881ZeMAyxer3WCIkUNwwrgzGBWto1OXADUwjvORF",
|
|
6
6
|
"manifest.charts.js": "sha384-gEAv/98+29RbhvNkiNWTNyBPbLTFr2Ld15wLu29FLCL57j1cwGChRrL9Pfk7dBtT",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"manifest.combobox.js": "sha384-MILTkrgsTzsYUjGo4ZuADqV85Xs4GmfB4xexfe9vbJMIoQTZ42W4zs+DTaVSPEsx",
|
|
12
12
|
"manifest.components.js": "sha384-4bc3HgPvF9DHMiOG08w+nWXI7Hho01RuAPAUL9xyLxOiS2igeZpulCVcvr5aT9v6",
|
|
13
13
|
"manifest.computed.js": "sha384-GYML/e81gQfN+iDbqhPUgny8bINCyL+SrbTofoGk4hlCnLLjv51mCv42JMqq+ku5",
|
|
14
|
-
"manifest.data.js": "sha384-
|
|
14
|
+
"manifest.data.js": "sha384-Dtpxer0CayxZCsGD8zCe8lW4MwsnaWA7Yxu1cWBxPQI1PJHh6qCwEJejHYt2go3F",
|
|
15
15
|
"manifest.datepicker.js": "sha384-XnbeURni2X91ozVzatQcSjYYIS4MoW12dBRJnYAGBr0nCQ624heH5s+xtQSpLZX6",
|
|
16
16
|
"manifest.defer.js": "sha384-WcCf8YQ1bKIXCLSZ24a0bJwepbYntYc8TzQM50hklo5VMIH8AtzDnwl6WU4prRv1",
|
|
17
17
|
"manifest.device.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
@@ -23,12 +23,11 @@
|
|
|
23
23
|
"manifest.markdown.js": "sha384-JWUxlUZADxk1hZY7NpDX051UhtXt/ofpfsCDBRcweU6JhLbN8B832yPLgCatxFdl",
|
|
24
24
|
"manifest.native.js": "sha384-g+txmrEszYtuOvRelTp/c1wSNR2AtllVI9rx5NPPD7DzhVUnMNughonZykv2xlBW",
|
|
25
25
|
"manifest.payments.js": "sha384-8pofk2zhxtapPMpwnFyTelPMrYhShNhDCE722uQh/EYFvZFuPLmSdgunqsarVQjv",
|
|
26
|
-
"manifest.resize.js": "sha384-+0IsyrGP/4gZmNbD6oU8dCgEyhpBsXMm74NIenYeUGuGRWyJDurcG27xE/CA0/aA",
|
|
27
26
|
"manifest.router.js": "sha384-xkQaHO60lMQwtnqGPxPJJ646eyqBjINs4AFesrQbe2dWqPIoIcJ9RD5wsjqjak7w",
|
|
28
27
|
"manifest.slides.js": "sha384-3uRTkyK9XPLmnxI2+igZlpi4EyPlU/7IHj5j3BZJJ2KN455vXyk99fiXV3feO/XY",
|
|
29
28
|
"manifest.status.js": "sha384-MBO345ab7H6Uq1X2dZLt4Ge1Gcra+Z2qYApvlAeIV8CJhDDJrz2FPXpp+Kcp8zcM",
|
|
30
29
|
"manifest.svg.js": "sha384-qr/8H9+2cIhSXNbRWRtIdrDlTOxKlNJqwEefHvCYoiBNW/kKhPTFa109Orv/EBnb",
|
|
31
|
-
"manifest.sw.js": "sha384-
|
|
30
|
+
"manifest.sw.js": "sha384-e59BjfTqiJXTPdMgK3bkvYA2mzha1WEQeLgPABSDg35IPb+5Zd0BMUvND2kxrxoc",
|
|
32
31
|
"manifest.tabs.js": "sha384-OIIcyaQnGS0eNYk4zgAFEDboNYect7THvUd9D0V6nqCeDOxQcoiqLrBEmmWVTTet",
|
|
33
32
|
"manifest.tailwind.js": "sha384-r/Jvqb+Piz21jUts10BbVAJoKkShOdJRVFpywyi4HPIi/OfOJq5CtPyWAPbdkMgt",
|
|
34
33
|
"manifest.text.edit.js": "sha384-RzOvLQE8GbmI6gntAwiotDewYT/08OHcd7sJ5hLon8e8u1Xbq1D7VfEGuaRVd2CE",
|
package/lib/manifest.sw.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
// Build stamps the published version here; unstamped (dev) falls back to the
|
|
18
18
|
// registration URL's `v`, then 'dev'.
|
|
19
|
-
const BUILD_VERSION = '0.5.
|
|
19
|
+
const BUILD_VERSION = '0.5.226';
|
|
20
20
|
const CACHE_PREFIX = 'mnfst-sw:';
|
|
21
21
|
const META_CACHE = CACHE_PREFIX + 'meta';
|
|
22
22
|
// Sized to hold a full precache.json warm-up (PRECACHE_MAX_ENTRIES server-side)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mnfst",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.226",
|
|
4
4
|
"private": false,
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"templates/starter",
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"test:e2e:sdk": "node tests/e2e/payments.sdk.e2e.mjs",
|
|
48
48
|
"test:e2e:sw": "node tests/e2e/sw.e2e.mjs",
|
|
49
49
|
"lint": "echo 'No linting configured'",
|
|
50
|
-
"release:docs": "node scripts/release-bump.mjs packages/docs && cd packages/docs && npm publish --auth-type=web"
|
|
50
|
+
"release:docs": "node scripts/release-bump.mjs packages/docs && cd packages/docs && npm publish --auth-type=web",
|
|
51
|
+
"release:check": "node scripts/release-check.mjs"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@rollup/plugin-commonjs": "^28.0.1",
|
package/lib/manifest.resize.css
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/* Manifest Resize Handles
|
|
2
|
-
|
|
3
|
-
Superseded by x-edit.size (manifest.edit.css) — kept for existing projects.
|
|
4
|
-
Self-contained: handle geometry lives here, not in a shared snippet. */
|
|
5
|
-
|
|
6
|
-
@layer base, components, utilities;
|
|
7
|
-
|
|
8
|
-
@layer utilities {
|
|
9
|
-
|
|
10
|
-
:where([x-resize]) {
|
|
11
|
-
position: relative;
|
|
12
|
-
|
|
13
|
-
/* Resize handle area */
|
|
14
|
-
.resize-handle {
|
|
15
|
-
position: absolute;
|
|
16
|
-
display: block;
|
|
17
|
-
width: var(--h);
|
|
18
|
-
height: var(--h);
|
|
19
|
-
z-index: 100;
|
|
20
|
-
background: transparent;
|
|
21
|
-
border: none;
|
|
22
|
-
outline: none;
|
|
23
|
-
--h: var(--spacing-resize-handle, 1rem);
|
|
24
|
-
|
|
25
|
-
/* Visible handle */
|
|
26
|
-
&::before {
|
|
27
|
-
content: '';
|
|
28
|
-
position: absolute;
|
|
29
|
-
top: 50%;
|
|
30
|
-
left: 50%;
|
|
31
|
-
transform: translate(-50%, -50%);
|
|
32
|
-
width: 1px;
|
|
33
|
-
height: 1px;
|
|
34
|
-
background: transparent;
|
|
35
|
-
transition: background-color 0.2s ease;
|
|
36
|
-
pointer-events: none
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
&:hover::before,
|
|
40
|
-
&:active::before {
|
|
41
|
-
background-color: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/* Handle geometry — edges span their axis, corners stay square */
|
|
46
|
-
.resize-handle-top,
|
|
47
|
-
.resize-handle-bottom {
|
|
48
|
-
left: 0;
|
|
49
|
-
width: 100%;
|
|
50
|
-
height: var(--h);
|
|
51
|
-
cursor: ns-resize;
|
|
52
|
-
|
|
53
|
-
&::before {
|
|
54
|
-
width: 100%
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.resize-handle-left,
|
|
59
|
-
.resize-handle-right,
|
|
60
|
-
.resize-handle-start,
|
|
61
|
-
.resize-handle-end {
|
|
62
|
-
top: 0;
|
|
63
|
-
width: var(--h);
|
|
64
|
-
height: 100%;
|
|
65
|
-
cursor: ew-resize;
|
|
66
|
-
|
|
67
|
-
&::before {
|
|
68
|
-
height: 100%
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.resize-handle-top {
|
|
73
|
-
top: calc(var(--h) * -0.5)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.resize-handle-bottom {
|
|
77
|
-
bottom: calc(var(--h) * -0.5)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.resize-handle-left,
|
|
81
|
-
.resize-handle-top-left,
|
|
82
|
-
.resize-handle-bottom-left {
|
|
83
|
-
left: calc(var(--h) * -0.5)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.resize-handle-right,
|
|
87
|
-
.resize-handle-top-right,
|
|
88
|
-
.resize-handle-bottom-right {
|
|
89
|
-
right: calc(var(--h) * -0.5)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/* Logical edges — inset-inline-* auto-flips for RTL */
|
|
93
|
-
.resize-handle-start,
|
|
94
|
-
.resize-handle-top-start,
|
|
95
|
-
.resize-handle-bottom-start {
|
|
96
|
-
inset-inline-start: calc(var(--h) * -0.5)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.resize-handle-end,
|
|
100
|
-
.resize-handle-top-end,
|
|
101
|
-
.resize-handle-bottom-end {
|
|
102
|
-
inset-inline-end: calc(var(--h) * -0.5)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.resize-handle-top-left,
|
|
106
|
-
.resize-handle-top-right,
|
|
107
|
-
.resize-handle-top-start,
|
|
108
|
-
.resize-handle-top-end {
|
|
109
|
-
top: calc(var(--h) * -0.5)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.resize-handle-bottom-left,
|
|
113
|
-
.resize-handle-bottom-right,
|
|
114
|
-
.resize-handle-bottom-start,
|
|
115
|
-
.resize-handle-bottom-end {
|
|
116
|
-
bottom: calc(var(--h) * -0.5)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/* Corner cursors */
|
|
120
|
-
.resize-handle-top-left,
|
|
121
|
-
.resize-handle-top-start {
|
|
122
|
-
cursor: nw-resize;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.resize-handle-top-right,
|
|
126
|
-
.resize-handle-top-end {
|
|
127
|
-
cursor: ne-resize;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.resize-handle-bottom-left,
|
|
131
|
-
.resize-handle-bottom-start {
|
|
132
|
-
cursor: sw-resize;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.resize-handle-bottom-right,
|
|
136
|
-
.resize-handle-bottom-end {
|
|
137
|
-
cursor: se-resize;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/* Screen overlay to get mouse coordinates */
|
|
141
|
-
.resize-overlay {
|
|
142
|
-
position: fixed;
|
|
143
|
-
top: 0;
|
|
144
|
-
left: 0;
|
|
145
|
-
width: 100vw;
|
|
146
|
-
height: 100vh;
|
|
147
|
-
z-index: 9999;
|
|
148
|
-
display: none;
|
|
149
|
-
background: transparent
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/* Resize states */
|
|
153
|
-
.resizable-closing {
|
|
154
|
-
opacity: 0.5;
|
|
155
|
-
transition: opacity 0.2s ease
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
.resizable-closed {
|
|
159
|
-
display: none !important
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/* Focus states for accessibility — framework ring (manifest.theme.css) */
|
|
163
|
-
.resize-handle:focus {
|
|
164
|
-
outline: none;
|
|
165
|
-
box-shadow: 0 0 0 2px color-mix(in oklch, var(--color-content-stark) 30%, transparent)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/* Reduced motion support */
|
|
169
|
-
@media (prefers-reduced-motion: reduce) {
|
|
170
|
-
|
|
171
|
-
:where(.resizable-closing,
|
|
172
|
-
.resize-handle) {
|
|
173
|
-
transition: none
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/* RTL cursor flips — placement follows the logical insets in the shared geometry */
|
|
179
|
-
[dir=rtl] :where([x-resize]) .resize-handle-top-start {
|
|
180
|
-
cursor: ne-resize;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
[dir=rtl] :where([x-resize]) .resize-handle-top-end {
|
|
184
|
-
cursor: nw-resize;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
[dir=rtl] :where([x-resize]) .resize-handle-bottom-start {
|
|
188
|
-
cursor: se-resize;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
[dir=rtl] :where([x-resize]) .resize-handle-bottom-end {
|
|
192
|
-
cursor: sw-resize;
|
|
193
|
-
}
|
|
194
|
-
}
|
package/lib/manifest.resize.js
DELETED
|
@@ -1,488 +0,0 @@
|
|
|
1
|
-
/* Manifest Resizer */
|
|
2
|
-
|
|
3
|
-
(function () {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function initializeResizablePlugin() {
|
|
7
|
-
// Cache for unit conversions to avoid repeated DOM manipulation
|
|
8
|
-
const unitCache = new Map();
|
|
9
|
-
let tempConversionEl = null;
|
|
10
|
-
|
|
11
|
-
// Helper to parse value and unit from CSS dimension
|
|
12
|
-
const parseDimension = (value) => {
|
|
13
|
-
if (typeof value === 'number') return { value, unit: 'px' };
|
|
14
|
-
const match = String(value).match(/^([\d.]+)(.*)$/);
|
|
15
|
-
return match ? { value: parseFloat(match[1]), unit: match[2] || 'px' } : { value: 0, unit: 'px' };
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Helper to convert any unit to pixels (cached and optimized)
|
|
19
|
-
const convertToPixels = (value, unit) => {
|
|
20
|
-
if (unit === 'px') return value;
|
|
21
|
-
|
|
22
|
-
const cacheKey = `${value}${unit}`;
|
|
23
|
-
if (unitCache.has(cacheKey)) {
|
|
24
|
-
return unitCache.get(cacheKey);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Use a single cached conversion element
|
|
28
|
-
if (!tempConversionEl) {
|
|
29
|
-
tempConversionEl = document.createElement('div');
|
|
30
|
-
tempConversionEl.style.cssText = 'visibility:hidden;position:absolute;top:-9999px;left:-9999px;width:0;height:0;';
|
|
31
|
-
document.body.appendChild(tempConversionEl);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
tempConversionEl.style.width = `${value}${unit}`;
|
|
35
|
-
const pixels = tempConversionEl.getBoundingClientRect().width;
|
|
36
|
-
|
|
37
|
-
unitCache.set(cacheKey, pixels);
|
|
38
|
-
return pixels;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// Helper to convert pixels back to original unit (cached)
|
|
42
|
-
const convertFromPixels = (pixels, unit, element) => {
|
|
43
|
-
if (unit === 'px') return pixels;
|
|
44
|
-
|
|
45
|
-
const cacheKey = `${pixels}${unit}${element.tagName}`;
|
|
46
|
-
if (unitCache.has(cacheKey)) {
|
|
47
|
-
return unitCache.get(cacheKey);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let result;
|
|
51
|
-
switch (unit) {
|
|
52
|
-
case '%':
|
|
53
|
-
result = (pixels / element.parentElement.getBoundingClientRect().width) * 100;
|
|
54
|
-
break;
|
|
55
|
-
case 'rem':
|
|
56
|
-
const remSize = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
57
|
-
result = pixels / remSize;
|
|
58
|
-
break;
|
|
59
|
-
case 'em':
|
|
60
|
-
const emSize = parseFloat(getComputedStyle(element).fontSize);
|
|
61
|
-
result = pixels / emSize;
|
|
62
|
-
break;
|
|
63
|
-
default:
|
|
64
|
-
result = pixels;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
unitCache.set(cacheKey, result);
|
|
68
|
-
return result;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
Alpine.directive('resize', (el, { modifiers, expression }, { evaluate }) => {
|
|
72
|
-
// Store configuration on the element for lazy initialization
|
|
73
|
-
el._resizeConfig = {
|
|
74
|
-
expression,
|
|
75
|
-
evaluate,
|
|
76
|
-
handles: null,
|
|
77
|
-
initialized: false
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Load saved width and height immediately if specified
|
|
81
|
-
if (expression) {
|
|
82
|
-
try {
|
|
83
|
-
const options = evaluate(expression);
|
|
84
|
-
if (options) {
|
|
85
|
-
if (options.saveWidth) {
|
|
86
|
-
const savedWidth = localStorage.getItem(`resizable-${options.saveWidth}`);
|
|
87
|
-
if (savedWidth) {
|
|
88
|
-
// Preserve the original unit if saved
|
|
89
|
-
const [value, unit] = savedWidth.split('|');
|
|
90
|
-
el.style.width = `${value}${unit || 'px'}`;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (options.saveHeight) {
|
|
94
|
-
const savedHeight = localStorage.getItem(`resizable-${options.saveHeight}`);
|
|
95
|
-
if (savedHeight) {
|
|
96
|
-
// Preserve the original unit if saved
|
|
97
|
-
const [value, unit] = savedHeight.split('|');
|
|
98
|
-
el.style.height = `${value}${unit || 'px'}`;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} catch (error) {
|
|
103
|
-
// Ignore parsing errors here, they'll be handled in initializeResizeElement
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Add hover listener to create handles on first interaction
|
|
108
|
-
el.addEventListener('mouseenter', initializeResizeElement, { once: true });
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
function initializeResizeElement(event) {
|
|
112
|
-
const el = event.target;
|
|
113
|
-
const config = el._resizeConfig;
|
|
114
|
-
if (config.initialized) return;
|
|
115
|
-
|
|
116
|
-
config.initialized = true;
|
|
117
|
-
|
|
118
|
-
// Helper to parse value and unit from CSS dimension
|
|
119
|
-
const parseDimension = (value) => {
|
|
120
|
-
if (typeof value === 'number') return { value, unit: 'px' };
|
|
121
|
-
const match = String(value).match(/^([\d.]+)(.*)$/);
|
|
122
|
-
return match ? { value: parseFloat(match[1]), unit: match[2] || 'px' } : { value: 0, unit: 'px' };
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// Parse options from expression or use defaults
|
|
126
|
-
let options = {};
|
|
127
|
-
if (config.expression) {
|
|
128
|
-
try {
|
|
129
|
-
options = config.evaluate(config.expression);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error('Error parsing x-resize expression:', config.expression, error);
|
|
132
|
-
options = {};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const {
|
|
136
|
-
snapDistance = 0,
|
|
137
|
-
snapPoints = [],
|
|
138
|
-
snapCloseX = null,
|
|
139
|
-
snapDistanceX = null,
|
|
140
|
-
snapDistanceY = null,
|
|
141
|
-
snapPointsX = [],
|
|
142
|
-
snapPointsY = [],
|
|
143
|
-
snapCloseY = null,
|
|
144
|
-
toggle = null,
|
|
145
|
-
handles = ['top', 'bottom', 'left', 'right', 'top-left', 'top-right', 'bottom-left', 'bottom-right'],
|
|
146
|
-
saveWidth = null,
|
|
147
|
-
saveHeight = null
|
|
148
|
-
} = options;
|
|
149
|
-
|
|
150
|
-
// Store handles for cleanup
|
|
151
|
-
config.handles = [];
|
|
152
|
-
|
|
153
|
-
// Parse constraints with units
|
|
154
|
-
const constraints = {
|
|
155
|
-
closeX: snapCloseX ? parseDimension(snapCloseX) : null,
|
|
156
|
-
closeY: snapCloseY ? parseDimension(snapCloseY) : null
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
// Parse snap points with units
|
|
160
|
-
const parsedSnapPoints = snapPoints.map(point => parseDimension(point));
|
|
161
|
-
const parsedSnapPointsX = snapPointsX.map(point => parseDimension(point));
|
|
162
|
-
const parsedSnapPointsY = snapPointsY.map(point => parseDimension(point));
|
|
163
|
-
|
|
164
|
-
// Detect RTL context
|
|
165
|
-
const isRTL = getComputedStyle(el).direction === 'rtl';
|
|
166
|
-
|
|
167
|
-
// Handle mapping for resize behavior
|
|
168
|
-
const handleMap = {
|
|
169
|
-
// Physical directions (fixed)
|
|
170
|
-
top: { edge: 'top', direction: 'vertical' },
|
|
171
|
-
bottom: { edge: 'bottom', direction: 'vertical' },
|
|
172
|
-
left: { edge: 'left', direction: 'horizontal' },
|
|
173
|
-
right: { edge: 'right', direction: 'horizontal' },
|
|
174
|
-
|
|
175
|
-
// Corners
|
|
176
|
-
'top-left': { edge: 'top-left', direction: 'both', edges: ['top', 'left'] },
|
|
177
|
-
'top-right': { edge: 'top-right', direction: 'both', edges: ['top', 'right'] },
|
|
178
|
-
'bottom-left': { edge: 'bottom-left', direction: 'both', edges: ['bottom', 'left'] },
|
|
179
|
-
'bottom-right': { edge: 'bottom-right', direction: 'both', edges: ['bottom', 'right'] },
|
|
180
|
-
|
|
181
|
-
// Logical directions (RTL-aware)
|
|
182
|
-
start: {
|
|
183
|
-
edge: isRTL ? 'right' : 'left',
|
|
184
|
-
direction: 'horizontal',
|
|
185
|
-
logical: true
|
|
186
|
-
},
|
|
187
|
-
end: {
|
|
188
|
-
edge: isRTL ? 'left' : 'right',
|
|
189
|
-
direction: 'horizontal',
|
|
190
|
-
logical: true
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
// Logical corners
|
|
194
|
-
'top-start': {
|
|
195
|
-
edge: isRTL ? 'top-right' : 'top-left',
|
|
196
|
-
direction: 'both',
|
|
197
|
-
edges: isRTL ? ['top', 'right'] : ['top', 'left'],
|
|
198
|
-
logical: true
|
|
199
|
-
},
|
|
200
|
-
'top-end': {
|
|
201
|
-
edge: isRTL ? 'top-left' : 'top-right',
|
|
202
|
-
direction: 'both',
|
|
203
|
-
edges: isRTL ? ['top', 'left'] : ['top', 'right'],
|
|
204
|
-
logical: true
|
|
205
|
-
},
|
|
206
|
-
'bottom-start': {
|
|
207
|
-
edge: isRTL ? 'bottom-right' : 'bottom-left',
|
|
208
|
-
direction: 'both',
|
|
209
|
-
edges: isRTL ? ['bottom', 'right'] : ['bottom', 'left'],
|
|
210
|
-
logical: true
|
|
211
|
-
},
|
|
212
|
-
'bottom-end': {
|
|
213
|
-
edge: isRTL ? 'bottom-left' : 'bottom-right',
|
|
214
|
-
direction: 'both',
|
|
215
|
-
edges: isRTL ? ['bottom', 'left'] : ['bottom', 'right'],
|
|
216
|
-
logical: true
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
// Create handles for each specified handle
|
|
221
|
-
handles.forEach(handleName => {
|
|
222
|
-
const handleInfo = handleMap[handleName];
|
|
223
|
-
if (!handleInfo) return;
|
|
224
|
-
|
|
225
|
-
const handle = document.createElement('span');
|
|
226
|
-
handle.className = `resize-handle resize-handle-${handleName}`;
|
|
227
|
-
handle.setAttribute('data-handle', handleName);
|
|
228
|
-
|
|
229
|
-
let startX, startY, startWidth, startHeight;
|
|
230
|
-
let currentSnap = null;
|
|
231
|
-
let lastWidth, lastHeight;
|
|
232
|
-
|
|
233
|
-
// Convert constraints to pixels for calculations
|
|
234
|
-
const pixelConstraints = {
|
|
235
|
-
closeX: constraints.closeX ? convertToPixels(constraints.closeX.value, constraints.closeX.unit) : null,
|
|
236
|
-
closeY: constraints.closeY ? convertToPixels(constraints.closeY.value, constraints.closeY.unit) : null
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
// Convert snap points to pixels
|
|
240
|
-
const pixelSnapPoints = parsedSnapPoints.map(point => ({
|
|
241
|
-
value: convertToPixels(point.value, point.unit),
|
|
242
|
-
unit: point.unit
|
|
243
|
-
}));
|
|
244
|
-
const pixelSnapPointsX = parsedSnapPointsX.map(point => ({
|
|
245
|
-
value: convertToPixels(point.value, point.unit),
|
|
246
|
-
unit: point.unit
|
|
247
|
-
}));
|
|
248
|
-
const pixelSnapPointsY = parsedSnapPointsY.map(point => ({
|
|
249
|
-
value: convertToPixels(point.value, point.unit),
|
|
250
|
-
unit: point.unit
|
|
251
|
-
}));
|
|
252
|
-
|
|
253
|
-
const snapDistancePixels = convertToPixels(snapDistance, 'px');
|
|
254
|
-
const snapDistanceXPixels = snapDistanceX ? convertToPixels(snapDistanceX, 'px') : snapDistancePixels;
|
|
255
|
-
const snapDistanceYPixels = snapDistanceY ? convertToPixels(snapDistanceY, 'px') : snapDistancePixels;
|
|
256
|
-
|
|
257
|
-
const handleMouseDown = (e) => {
|
|
258
|
-
e.preventDefault();
|
|
259
|
-
e.stopPropagation();
|
|
260
|
-
|
|
261
|
-
el.classList.add('resizable-dragging');
|
|
262
|
-
|
|
263
|
-
startX = e.clientX;
|
|
264
|
-
startY = e.clientY;
|
|
265
|
-
startWidth = el.getBoundingClientRect().width;
|
|
266
|
-
startHeight = el.getBoundingClientRect().height;
|
|
267
|
-
|
|
268
|
-
// Each drag starts with clean snap state
|
|
269
|
-
currentSnap = null;
|
|
270
|
-
lastWidth = startWidth;
|
|
271
|
-
lastHeight = startHeight;
|
|
272
|
-
|
|
273
|
-
// Show overlay
|
|
274
|
-
const overlay = document.querySelector('.resize-overlay') || createOverlay();
|
|
275
|
-
overlay.style.display = 'block';
|
|
276
|
-
document.body.appendChild(overlay);
|
|
277
|
-
|
|
278
|
-
document.addEventListener('mousemove', handleMouseMove);
|
|
279
|
-
document.addEventListener('mouseup', handleMouseUp);
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
const handleMouseMove = (e) => {
|
|
283
|
-
const deltaX = e.clientX - startX;
|
|
284
|
-
const deltaY = e.clientY - startY;
|
|
285
|
-
|
|
286
|
-
let newWidth = startWidth;
|
|
287
|
-
let newHeight = startHeight;
|
|
288
|
-
|
|
289
|
-
// Calculate new dimensions based on handle type
|
|
290
|
-
if (handleInfo.direction === 'horizontal' || handleInfo.direction === 'both') {
|
|
291
|
-
if (handleInfo.edge === 'left' || handleInfo.edge === 'top-left' || handleInfo.edge === 'bottom-left') {
|
|
292
|
-
newWidth = startWidth - deltaX;
|
|
293
|
-
} else if (handleInfo.edge === 'right' || handleInfo.edge === 'top-right' || handleInfo.edge === 'bottom-right') {
|
|
294
|
-
newWidth = startWidth + deltaX;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (handleInfo.direction === 'vertical' || handleInfo.direction === 'both') {
|
|
299
|
-
if (handleInfo.edge === 'top' || handleInfo.edge === 'top-left' || handleInfo.edge === 'top-right') {
|
|
300
|
-
newHeight = startHeight - deltaY;
|
|
301
|
-
} else if (handleInfo.edge === 'bottom' || handleInfo.edge === 'bottom-left' || handleInfo.edge === 'bottom-right') {
|
|
302
|
-
newHeight = startHeight + deltaY;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Handle snap-close behavior for width
|
|
307
|
-
if (pixelConstraints.closeX !== null && newWidth <= pixelConstraints.closeX) {
|
|
308
|
-
// Close only while shrinking toward the close edge
|
|
309
|
-
if (newWidth < lastWidth || currentSnap === 'closing') {
|
|
310
|
-
el.classList.add('resizable-closing');
|
|
311
|
-
currentSnap = 'closing';
|
|
312
|
-
|
|
313
|
-
if (toggle) {
|
|
314
|
-
config.evaluate(`${toggle} = false`);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
el.dispatchEvent(new CustomEvent('resize', {
|
|
318
|
-
detail: {
|
|
319
|
-
width: newWidth,
|
|
320
|
-
height: newHeight,
|
|
321
|
-
unit: 'px',
|
|
322
|
-
snap: currentSnap,
|
|
323
|
-
closing: true
|
|
324
|
-
}
|
|
325
|
-
}));
|
|
326
|
-
}
|
|
327
|
-
lastWidth = newWidth;
|
|
328
|
-
lastHeight = newHeight;
|
|
329
|
-
return; // Exit early to prevent further width calculations
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Handle snap-close behavior for height
|
|
333
|
-
if (pixelConstraints.closeY !== null && newHeight <= pixelConstraints.closeY) {
|
|
334
|
-
// Close only while shrinking toward the close edge
|
|
335
|
-
if (newHeight < lastHeight || currentSnap === 'closing') {
|
|
336
|
-
el.classList.add('resizable-closing');
|
|
337
|
-
currentSnap = 'closing';
|
|
338
|
-
|
|
339
|
-
if (toggle) {
|
|
340
|
-
config.evaluate(`${toggle} = false`);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
el.dispatchEvent(new CustomEvent('resize', {
|
|
344
|
-
detail: {
|
|
345
|
-
width: newWidth,
|
|
346
|
-
height: newHeight,
|
|
347
|
-
unit: 'px',
|
|
348
|
-
snap: currentSnap,
|
|
349
|
-
closing: true
|
|
350
|
-
}
|
|
351
|
-
}));
|
|
352
|
-
}
|
|
353
|
-
lastWidth = newWidth;
|
|
354
|
-
lastHeight = newHeight;
|
|
355
|
-
return; // Exit early to prevent further height calculations
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Above close thresholds: clear any closing state
|
|
359
|
-
if (currentSnap === 'closing') {
|
|
360
|
-
currentSnap = null;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Apply constraints only if we're not closing
|
|
364
|
-
// Note: maxWidth and maxHeight are now handled by CSS (e.g., Tailwind classes)
|
|
365
|
-
|
|
366
|
-
// Handle normal snap points for width
|
|
367
|
-
const widthSnapPoints = pixelSnapPointsX.length > 0 ? pixelSnapPointsX : pixelSnapPoints;
|
|
368
|
-
const widthSnapDistance = snapDistanceXPixels;
|
|
369
|
-
for (const point of widthSnapPoints) {
|
|
370
|
-
const distance = Math.abs(newWidth - point.value);
|
|
371
|
-
if (distance < widthSnapDistance) {
|
|
372
|
-
newWidth = point.value;
|
|
373
|
-
currentSnap = `${convertFromPixels(newWidth, point.unit, el)}${point.unit}`;
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Handle normal snap points for height
|
|
379
|
-
const heightSnapPoints = pixelSnapPointsY.length > 0 ? pixelSnapPointsY : pixelSnapPoints;
|
|
380
|
-
const heightSnapDistance = snapDistanceYPixels;
|
|
381
|
-
for (const point of heightSnapPoints) {
|
|
382
|
-
const distance = Math.abs(newHeight - point.value);
|
|
383
|
-
if (distance < heightSnapDistance) {
|
|
384
|
-
newHeight = point.value;
|
|
385
|
-
currentSnap = `${convertFromPixels(newHeight, point.unit, el)}${point.unit}`;
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Convert back to original unit for display
|
|
391
|
-
el.style.width = `${newWidth}px`;
|
|
392
|
-
el.style.height = `${newHeight}px`;
|
|
393
|
-
lastWidth = newWidth;
|
|
394
|
-
lastHeight = newHeight;
|
|
395
|
-
el.classList.remove('resizable-closing', 'resizable-closed');
|
|
396
|
-
if (toggle) {
|
|
397
|
-
config.evaluate(`${toggle} = true`);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (currentSnap !== 'closing') {
|
|
401
|
-
if (saveWidth) {
|
|
402
|
-
localStorage.setItem(`resizable-${saveWidth}`,
|
|
403
|
-
`${newWidth}|px`);
|
|
404
|
-
}
|
|
405
|
-
if (saveHeight) {
|
|
406
|
-
localStorage.setItem(`resizable-${saveHeight}`,
|
|
407
|
-
`${newHeight}|px`);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Dispatch resize event
|
|
412
|
-
el.dispatchEvent(new CustomEvent('resize', {
|
|
413
|
-
detail: {
|
|
414
|
-
width: newWidth,
|
|
415
|
-
height: newHeight,
|
|
416
|
-
unit: 'px',
|
|
417
|
-
snap: currentSnap,
|
|
418
|
-
closing: currentSnap === 'closing'
|
|
419
|
-
}
|
|
420
|
-
}));
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
const handleMouseUp = () => {
|
|
424
|
-
document.removeEventListener('mousemove', handleMouseMove);
|
|
425
|
-
document.removeEventListener('mouseup', handleMouseUp);
|
|
426
|
-
|
|
427
|
-
el.classList.remove('resizable-dragging');
|
|
428
|
-
|
|
429
|
-
// Hide overlay
|
|
430
|
-
const overlay = document.querySelector('.resize-overlay');
|
|
431
|
-
if (overlay) {
|
|
432
|
-
overlay.style.display = 'none';
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
if (currentSnap === 'closing') {
|
|
436
|
-
el.classList.add('resizable-closed');
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
|
|
440
|
-
handle.addEventListener('mousedown', handleMouseDown);
|
|
441
|
-
el.appendChild(handle);
|
|
442
|
-
config.handles.push(handle);
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
function createOverlay() {
|
|
447
|
-
const overlay = document.createElement('div');
|
|
448
|
-
overlay.className = 'resize-overlay';
|
|
449
|
-
overlay.style.display = 'none';
|
|
450
|
-
return overlay;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Track initialization to prevent duplicates
|
|
455
|
-
let resizePluginInitialized = false;
|
|
456
|
-
|
|
457
|
-
function ensureResizePluginInitialized() {
|
|
458
|
-
if (resizePluginInitialized) return;
|
|
459
|
-
if (!window.Alpine || typeof window.Alpine.directive !== 'function') return;
|
|
460
|
-
|
|
461
|
-
resizePluginInitialized = true;
|
|
462
|
-
initializeResizablePlugin();
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Expose on window for loader to call if needed
|
|
466
|
-
window.ensureResizePluginInitialized = ensureResizePluginInitialized;
|
|
467
|
-
|
|
468
|
-
// Handle both DOMContentLoaded and alpine:init
|
|
469
|
-
if (document.readyState === 'loading') {
|
|
470
|
-
document.addEventListener('DOMContentLoaded', ensureResizePluginInitialized);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
document.addEventListener('alpine:init', ensureResizePluginInitialized);
|
|
474
|
-
|
|
475
|
-
// If Alpine is already initialized when this script loads, initialize immediately
|
|
476
|
-
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
|
477
|
-
setTimeout(ensureResizePluginInitialized, 0);
|
|
478
|
-
} else {
|
|
479
|
-
const checkAlpine = setInterval(() => {
|
|
480
|
-
if (window.Alpine && typeof window.Alpine.directive === 'function') {
|
|
481
|
-
clearInterval(checkAlpine);
|
|
482
|
-
ensureResizePluginInitialized();
|
|
483
|
-
}
|
|
484
|
-
}, 10);
|
|
485
|
-
setTimeout(() => clearInterval(checkAlpine), 5000);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
})();
|