This commit is contained in:
Jarek Krochmalski committed 2026-01-11 07:16:18 +01:00
1 parent bd7b832394
commit 7643807717
86 files changed
+3644 -1385

No files matched your search

+8 -1
View File
@@ -225,6 +225,9 @@
// Mutable ref for callback - allows updating without recreating editor
let onchangeRef: ((value: string) => void) | undefined = onchange;
// Flag to suppress onchange during programmatic value sync
let isSyncingExternalValue = false;
// Keep callback ref updated when prop changes
$effect(() => {
onchangeRef = onchange;
@@ -660,8 +663,9 @@
view.update(trs);
// Check if any transaction changed the document
// Skip onchange during programmatic value sync (only fire for user edits)
const lastChangingTr = trs.findLast(tr => tr.docChanged);
if (lastChangingTr && onchangeRef) {
if (lastChangingTr && onchangeRef && !isSyncingExternalValue) {
// Defer callback to next microtask to avoid blocking input handling
// This allows key repeat to work properly
const newContent = lastChangingTr.newDoc.toString();
@@ -801,9 +805,12 @@
// Only update if the external value differs from editor content
// This prevents feedback loops from editor changes
if (externalValue !== currentContent) {
// Suppress onchange during programmatic sync - only user edits should trigger it
isSyncingExternalValue = true;
view.dispatch({
changes: { from: 0, to: currentContent.length, insert: externalValue }
});
isSyncingExternalValue = false;
}
}
});
+1 -1
View File
@@ -104,7 +104,7 @@
<div class="space-y-3">
<!-- Variables List -->
<div class="space-y-3">
{#each variables as variable, index (index)}
{#each variables as variable, index (`${index}-${variable.key}`)}
{@const source = getSource(variable.key)}
{@const isVarRequired = isRequired(variable.key)}
{@const isVarOptional = isOptional(variable.key)}
+22 -25
View File
@@ -46,38 +46,35 @@
let confirmClearOpen = $state(false);
let contentAreaRef: HTMLDivElement;
let parseWarnings = $state<string[]>([]);
let hasMergedOnLoad = $state(false);
// Count of secrets (for display in hint)
const secretCount = $derived(variables.filter(v => v.isSecret && v.key.trim()).length);
/**
* Merge variables and rawContent on initial load.
* Called by parent after setting both variables and rawContent.
* This ensures both are in sync regardless of which view mode is active.
* Sync variables with rawContent after initial load.
* Pass the loaded data directly to avoid timing issues with bindable props.
* Merges: secrets from loadedVars (DB) + non-secrets from loadedRaw (file).
*/
export function mergeOnLoad() {
if (hasMergedOnLoad) return;
hasMergedOnLoad = true;
// If rawContent exists, parse it and merge with variables (which may have secrets from DB)
if (rawContent.trim()) {
const { vars: rawVars } = parseRawContent(rawContent);
const rawVarsByKey = new Map(rawVars.map(v => [v.key, v]));
// Secrets come from variables (DB), non-secrets come from rawContent (file)
// But if a var exists in variables but not in rawContent, keep it (could be new)
const secrets = variables.filter(v => v.isSecret);
const nonSecretsFromRaw = rawVars;
// Also keep non-secrets from variables that aren't in raw (new vars added before first save)
const rawKeys = new Set(rawVars.map(v => v.key));
const newNonSecrets = variables.filter(v => !v.isSecret && v.key.trim() && !rawKeys.has(v.key));
variables = [...nonSecretsFromRaw, ...newNonSecrets, ...secrets];
export function syncAfterLoad(loadedVars: EnvVar[], loadedRaw: string) {
if (!loadedRaw.trim()) {
// No raw content - just use the loaded variables as-is
variables = loadedVars;
rawContent = '';
return;
}
// If no rawContent, variables is already correct (from DB), just need to generate raw
// for when user switches to text view (done in handleViewModeChange)
const { vars: rawVars } = parseRawContent(loadedRaw);
// Secrets come from loadedVars (DB), non-secrets come from loadedRaw (file)
const secrets = loadedVars.filter(v => v.isSecret);
// Also keep non-secrets from loadedVars that aren't in raw (new vars added before first save)
const rawKeys = new Set(rawVars.map(v => v.key));
const newNonSecrets = loadedVars.filter(v => !v.isSecret && v.key.trim() && !rawKeys.has(v.key));
// Set both at once to avoid any intermediate states
variables = [...rawVars, ...newNonSecrets, ...secrets];
rawContent = loadedRaw;
}
/**
+1 -1
View File
@@ -111,7 +111,7 @@
</Button>
{/snippet}
</Popover.Trigger>
<Popover.Content class="w-[350px] p-0" align="start">
<Popover.Content class="w-[350px] p-0 z-[200]" align="start">
<Command.Root shouldFilter={false}>
<Command.Input bind:value={searchQuery} placeholder="Search timezone..." />
<Command.List class="max-h-[300px]">
+18 -20
View File
@@ -496,34 +496,32 @@
});
// Row state cache to prevent creating new objects on every scroll
// Use $derived to track dependencies synchronously (unlike $effect which is async)
let rowStateCache = new WeakMap<object, DataGridRowState>();
let rowStateCacheDataRef: T[] | null = null;
let rowStateCacheExpandedRef: Set<unknown> | null = null;
let rowStateCacheSelectedRef: Set<unknown> | null = null;
// Clear row state cache when data or selection/expansion state changes
$effect(() => {
if (data !== rowStateCacheDataRef ||
expandedKeys !== rowStateCacheExpandedRef ||
selectedKeys !== rowStateCacheSelectedRef) {
rowStateCache = new WeakMap();
rowStateCacheDataRef = data;
rowStateCacheExpandedRef = expandedKeys;
rowStateCacheSelectedRef = selectedKeys;
}
});
// Track cache invalidation keys - when these change, cache is stale
let cachedSelectedKeysRef: Set<unknown> | null = null;
let cachedExpandedKeysRef: Set<unknown> | null = null;
let cachedHighlightedKeyRef: unknown = undefined;
// Helper to get row state (memoized via WeakMap)
// Cache is invalidated synchronously when selection/expansion changes
function getRowState(item: T, index: number): DataGridRowState {
const actualIndex = virtualScroll ? startIndex + index : index;
// Check if cache needs to be cleared (synchronous check)
if (selectedKeys !== cachedSelectedKeysRef ||
expandedKeys !== cachedExpandedKeysRef ||
highlightedKey !== cachedHighlightedKeyRef) {
rowStateCache = new WeakMap();
cachedSelectedKeysRef = selectedKeys;
cachedExpandedKeysRef = expandedKeys;
cachedHighlightedKeyRef = highlightedKey;
}
// Try to get cached state
const cached = rowStateCache.get(item as object);
if (cached && cached.index === actualIndex) {
// Update mutable fields that may have changed
cached.isSelected = isSelected(item[keyField]);
cached.isHighlighted = highlightedKey === item[keyField];
cached.isExpanded = isExpanded(item[keyField]);
return cached;
}
@@ -761,7 +759,7 @@
e.stopPropagation();
toggleSelection(item[keyField]);
}}
class="flex items-center justify-center transition-colors cursor-pointer {rowState.isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-40 hover:!opacity-100'}"
class="flex items-center justify-center w-full h-full min-h-[24px] transition-colors cursor-pointer {rowState.isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-40 hover:!opacity-100'}"
>
{#if rowState.isSelected}
<CheckSquare class="w-3.5 h-3.5 text-muted-foreground" />
@@ -870,7 +868,7 @@
<button
type="button"
onclick={(e) => { e.stopPropagation(); toggleSelection(item[keyField]); }}
class="flex items-center justify-center transition-colors cursor-pointer {rowState.isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-40 hover:!opacity-100'}"
class="flex items-center justify-center w-full h-full min-h-[24px] transition-colors cursor-pointer {rowState.isSelected ? 'opacity-100' : 'opacity-0 group-hover:opacity-40 hover:!opacity-100'}"
>
{#if rowState.isSelected}
<CheckSquare class="w-3.5 h-3.5 text-muted-foreground" />
+1 -1
View File
@@ -37,7 +37,7 @@
<CurrentIcon class="h-4 w-4" />
</Button>
</Popover.Trigger>
<Popover.Content class="w-80 p-3" align="start">
<Popover.Content class="w-80 p-3 z-[200]" align="start">
<div class="space-y-3">
<Input
bind:value={searchQuery}
@@ -62,7 +62,7 @@
<span class="text-xs">{placeholder}</span>
{/if}
</Popover.Trigger>
<Popover.Content class="w-auto p-0" align="start">
<Popover.Content class="w-auto p-0 z-[200]" align="start">
<Calendar
type="single"
value={dateValue}
@@ -27,7 +27,7 @@
bind:ref
data-slot="dialog-content"
class={cn(
"bg-background fixed start-[50%] top-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg",
"bg-background fixed start-[50%] top-[50%] z-[150] grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg",
!className?.includes('max-w-') && "sm:max-w-lg",
className
)}
@@ -13,7 +13,7 @@
bind:ref
data-slot="dialog-overlay"
class={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[150] bg-black/50",
className
)}
{...restProps}
@@ -21,7 +21,7 @@
data-slot="dropdown-menu-content"
{sideOffset}
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-dropdown-menu-content-available-height) origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md outline-none",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-dropdown-menu-content-available-height) origin-(--bits-dropdown-menu-content-transform-origin) z-[200] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md outline-none",
className
)}
{...restProps}
@@ -13,7 +13,7 @@
bind:ref
data-slot="dropdown-menu-sub-content"
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-dropdown-menu-content-transform-origin) z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-dropdown-menu-content-transform-origin) z-[200] min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg",
className
)}
{...restProps}
@@ -21,7 +21,7 @@
{sideOffset}
{align}
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-popover-content-transform-origin) outline-hidden z-50 w-72 rounded-md border p-4 shadow-md",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-popover-content-transform-origin) outline-hidden z-[200] w-72 rounded-md border p-4 shadow-md",
className
)}
{...restProps}
@@ -24,7 +24,7 @@
{preventScroll}
data-slot="select-content"
class={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) relative z-50 min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) relative z-[200] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border shadow-md data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
{...restProps}
@@ -21,7 +21,7 @@
{sideOffset}
{side}
class={cn(
"bg-popover text-popover-foreground border shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-tooltip-content-transform-origin) z-[100] w-fit fixed text-balance rounded-md px-3 py-1.5 text-xs",
"bg-popover text-popover-foreground border shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 origin-(--bits-tooltip-content-transform-origin) z-[200] w-fit fixed text-balance rounded-md px-3 py-1.5 text-xs",
className
)}
{...restProps}