|
|
|
|
@ -1,98 +1,103 @@
|
|
|
|
|
<script>
|
|
|
|
|
import { get_repl_context } from '$lib/context.js';
|
|
|
|
|
import { tick } from 'svelte';
|
|
|
|
|
import { tick, untrack } from 'svelte';
|
|
|
|
|
|
|
|
|
|
export let key = '';
|
|
|
|
|
/** @type {import('svelte/types/compiler/interfaces').Ast} */
|
|
|
|
|
export let value;
|
|
|
|
|
export let collapsed = true;
|
|
|
|
|
/** @type {import('svelte/types/compiler/interfaces').Ast[]} */
|
|
|
|
|
export let path_nodes = [];
|
|
|
|
|
export let autoscroll = true;
|
|
|
|
|
/** @type {{ key?: string; value: unknown; collapsed?: boolean; path_nodes?: unknown[]; autoscroll?: boolean }} */
|
|
|
|
|
let { key = '', value, collapsed = true, path_nodes = [], autoscroll = true } = $props();
|
|
|
|
|
|
|
|
|
|
const { module_editor, toggleable } = get_repl_context();
|
|
|
|
|
|
|
|
|
|
/** @type {HTMLLIElement} */
|
|
|
|
|
let list_item_el;
|
|
|
|
|
|
|
|
|
|
$: is_root = path_nodes[0] === value;
|
|
|
|
|
$: is_leaf = path_nodes[path_nodes.length - 1] === value;
|
|
|
|
|
$: is_ast_array = Array.isArray(value);
|
|
|
|
|
$: is_collapsable = value && typeof value === 'object';
|
|
|
|
|
$: is_markable =
|
|
|
|
|
is_collapsable &&
|
|
|
|
|
'start' in value &&
|
|
|
|
|
'end' in value &&
|
|
|
|
|
typeof value.start === 'number' &&
|
|
|
|
|
typeof value.end === 'number';
|
|
|
|
|
$: key_text = key ? `${key}:` : '';
|
|
|
|
|
|
|
|
|
|
let preview_text = '';
|
|
|
|
|
$: {
|
|
|
|
|
if (!is_collapsable || !collapsed) break $;
|
|
|
|
|
|
|
|
|
|
const is_root = $derived(path_nodes[0] === value);
|
|
|
|
|
const is_leaf = $derived(path_nodes[path_nodes.length - 1] === value);
|
|
|
|
|
const is_ast_array = $derived(Array.isArray(value));
|
|
|
|
|
/**
|
|
|
|
|
* @param {unknown} value
|
|
|
|
|
* @returns {value is {}}
|
|
|
|
|
*/
|
|
|
|
|
function is_collapsable(value) {
|
|
|
|
|
return value !== null && typeof value === 'object';
|
|
|
|
|
}
|
|
|
|
|
const is_markable = $derived(
|
|
|
|
|
is_collapsable(value) &&
|
|
|
|
|
'start' in value &&
|
|
|
|
|
'end' in value &&
|
|
|
|
|
typeof value.start === 'number' &&
|
|
|
|
|
typeof value.end === 'number'
|
|
|
|
|
);
|
|
|
|
|
const key_text = key ? `${key}:` : '';
|
|
|
|
|
|
|
|
|
|
let preview_text = $state('');
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (!is_collapsable(value) || !collapsed) return;
|
|
|
|
|
if (is_ast_array) {
|
|
|
|
|
if (!('length' in value)) break $;
|
|
|
|
|
|
|
|
|
|
if (!('length' in value)) return;
|
|
|
|
|
preview_text = `[ ${value.length} element${value.length === 1 ? '' : 's'} ]`;
|
|
|
|
|
} else {
|
|
|
|
|
preview_text = `{ ${Object.keys(value).join(', ')} }`;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$: collapsed = !path_nodes.includes(value);
|
|
|
|
|
$effect(() => {
|
|
|
|
|
const collapse = !path_nodes.includes(value);
|
|
|
|
|
untrack(() => (collapsed = collapse));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$effect(() => {
|
|
|
|
|
if (!autoscroll || !is_leaf || $toggleable) return;
|
|
|
|
|
|
|
|
|
|
$: if (autoscroll && is_leaf && !$toggleable) {
|
|
|
|
|
// wait for all nodes to render before scroll
|
|
|
|
|
tick().then(() => {
|
|
|
|
|
if (list_item_el) {
|
|
|
|
|
list_item_el.scrollIntoView();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** @param {MouseEvent | FocusEvent} e */
|
|
|
|
|
function handle_mark_text(e) {
|
|
|
|
|
if (is_markable) {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
'start' in value &&
|
|
|
|
|
'end' in value &&
|
|
|
|
|
typeof value.start === 'number' &&
|
|
|
|
|
typeof value.end === 'number'
|
|
|
|
|
) {
|
|
|
|
|
$module_editor?.markText({ from: value.start ?? 0, to: value.end ?? 0 });
|
|
|
|
|
}
|
|
|
|
|
if (!is_markable) return;
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
is_collapsable(value) &&
|
|
|
|
|
'start' in value &&
|
|
|
|
|
'end' in value &&
|
|
|
|
|
typeof value.start === 'number' &&
|
|
|
|
|
typeof value.end === 'number'
|
|
|
|
|
) {
|
|
|
|
|
$module_editor?.markText({ from: value.start ?? 0, to: value.end ?? 0 });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @param {MouseEvent} e */
|
|
|
|
|
function handle_unmark_text(e) {
|
|
|
|
|
if (is_markable) {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
$module_editor?.unmarkText();
|
|
|
|
|
}
|
|
|
|
|
if (!is_markable) return;
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
$module_editor?.unmarkText();
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<li
|
|
|
|
|
bind:this={list_item_el}
|
|
|
|
|
class:marked={!is_root && is_leaf}
|
|
|
|
|
on:mouseover={handle_mark_text}
|
|
|
|
|
on:focus={handle_mark_text}
|
|
|
|
|
on:mouseleave={handle_unmark_text}
|
|
|
|
|
onmouseover={handle_mark_text}
|
|
|
|
|
onfocus={handle_mark_text}
|
|
|
|
|
onmouseleave={handle_unmark_text}
|
|
|
|
|
>
|
|
|
|
|
{#if !is_root && is_collapsable}
|
|
|
|
|
<button class="ast-toggle" class:open={!collapsed} on:click={() => (collapsed = !collapsed)}>
|
|
|
|
|
{#if !is_root && is_collapsable(value)}
|
|
|
|
|
<button class="ast-toggle" class:open={!collapsed} onclick={() => (collapsed = !collapsed)}>
|
|
|
|
|
{key_text}
|
|
|
|
|
</button>
|
|
|
|
|
{:else if key_text}
|
|
|
|
|
<span>{key_text}</span>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if is_collapsable}
|
|
|
|
|
{#if is_collapsable(value)}
|
|
|
|
|
{#if collapsed && !is_root}
|
|
|
|
|
<button class="preview" on:click={() => (collapsed = !collapsed)}>
|
|
|
|
|
<button class="preview" onclick={() => (collapsed = !collapsed)}>
|
|
|
|
|
{preview_text}
|
|
|
|
|
</button>
|
|
|
|
|
{:else}
|
|
|
|
|
|