mnfst 0.5.187 → 0.5.189

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.d.ts CHANGED
@@ -139,41 +139,49 @@ export interface ManifestLocale {
139
139
  set(locale: string): void;
140
140
  }
141
141
 
142
- /** `$toast` from the toasts plugin. */
143
- export interface ManifestToast {
144
- (message: string, opts?: { type?: 'info' | 'success' | 'warning' | 'error'; duration?: number }): void;
145
- info(message: string, opts?: Record<string, unknown>): void;
146
- success(message: string, opts?: Record<string, unknown>): void;
147
- warning(message: string, opts?: Record<string, unknown>): void;
148
- error(message: string, opts?: Record<string, unknown>): void;
149
- dismiss(id?: string): void;
142
+ /** One toast flavor: callable, with `.dismiss` (adds a close button) and `.fixed` (sticky) variants. */
143
+ export interface ManifestToastVariant {
144
+ (message: string, opts?: Record<string, unknown>): void;
145
+ dismiss(message: string, opts?: Record<string, unknown>): void;
146
+ fixed(message: string, opts?: Record<string, unknown>): void;
150
147
  }
151
148
 
152
- /** `$colors` from the themes plugin. */
153
- export interface ManifestTheme {
154
- readonly current: 'light' | 'dark' | 'system';
155
- set(theme: 'light' | 'dark' | 'system'): void;
156
- toggle(): void;
149
+ /** `$toast` from the toasts plugin. Typed variants: `$toast.brand(...)`, `$toast.positive.dismiss(...)`. */
150
+ export interface ManifestToast extends ManifestToastVariant {
151
+ brand: ManifestToastVariant;
152
+ accent: ManifestToastVariant;
153
+ positive: ManifestToastVariant;
154
+ negative: ManifestToastVariant;
157
155
  }
158
156
 
159
- /** `$colors` from the colors plugin. */
160
- export interface ManifestColors {
161
- [name: string]: string | ManifestColors;
157
+ /** `$color` from the color plugin — the current light/dark/system color mode. */
158
+ export interface ManifestColor {
159
+ current: 'light' | 'dark' | 'system' | string;
162
160
  }
163
161
 
164
- /** `$colorpicker` from the colorpicker plugin. */
162
+ /** `$colorpicker` from the colorpicker plugin. Loose by design. */
165
163
  export interface ManifestColorpicker {
166
- open(opts?: Record<string, unknown>): void;
167
- close(): void;
164
+ (id?: string): Record<string, unknown>;
168
165
  [extra: string]: unknown;
169
166
  }
170
167
 
171
- /** `$url` from the url-parameters plugin: read/write URL query params reactively. */
168
+ /** One URL query parameter handle from `$url.<name>` (reactive). */
169
+ export interface ManifestUrlParam {
170
+ /** Current value (get/set; empty string when absent). */
171
+ value: string;
172
+ /** First value when the param repeats. */
173
+ readonly first: string;
174
+ /** All values for a repeating param. */
175
+ readonly all: string[];
176
+ set(value: string): void;
177
+ add(value: string): void;
178
+ remove(value?: string): void;
179
+ clear(): void;
180
+ }
181
+
182
+ /** `$url` from the url-parameters plugin: `$url.tab.value`, `$url.tag.add('x')`, ... */
172
183
  export interface ManifestUrl {
173
- get(name: string): string | null;
174
- set(name: string, value: string | null): void;
175
- has(name: string): boolean;
176
- [name: string]: unknown;
184
+ [param: string]: ManifestUrlParam;
177
185
  }
178
186
 
179
187
  /** `$route` magic — true if the current route matches the given pattern. */
@@ -188,6 +196,152 @@ export type ManifestModify = (attr: string) => string | undefined;
188
196
  /** `$try(fn)` — run a callback and swallow any thrown error, returning undefined on failure. */
189
197
  export type ManifestTry = <T>(fn: () => T) => T | undefined;
190
198
 
199
+ /** `$pay` from the payments plugin: callable to start checkout, plus portal/state helpers. */
200
+ export interface ManifestPay {
201
+ (ref: string, payload?: Record<string, unknown>): Promise<unknown>;
202
+ portal(ref?: string, payload?: Record<string, unknown>): Promise<unknown>;
203
+ refresh(): Promise<unknown>;
204
+ register(name: string, adapter: unknown): void;
205
+ readonly state: Record<string, unknown> | undefined;
206
+ readonly loading: boolean | undefined;
207
+ [extra: string]: unknown;
208
+ }
209
+
210
+ /** A conversation handle returned by `$chat.open(...)`. Loose by design. */
211
+ export interface ManifestChatHandle {
212
+ readonly messages: unknown[];
213
+ readonly participants: unknown[];
214
+ readonly typing: unknown[];
215
+ readonly me: unknown;
216
+ [extra: string]: unknown;
217
+ }
218
+
219
+ /** `$chat` from the chat plugin — adapter-based conversation projection. */
220
+ export interface ManifestChat {
221
+ open(conversationId: string, opts?: Record<string, unknown>): ManifestChatHandle;
222
+ merge(handles: ManifestChatHandle[], opts?: Record<string, unknown>): ManifestChatHandle;
223
+ adapter(name: string, factory?: unknown): unknown;
224
+ flatten(tree: unknown): unknown[];
225
+ /** Shared revision counter — trackable even before any handle resolves. */
226
+ readonly version: number;
227
+ [extra: string]: unknown;
228
+ }
229
+
230
+ /** One `$status.<name>` entry. */
231
+ export interface ManifestStatusEntry {
232
+ readonly state: string;
233
+ [extra: string]: unknown;
234
+ }
235
+
236
+ /** `$status` from the status plugin: `$status.api.state`, iterable, stringifies to the overall state. */
237
+ export interface ManifestStatus extends Iterable<ManifestStatusEntry> {
238
+ readonly [name: string]: any;
239
+ }
240
+
241
+ /** `$presence` from the Appwrite presences plugin. */
242
+ export interface ManifestPresence {
243
+ start(opts?: Record<string, unknown>): Promise<unknown>;
244
+ stop(): void;
245
+ set(values: Record<string, unknown>): Promise<unknown>;
246
+ clear(): Promise<unknown>;
247
+ of(userId: string): unknown;
248
+ all(): unknown[];
249
+ readonly records: Record<string, unknown>;
250
+ readonly list: unknown[];
251
+ readonly ready: boolean;
252
+ readonly error: unknown;
253
+ readonly me: unknown;
254
+ }
255
+
256
+ /** `$date(id?)` from the datepicker plugin — the picker API for the nearest (or given) instance. */
257
+ export type ManifestDate = (id?: string) => Record<string, unknown>;
258
+
259
+ /** `$chart(id?)` from the charts plugin — the chart API for the nearest (or given) instance. */
260
+ export type ManifestChart = (id?: string) => Record<string, unknown>;
261
+
262
+ /** `$export(opts)` from the export plugin — export the page or a data source (pdf/csv/json/...). */
263
+ export type ManifestExport = (opts?: {
264
+ format?: string;
265
+ filename?: string;
266
+ source?: string;
267
+ data?: unknown;
268
+ [extra: string]: unknown;
269
+ }) => Promise<unknown>;
270
+
271
+ /** `$device` from the utilities plugin (enriched by the native plugin when present). */
272
+ export interface ManifestDevice {
273
+ readonly os: string;
274
+ readonly touch: boolean;
275
+ readonly online: boolean;
276
+ readonly standalone: boolean;
277
+ readonly native: boolean;
278
+ readonly platform: string;
279
+ }
280
+
281
+ /** `$share` from the native plugin — native sheet, web share, or clipboard fallback. */
282
+ export type ManifestShare = (opts?: { title?: string; text?: string; url?: string }) =>
283
+ Promise<{ shared: boolean; method: string; cancelled?: boolean }>;
284
+
285
+ /** `$push` from the native plugin — push/notification permissions and token. */
286
+ export interface ManifestPush {
287
+ request(): Promise<string>;
288
+ readonly permission: string;
289
+ [extra: string]: unknown;
290
+ }
291
+
292
+ /** `$secure` from the native plugin — secure key-value storage (native keychain or fallback). */
293
+ export interface ManifestSecure {
294
+ get(key: string): Promise<string | null>;
295
+ set(key: string, value: string): Promise<void>;
296
+ remove(key: string): Promise<void>;
297
+ keys(): Promise<string[]>;
298
+ clear(): Promise<void>;
299
+ use(adapter: unknown): void;
300
+ }
301
+
302
+ /** `$links` from the native plugin — deep/universal link handling. */
303
+ export interface ManifestLinks {
304
+ on(handler: (url: string) => void): void;
305
+ open(url: string): void;
306
+ readonly last: string | null;
307
+ }
308
+
309
+ /** `$app` from the native plugin — foreground/background state. */
310
+ export interface ManifestApp {
311
+ readonly active: boolean;
312
+ onChange(handler: (active: boolean) => void): void;
313
+ }
314
+
315
+ /** `$haptics` from the native plugin — haptic feedback with web vibration fallback. */
316
+ export interface ManifestHaptics {
317
+ impact(style?: string): Promise<unknown>;
318
+ notification(type?: string): Promise<unknown>;
319
+ [extra: string]: unknown;
320
+ }
321
+
322
+ /** `$biometric` from the native plugin. */
323
+ export interface ManifestBiometric {
324
+ available(): Promise<boolean>;
325
+ verify(opts?: Record<string, unknown>): Promise<unknown>;
326
+ }
327
+
328
+ /** `$camera` from the native plugin. */
329
+ export interface ManifestCamera {
330
+ photo(opts?: Record<string, unknown>): Promise<unknown>;
331
+ pick(opts?: Record<string, unknown>): Promise<unknown>;
332
+ }
333
+
334
+ /**
335
+ * Manifest's custom-element/attribute authoring surface (documentation aid —
336
+ * these are HTML attributes, not JS globals). Element styles pair with most.
337
+ */
338
+ export type ManifestDirective =
339
+ | 'x-route' | 'x-anchors'
340
+ | 'x-icon' | 'x-svg' | 'x-markdown' | 'x-code' | 'x-code-group'
341
+ | 'x-toast' | 'x-tooltip' | 'x-carousel' | 'x-virtual' | 'x-resize'
342
+ | 'x-chart' | 'x-date' | 'x-color' | 'x-colorpicker' | 'x-combobox'
343
+ | 'x-export' | 'x-pay' | 'x-files' | 'x-data-files';
344
+
191
345
  // ---------------------------------------------------------------------------
192
346
  // Global declarations (what Alpine exposes inside `x-data`, `x-show`, etc.)
193
347
  // ---------------------------------------------------------------------------
@@ -201,20 +355,50 @@ declare global {
201
355
  const $modify: ManifestModify;
202
356
  /** Run a callback safely, returning undefined on error. */
203
357
  const $try: ManifestTry;
204
- /** Appwrite auth plugin (only present when the plugin is loaded). */
358
+ /** Appwrite auth plugin (present when the plugin is loaded). */
205
359
  const $auth: ManifestAuth;
206
360
  /** Localization plugin. */
207
361
  const $locale: ManifestLocale;
208
362
  /** Toasts plugin. */
209
363
  const $toast: ManifestToast;
210
- /** Themes plugin. */
211
- const $colors: ManifestTheme;
212
- /** Colors plugin. */
213
- const $colors: ManifestColors;
364
+ /** Color-mode plugin (`$color.current = 'dark'`). */
365
+ const $color: ManifestColor;
214
366
  /** Colorpicker plugin. */
215
367
  const $colorpicker: ManifestColorpicker;
216
- /** URL parameters plugin. */
368
+ /** URL parameters plugin (`$url.tab.value`). */
217
369
  const $url: ManifestUrl;
370
+ /** Payments plugin. */
371
+ const $pay: ManifestPay;
372
+ /** Chat plugin. */
373
+ const $chat: ManifestChat;
374
+ /** Status plugin. */
375
+ const $status: ManifestStatus;
376
+ /** Appwrite presences plugin. */
377
+ const $presence: ManifestPresence;
378
+ /** Datepicker plugin. */
379
+ const $date: ManifestDate;
380
+ /** Charts plugin. */
381
+ const $chart: ManifestChart;
382
+ /** Export plugin. */
383
+ const $export: ManifestExport;
384
+ /** Device/platform info (utilities; enriched by the native plugin). */
385
+ const $device: ManifestDevice;
386
+ /** Native share (native plugin). */
387
+ const $share: ManifestShare;
388
+ /** Push permissions (native plugin). */
389
+ const $push: ManifestPush;
390
+ /** Secure storage (native plugin). */
391
+ const $secure: ManifestSecure;
392
+ /** Deep links (native plugin). */
393
+ const $links: ManifestLinks;
394
+ /** App foreground state (native plugin). */
395
+ const $app: ManifestApp;
396
+ /** Haptics (native plugin). */
397
+ const $haptics: ManifestHaptics;
398
+ /** Biometric auth (native plugin). */
399
+ const $biometric: ManifestBiometric;
400
+ /** Camera (native plugin). */
401
+ const $camera: ManifestCamera;
218
402
 
219
403
  /** Window-level Manifest namespace exposed by the loader. */
220
404
  interface Window {
@@ -1845,18 +1845,6 @@ window.ManifestDataAPI = {
1845
1845
 
1846
1846
 
1847
1847
 
1848
- /* Manifest Data Sources - Error Handling & Loading States */
1849
-
1850
- // Placeholder for Phase 4: Error handling and loading states
1851
- // This will be implemented in Phase 4
1852
-
1853
- // Export empty object for now
1854
- window.ManifestDataErrors = {
1855
- // Phase 4: Error handling will be added here
1856
- };
1857
-
1858
-
1859
-
1860
1848
  /* Manifest Data Sources - Unified Mutation System */
1861
1849
  // Handles optimistic updates, scoped updates, rollback, and background sync
1862
1850
 
@@ -3221,6 +3209,40 @@ function attachArrayMethods(array, dataSourceName, reloadDataSource) {
3221
3209
  }
3222
3210
 
3223
3211
  const term = searchTerm.toLowerCase().trim();
3212
+
3213
+ // Weighted mode: $search(term, { title: 3, body: 1 }) — ranks by the
3214
+ // best-matching field's weight per whitespace-separated term (prefix
3215
+ // match gets a small boost); every term must match somewhere.
3216
+ const first = attributes[0];
3217
+ if (attributes.length === 1 && first && typeof first === 'object' && !Array.isArray(first)) {
3218
+ const fields = Object.keys(first);
3219
+ const terms = term.split(/\s+/).filter(Boolean);
3220
+ const scored = [];
3221
+ for (const item of array) {
3222
+ if (!item || typeof item !== 'object') continue;
3223
+ let total = 0, ok = true;
3224
+ for (const t of terms) {
3225
+ let best = 0;
3226
+ for (const f of fields) {
3227
+ const v = item[f];
3228
+ if (v == null) continue;
3229
+ const s = String(v).toLowerCase();
3230
+ if (!s.includes(t)) continue;
3231
+ let w = Number(first[f]) || 0;
3232
+ if (s.startsWith(t)) w *= 1.2;
3233
+ if (w > best) best = w;
3234
+ }
3235
+ if (!best) { ok = false; break; }
3236
+ total += best;
3237
+ }
3238
+ if (ok) scored.push([total, item]);
3239
+ }
3240
+ scored.sort((a, b) => b[0] - a[0]);
3241
+ const ranked = scored.map(e => e[1]);
3242
+ attachArrayMethods(ranked, dataSourceName, reloadDataSource);
3243
+ return ranked;
3244
+ }
3245
+
3224
3246
  const attrs = attributes.length > 0 ? attributes : Object.keys(array[0] || {});
3225
3247
 
3226
3248
  const filtered = array.filter(item => {
@@ -8423,6 +8445,19 @@ function registerXMagicMethod(loadDataSource) {
8423
8445
  return undefined;
8424
8446
  }
8425
8447
 
8448
+ // $x.$register(name, data) — install/replace a client-side source at
8449
+ // runtime (array or object). Reactive like any manifest.json source.
8450
+ if (prop === '$register') {
8451
+ return (name, data) => {
8452
+ if (!name || typeof name !== 'string') return undefined;
8453
+ const stamped = Array.isArray(data)
8454
+ ? data.map(d => (d && typeof d === 'object' && !('contentType' in d)) ? { contentType: name, ...d } : d)
8455
+ : data;
8456
+ window.ManifestDataStore?.updateStore?.(name, stamped, { loading: false, error: null, ready: true, allowDuringInit: true });
8457
+ return true;
8458
+ };
8459
+ }
8460
+
8426
8461
  // Resolve+cache from raw data BEFORE reading Alpine.store('data'):
8427
8462
  // the store read registers a reactive dep that can re-run this
8428
8463
  // effect and re-enter get() → stack overflow. Caching first makes
@@ -8,7 +8,7 @@
8
8
  min-width: 17rem;
9
9
  background: var(--color-popover-surface, oklch(98.5% 0 0));
10
10
  color: var(--color-content-stark, oklch(20.5% 0 0));
11
- border: 1px solid var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent));
11
+ border: 1px solid var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
12
12
  border-radius: var(--radius, 0.5rem);
13
13
 
14
14
  /* Shell */
@@ -43,7 +43,7 @@
43
43
  transition: var(--transition, all .05s ease-in-out);
44
44
 
45
45
  &:hover {
46
- background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent));
46
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
47
47
  color: var(--color-content-stark, oklch(20.5% 0 0))
48
48
  }
49
49
  }
@@ -135,7 +135,7 @@
135
135
  transition: var(--transition, all .05s ease-in-out);
136
136
 
137
137
  &:hover:not(:disabled):not([aria-selected=true]) {
138
- background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
138
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
139
139
  }
140
140
 
141
141
  &:focus-visible {
@@ -151,7 +151,7 @@
151
151
 
152
152
  /* Today marker */
153
153
  &[aria-current=date]:not([aria-selected=true]) {
154
- border-color: var(--color-line, color-mix(oklch(20.5% 0 0) 18%, transparent));
154
+ border-color: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 18%, transparent));
155
155
  font-weight: 600
156
156
  }
157
157
 
@@ -212,11 +212,11 @@
212
212
  transition: var(--transition, all .05s ease-in-out);
213
213
 
214
214
  &:hover:not(:disabled) {
215
- background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
215
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
216
216
  }
217
217
 
218
218
  &[aria-current=true] {
219
- border-color: var(--color-line, color-mix(oklch(20.5% 0 0) 18%, transparent));
219
+ border-color: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 18%, transparent));
220
220
  font-weight: 600
221
221
  }
222
222
 
@@ -251,7 +251,7 @@
251
251
  padding-bottom: 0.5rem;
252
252
  padding-inline-start: 0.5rem;
253
253
  border: none;
254
- border-top: 1px solid var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent));
254
+ border-top: 1px solid var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
255
255
 
256
256
  /* "Time" label */
257
257
  &>label {
@@ -307,7 +307,7 @@
307
307
 
308
308
  &:hover,
309
309
  &:focus {
310
- background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
310
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
311
311
  }
312
312
  }
313
313
 
@@ -334,7 +334,7 @@
334
334
  transition: var(--transition, all .05s ease-in-out);
335
335
 
336
336
  &:hover {
337
- background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
337
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
338
338
  }
339
339
  }
340
340
 
@@ -380,7 +380,7 @@
380
380
  width: 100%;
381
381
  contain: inline-size;
382
382
  padding: 0.5rem 0;
383
- border-top: 1px solid var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent));
383
+ border-top: 1px solid var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
384
384
 
385
385
  /* Today | Clear buttons */
386
386
  &>button {
@@ -417,7 +417,7 @@
417
417
  padding: 0.25rem 0.625rem;
418
418
  font-size: 0.75rem;
419
419
  white-space: nowrap;
420
- background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent));
420
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
421
421
  color: var(--color-content-stark, oklch(20.5% 0 0));
422
422
  border: none;
423
423
  border-radius: calc(var(--radius, 0.5rem) * 2);
@@ -425,7 +425,7 @@
425
425
  transition: var(--transition, all .05s ease-in-out);
426
426
 
427
427
  &:hover {
428
- background: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 16%, transparent))
428
+ background: var(--color-field-surface-hover, color-mix(in oklch, oklch(20.5% 0 0) 16%, transparent))
429
429
  }
430
430
  }
431
431
  }
@@ -480,7 +480,7 @@
480
480
  transition: var(--transition, all .05s ease-in-out);
481
481
 
482
482
  &:hover {
483
- background: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
483
+ background: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
484
484
  }
485
485
 
486
486
  &[aria-selected=true] {
@@ -22,7 +22,7 @@
22
22
  flex: 1;
23
23
  width: auto;
24
24
  height: 1px;
25
- background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
25
+ background-color: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
26
26
  }
27
27
 
28
28
  /* Space between lines and content */
@@ -71,12 +71,12 @@
71
71
  &:hover {
72
72
  color: var(--color-field-inverse, oklch(43.9% 0 0));
73
73
  text-decoration: inherit;
74
- background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
74
+ background-color: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
75
75
  }
76
76
 
77
77
  &:active {
78
78
  color: var(--color-field-inverse, oklch(43.9% 0 0));
79
- background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
79
+ background-color: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
80
80
  }
81
81
 
82
82
  & span,
@@ -103,7 +103,7 @@
103
103
  margin-inline-start: calc(0.25rem * -1);
104
104
  margin-top: 0.25rem;
105
105
  margin-bottom: 0.25rem;
106
- background-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent))
106
+ background-color: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
107
107
  }
108
108
 
109
109
  /* Labels (not inner field-surface wrappers) */
@@ -153,7 +153,7 @@
153
153
 
154
154
  /* Dark theme */
155
155
  :where(.dark menu[popover]) :where(li, a, button, label:not(:has(input:not([type=checkbox], [type=radio], [type=hidden]), select, textarea, .combobox, .label, label))):hover {
156
- background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
156
+ background-color: var(--color-field-surface-hover, color-mix(in oklch, oklch(20.5% 0 0) 15%, transparent))
157
157
  }
158
158
 
159
159
  /* Nested menu alignment (not combobox listboxes, which anchor to their field) */
@@ -3,8 +3,12 @@
3
3
  // Most recent input modality, so we only move focus into a menu for keyboard opens.
4
4
  // Capture-phase so no handler can stop it first.
5
5
  let lastInputModality = 'mouse';
6
+ // Also the last pressed node: the router claims internal link clicks with
7
+ // stopImmediatePropagation, so pointerdown is our only view of which row a
8
+ // navigating item came from.
9
+ let lastPointerTarget = null;
6
10
  document.addEventListener('keydown', () => { lastInputModality = 'keyboard'; }, true);
7
- document.addEventListener('pointerdown', () => { lastInputModality = 'mouse'; }, true);
11
+ document.addEventListener('pointerdown', (e) => { lastInputModality = 'mouse'; lastPointerTarget = e.target; }, true);
8
12
 
9
13
  function initializeDropdownPlugin() {
10
14
  // Ensure an Alpine context exists. Keep the scope empty — seeding properties
@@ -542,4 +546,18 @@ document.addEventListener('click', (event) => {
542
546
  }
543
547
  });
544
548
  }
545
- });
549
+ });
550
+
551
+ // Auto-close for rows that navigate. The router claims internal link clicks with
552
+ // stopImmediatePropagation, so the menu's own click handler never runs — close on
553
+ // the route change it dispatches instead. Hash links aren't intercepted and still
554
+ // close via the normal path.
555
+ window.addEventListener('manifest:route-change', () => {
556
+ const pressed = lastPointerTarget;
557
+ document.querySelectorAll('[popover]:popover-open').forEach((menu) => {
558
+ if (!menu.__mnfstCloseBound) return;
559
+ const row = pressed && menu.contains(pressed) ? pressed : null;
560
+ if (row && row.closest('[keep-open]')) return;
561
+ if (menu.__mnfstAutoClose || (row && row.closest('[close]'))) menu.hidePopover();
562
+ });
563
+ });
@@ -148,7 +148,7 @@
148
148
  :where(fieldset:has(legend)):not(.unstyle) {
149
149
  padding: 1ch 1.5ch 1.5ch 1.5ch;
150
150
  border-radius: var(--radius, 0.5rem);
151
- border-color: var(--color-line, color-mix(oklch(20.5% 0 0) 10%, transparent));
151
+ border-color: var(--color-line, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
152
152
  border-style: solid;
153
153
  border-width: 1px;
154
154
 
@@ -13,7 +13,7 @@
13
13
  width: 100%;
14
14
  max-width: 100%;
15
15
  color: var(--color-field-inverse, oklch(43.9% 0 0));
16
- background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent));
16
+ background-color: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent));
17
17
  border-width: 0;
18
18
  border-style: solid;
19
19
  border-color: transparent;
@@ -23,15 +23,15 @@
23
23
  appearance: none;
24
24
 
25
25
  &:hover {
26
- background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
26
+ background-color: var(--color-field-surface-hover, color-mix(in oklch, oklch(20.5% 0 0) 15%, transparent))
27
27
  }
28
28
 
29
29
  &:active {
30
- background-color: var(--color-field-surface-hover, color-mix(oklch(20.5% 0 0) 15%, transparent))
30
+ background-color: var(--color-field-surface-hover, color-mix(in oklch, oklch(20.5% 0 0) 15%, transparent))
31
31
  }
32
32
 
33
33
  &:focus-visible {
34
- background-color: var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent))
34
+ background-color: var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent))
35
35
  }
36
36
 
37
37
  &::placeholder {
@@ -39,7 +39,7 @@
39
39
  }
40
40
 
41
41
  &::selection {
42
- background-color: color-mix(in oklch, var(--color-field-surface, color-mix(oklch(20.5% 0 0) 10%, transparent)) 80%, var(--color-field-inverse, oklch(43.9% 0 0)))
42
+ background-color: color-mix(in oklch, var(--color-field-surface, color-mix(in oklch, oklch(20.5% 0 0) 10%, transparent)) 80%, var(--color-field-inverse, oklch(43.9% 0 0)))
43
43
  }
44
44
 
45
45
  &[type=file] {
@@ -7,11 +7,11 @@
7
7
  "manifest.code.js": "sha384-G3MNfvQRWWY+gm4SGGrSsgQbDeil9MP+ON2DTk6Rcq9Nsex8woHMDt5EJHrFH0Nj",
8
8
  "manifest.color.js": "sha384-6Rv3LxyTcZNjrhtayQfqRdCx0uSZ4BiEbgEI98I62eTvp8Aw7LBIoNJ0Je1oktwL",
9
9
  "manifest.colorpicker.js": "sha384-N5ezEwgKrDodV4YdURxhdyZKmg1n/QhqvmAC4SEG03mw1pEAZAA8Gphp+NLAOxts",
10
- "manifest.combobox.js": "sha384-CqMe9F/Aw0fAwNCevlWOTOX+jl2yVz0zejztF97wvEu9uXH3zg2v87fceBlTXCq0",
11
- "manifest.components.js": "sha384-eZDib+RFdwd9mOUdi6o53eNS0LBlEEkZPDegxtkAcE+GrGIFxsuB4O0ymfixGNfM",
12
- "manifest.data.js": "sha384-3lLvGnqcQr4Ium80MdaQTr8RytjVbs5rnxd0q41BF3ppcoQccMFAcvgFSyZP/0sl",
10
+ "manifest.combobox.js": "sha384-rCSqpwzVLwr6alHPmAgW5sEVJylfK3XReyG475O1N9dkyMy/XA8hBCYILc+4qblU",
11
+ "manifest.components.js": "sha384-N9RY5uBUIULem7WT5/TRGxSXsrYr7gTZeaaOoS/+xYmvgdXmqpS4rJ+tqbcX2i++",
12
+ "manifest.data.js": "sha384-v5R+3pBJxHCQCmIzn2QLK0z9htnCYk8uKeWhl6rn+AAekA1XjQiLgpq5wOTKJ205",
13
13
  "manifest.datepicker.js": "sha384-52BBZdlLN+EATiUorwTEQOWGJZiSNu0voy9q+WQxxWpUeYsGGkuS0QPPXGx2akxG",
14
- "manifest.dropdowns.js": "sha384-5NccbUMdqSi/T4+Z/rUBRZl/UVtW4pqvXxbNqKgyiR3LtyYsb0JQ12ZkjK0uq1p9",
14
+ "manifest.dropdowns.js": "sha384-NfZU+U1oEEBnh1dEFANyoUrpGm3SJ/np/b6lczh+wTqJLjNpXd+LDP54db3b64cG",
15
15
  "manifest.export.js": "sha384-NkqOydcrdQ6qFEcpBKc80JCbeSCuybASrTUGajuTMBrHMzt1OIe8b3ndNn9VfqSh",
16
16
  "manifest.icons.js": "sha384-kKOINpX70YzNO3FvkV6umrmO3f0KIu0DNbwB+ej55a7y/lhU7v/myQtXL0bwCTbt",
17
17
  "manifest.localization.js": "sha384-MUetuW8PllIM+9JDMw/tx+qGem8t+ZUN5WZpycsTDKtz+LaX4DJehsfh/YZt9J7L",