1.0.7
This commit is contained in:
1 parent
bd7b832394
commit
7643807717
86 files changed
+3644
-1385
No files matched your search
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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]">
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in new issue
Block a user