You've already forked obsidian-visualiser
Try to add character editor inside the character sheet
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { RouteLocationAsRelativeTyped, RouteLocationRaw, RouteMapGeneric } from "vue-router";
|
||||
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node } from "#shared/dom";
|
||||
import { type NodeProperties, type Class, type NodeChildren, dom, mergeClasses, text, div, icon, type Node, span } from "#shared/dom";
|
||||
import { contextmenu, followermenu, minimizeBox, popper, teleport, tooltip, type FloatState } from "#shared/floating";
|
||||
import { clamp } from "#shared/general";
|
||||
import { clamp, deepEquals, shallowEquals } from "#shared/general";
|
||||
import { Tree } from "#shared/tree";
|
||||
import type { Placement } from "@floating-ui/dom";
|
||||
import { reactivity, type Reactive } from '#shared/reactive';
|
||||
@@ -10,7 +10,7 @@ export function link(children: NodeChildren, properties?: NodeProperties & { act
|
||||
{
|
||||
const router = useRouter();
|
||||
const nav = link ? router.resolve(link) : undefined;
|
||||
return dom('a', { ...properties, class: [properties?.class, properties?.active && router.currentRoute.value.fullPath === nav?.fullPath ? properties.active : undefined], attributes: { href: nav?.href, 'data-active': properties?.active ? mergeClasses(properties?.active) : undefined }, listeners: link ? {
|
||||
return dom('a', { ...properties, class: [properties?.class, properties?.active && router.currentRoute.value.fullPath === nav?.fullPath ? properties.active : undefined], attributes: { href: nav?.href, 'data-active': !!properties?.active }, listeners: link ? {
|
||||
click: function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
@@ -83,12 +83,10 @@ export function optionmenu(options: Array<{ title: string, click: () => void }>,
|
||||
}
|
||||
export type Option<T> = { text: string, render?: () => HTMLElement, value: T | Option<T>[] } | undefined;
|
||||
type StoredOption<T> = { item: Option<T>, dom: HTMLElement, container?: HTMLElement, children?: Array<StoredOption<T>> };
|
||||
export function select<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
|
||||
export function select<T extends NonNullable<any>>(options: Reactive<Array<{ text: string, value: T } | undefined>>, settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLDivElement & { disabled: boolean, value: T | undefined }
|
||||
{
|
||||
let context: { close: Function };
|
||||
let focused: number | undefined;
|
||||
|
||||
options = options.filter(e => !!e);
|
||||
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
||||
let focused: number | undefined, value: T | undefined, valueText: Text = text(''), disabled: boolean, change: ((v: T) => void) | undefined = undefined;
|
||||
|
||||
const focus = (i?: number) => {
|
||||
focused !== undefined && optionElements[focused]?.toggleAttribute('data-focused', false);
|
||||
@@ -96,51 +94,36 @@ export function select<T extends NonNullable<any>>(options: Array<{ text: string
|
||||
focused = i;
|
||||
}
|
||||
|
||||
let disabled = settings?.disabled ?? false;
|
||||
const textValue = text(options.find(e => Array.isArray(e) ? false : e?.value === settings?.defaultValue)?.text ?? '');
|
||||
const optionElements = options.map((e, i) => {
|
||||
if(e === undefined)
|
||||
return;
|
||||
|
||||
return dom('div', { listeners: { click: (_e) => {
|
||||
textValue.textContent = e.text;
|
||||
settings?.change && settings?.change(e.value);
|
||||
context && context.close && !_e.ctrlKey && context.close();
|
||||
}, mouseenter: (e) => focus(i) }, class: ['data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option] }, [ text(e.text) ]);
|
||||
});
|
||||
const select = dom('div', { listeners: { click: () => {
|
||||
if(disabled)
|
||||
return;
|
||||
|
||||
const handleKeys = (e: KeyboardEvent) => {
|
||||
switch(e.key.toLocaleLowerCase())
|
||||
{
|
||||
case 'arrowdown':
|
||||
focus(clamp((focused ?? -1) + 1, 0, options.length - 1));
|
||||
return;
|
||||
case 'arrowup':
|
||||
focus(clamp((focused ?? 1) - 1, 0, options.length - 1));
|
||||
return;
|
||||
case 'pageup':
|
||||
focus(0);
|
||||
return;
|
||||
case 'pagedown':
|
||||
focus(optionElements.length - 1);
|
||||
return;
|
||||
case 'enter':
|
||||
focused && optionElements[focused]?.click();
|
||||
return;
|
||||
case 'escape':
|
||||
context?.close();
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
const handleKeys = (e: KeyboardEvent) => {
|
||||
switch(e.key.toLocaleLowerCase())
|
||||
{
|
||||
case 'arrowdown':
|
||||
focus(clamp((focused ?? -1) + 1, 0, _options.length - 1));
|
||||
return;
|
||||
case 'arrowup':
|
||||
focus(clamp((focused ?? 1) - 1, 0, _options.length - 1));
|
||||
return;
|
||||
case 'pageup':
|
||||
focus(0);
|
||||
return;
|
||||
case 'pagedown':
|
||||
focus(optionElements.length - 1);
|
||||
return;
|
||||
case 'enter':
|
||||
focused && optionElements[focused]?.click();
|
||||
return;
|
||||
case 'escape':
|
||||
context?.close();
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
window.addEventListener('keydown', handleKeys);
|
||||
}
|
||||
const select = dom('div', { listeners: { focus: () => {
|
||||
if(disabled) return;
|
||||
|
||||
const box = select.getBoundingClientRect();
|
||||
context = contextmenu(box.x, box.y + box.height, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { "min-width": `${box.width}px` }, blur: () => window.removeEventListener('keydown', handleKeys) });
|
||||
} }, class: ['mx-4 inline-flex items-center justify-between px-3 text-sm font-semibold leading-none h-8 gap-1 bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:border-light-25 dark: data-[disabled]:border-dark-25 data-[disabled]:bg-light-20 dark: data-[disabled]:bg-dark-20', settings?.class?.container] }, [ dom('span', {}, [ textValue ]), icon('radix-icons:caret-down') ]);
|
||||
window.addEventListener('keydown', handleKeys);
|
||||
context = followermenu(select, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { 'min-width': `${select.clientWidth}px` }, blur: () => window.removeEventListener('keydown', handleKeys) });
|
||||
} }, class: ['mx-4 inline-flex items-center justify-between px-3 text-sm font-semibold leading-none outline-none cursor-default h-8 gap-1 bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:border-light-25 dark: data-[disabled]:border-dark-25 data-[disabled]:bg-light-20 dark: data-[disabled]:bg-dark-20', settings?.class?.container], attributes: { tabindex: '0' } }, [ span('', valueText), icon('radix-icons:caret-down') ]) as HTMLDivElement & { value: T | undefined, disabled: boolean };
|
||||
|
||||
Object.defineProperty(select, 'disabled', {
|
||||
get: () => disabled,
|
||||
@@ -148,12 +131,131 @@ export function select<T extends NonNullable<any>>(options: Array<{ text: string
|
||||
disabled = !!v;
|
||||
select.toggleAttribute('data-disabled', disabled);
|
||||
},
|
||||
})
|
||||
});
|
||||
Object.defineProperty(select, 'value', {
|
||||
get: () => value,
|
||||
set: (v) => {
|
||||
if(v === value) return;
|
||||
if(v === undefined)
|
||||
{
|
||||
valueText.textContent = '';
|
||||
focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
const idx = _options.findIndex(e => e?.value === v);
|
||||
if(idx !== -1)
|
||||
{
|
||||
valueText.textContent = _options[idx]?.text ?? '';
|
||||
focus(idx);
|
||||
}
|
||||
else return select.value = undefined;
|
||||
}
|
||||
value = v;
|
||||
change && change(v);
|
||||
}
|
||||
});
|
||||
|
||||
reactivity(options, (o) => {
|
||||
_options = o.filter(e => !!e);
|
||||
if(!_options.find(e => e.value === value)) select.value = undefined;
|
||||
optionElements = _options.map((e, i) => dom('div', { listeners: { click: (_e) => { select.value = e.value ; context.close(); }, mouseenter: (e) => focus(i) }, class: ['data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option], text: e.text }));
|
||||
});
|
||||
|
||||
select.disabled = settings?.disabled ?? false;
|
||||
select.value = settings?.defaultValue;
|
||||
|
||||
change = settings?.change;
|
||||
return select;
|
||||
}
|
||||
export function multiselect<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLElement
|
||||
export function multiselect<T extends NonNullable<any>>(options: Array<{ text: string, value: T } | undefined>, settings?: { defaultValue?: T[], change?: (value: T[]) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean }): HTMLDivElement & { disabled: boolean, value: T[] | undefined }
|
||||
{
|
||||
let context: { close: Function };
|
||||
let context: { close: Function }, _options: Array<{ text: string, value: T }> = [], optionElements: HTMLElement[] = [];
|
||||
let focused: number | undefined, value: T[] | undefined, valueText: Text = text(''), disabled: boolean, change: ((v: T[]) => void) | undefined = undefined;
|
||||
|
||||
const focus = (i?: number) => {
|
||||
focused !== undefined && optionElements[focused]?.toggleAttribute('data-focused', false);
|
||||
i !== undefined && optionElements[i]?.toggleAttribute('data-focused', true) && optionElements[i]?.scrollIntoView({ behavior: 'instant', block: 'nearest' });
|
||||
focused = i;
|
||||
}
|
||||
|
||||
const handleKeys = (e: KeyboardEvent) => {
|
||||
switch(e.key.toLocaleLowerCase())
|
||||
{
|
||||
case 'arrowdown':
|
||||
focus(clamp((focused ?? -1) + 1, 0, _options.length - 1));
|
||||
return;
|
||||
case 'arrowup':
|
||||
focus(clamp((focused ?? 1) - 1, 0, _options.length - 1));
|
||||
return;
|
||||
case 'pageup':
|
||||
focus(0);
|
||||
return;
|
||||
case 'pagedown':
|
||||
focus(optionElements.length - 1);
|
||||
return;
|
||||
case 'enter':
|
||||
focused && optionElements[focused]?.click();
|
||||
return;
|
||||
case 'escape':
|
||||
context?.close();
|
||||
return;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
const select = dom('div', { listeners: { focus: () => {
|
||||
if(disabled) return;
|
||||
|
||||
window.addEventListener('keydown', handleKeys);
|
||||
context = followermenu(select, optionElements.filter(e => !!e).length > 0 ? optionElements : [ div('text-light-60 dark:text-dark-60 italic text-center px-2 py-1', [ text('Aucune option') ]) ], { placement: "bottom-start", class: ['flex flex-col max-h-[320px] overflow-auto', settings?.class?.popup], style: { 'min-width': `${select.clientWidth}px` }, blur: () => window.removeEventListener('keydown', handleKeys) });
|
||||
} }, class: ['mx-4 inline-flex items-center justify-between px-3 text-sm font-semibold leading-none outline-none cursor-default h-8 gap-1 bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 hover:border-light-50 dark:hover:border-dark-50 data-[disabled]:border-light-25 dark: data-[disabled]:border-dark-25 data-[disabled]:bg-light-20 dark: data-[disabled]:bg-dark-20', settings?.class?.container], attributes: { tabindex: '0' } }, [ span('', valueText), icon('radix-icons:caret-down') ]) as HTMLDivElement & { value: T[] | undefined, disabled: boolean };
|
||||
|
||||
Object.defineProperty(select, 'disabled', {
|
||||
get: () => disabled,
|
||||
set: (v) => {
|
||||
disabled = !!v;
|
||||
select.toggleAttribute('data-disabled', disabled);
|
||||
},
|
||||
});
|
||||
Object.defineProperty(select, 'value', {
|
||||
get: () => value,
|
||||
set: (v) => {
|
||||
if(Array.isArray(v)) v = v.filter(e => _options.find(f => f.value === e));
|
||||
if(shallowEquals(v, value)) return;
|
||||
|
||||
if(v === undefined || (Array.isArray(v) && v.length === 0))
|
||||
{
|
||||
valueText.textContent = '';
|
||||
focus();
|
||||
}
|
||||
else if(Array.isArray(v)) valueText.textContent = `${_options.find(e => e.value === v[0])?.text ?? ''}${v.length > 1 ? ' +' + (v.length - 1) : ''}`;
|
||||
else throw new Error('Invalid value type');
|
||||
|
||||
value = v;
|
||||
change && change(v);
|
||||
}
|
||||
});
|
||||
|
||||
reactivity(options, (o) => {
|
||||
_options = o.filter(e => !!e);
|
||||
if(!_options.find(e => e.value === value)) select.value = undefined;
|
||||
optionElements = _options.map((e, i) => dom('div', { listeners: { click: function(_e) {
|
||||
const v = [];
|
||||
if(select.value) v.push(...select.value);
|
||||
const idx = select.value?.indexOf(e.value) ?? -1;
|
||||
idx === -1 ? v.push(e.value) : v.splice(idx, 1);
|
||||
this.toggleAttribute('data-selected', idx === -1);
|
||||
select.value = v;
|
||||
_e.ctrlKey || context.close();
|
||||
}, mouseenter: (e) => focus(i) }, class: ['group flex flex-row justify-between items-center data-[focused]:bg-light-30 dark:data-[focused]:bg-dark-30 text-light-70 dark:text-dark-70 data-[focused]:text-light-100 dark:data-[focused]:text-dark-100 py-1 px-2 cursor-pointer', settings?.class?.option], attributes: { 'data-selected': settings?.defaultValue?.includes(e.value) ?? false } }, [ text(e.text), icon('radix-icons:check', { class: 'hidden group-data-[selected]:block' }) ]));
|
||||
});
|
||||
|
||||
select.disabled = settings?.disabled ?? false;
|
||||
select.value = settings?.defaultValue;
|
||||
|
||||
change = settings?.change;
|
||||
return select;
|
||||
/* let context: { close: Function };
|
||||
let focused: number | undefined;
|
||||
let selection: T[] = settings?.defaultValue ?? [];
|
||||
|
||||
@@ -221,7 +323,7 @@ export function multiselect<T extends NonNullable<any>>(options: Array<{ text: s
|
||||
select.toggleAttribute('data-disabled', disabled);
|
||||
},
|
||||
})
|
||||
return select;
|
||||
return select; */
|
||||
}
|
||||
export function combobox<T extends NonNullable<any>>(options: Option<T>[], settings?: { defaultValue?: T, change?: (value: T) => void, class?: { container?: Class, popup?: Class, option?: Class }, disabled?: boolean, fill?: 'contain' | 'cover' })
|
||||
{
|
||||
@@ -483,7 +585,7 @@ export function table(content: TableRow[], headers: TableRow, properties?: { cla
|
||||
const render = (item: (() => HTMLElement) | HTMLElement | string) => typeof item === 'string' ? text(item) : typeof item === 'function' ? item() : item;
|
||||
return dom('table', { class: ['', properties?.class?.table] }, [ dom('thead', { class: ['', properties?.class?.header] }, [ dom('tr', { class: '' }, Object.values(headers).map(e => dom('th', {}, [ render(e) ]))) ]), dom('tbody', { class: ['', properties?.class?.body] }, content.map(e => dom('tr', { class: ['', properties?.class?.row] }, Object.keys(headers).map(f => e.hasOwnProperty(f) ? dom('td', { class: ['', properties?.class?.cell] }, [ render(e[f]!) ]) : undefined)))) ]);
|
||||
}
|
||||
export function toggle(settings?: { defaultValue?: boolean, change?: (value: boolean) => void, disabled?: boolean, class?: { container?: Class } })
|
||||
export function toggle(settings?: { defaultValue?: boolean, change?: (value: boolean) => void, disabled?: Reactive<boolean>, class?: { container?: Class } })
|
||||
{
|
||||
let state = settings?.defaultValue ?? false;
|
||||
const element = dom("div", { class: [`group mx-3 w-12 h-6 select-none transition-all border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 outline-none
|
||||
@@ -501,7 +603,7 @@ export function toggle(settings?: { defaultValue?: boolean, change?: (value: boo
|
||||
}, [ div('block w-[18px] h-[18px] translate-x-[2px] will-change-transform transition-transform bg-light-50 dark:bg-dark-50 group-data-[state=checked]:translate-x-[26px] group-data-[disabled]:bg-light-30 dark:group-data-[disabled]:bg-dark-30 group-data-[disabled]:border-light-30 dark:group-data-[disabled]:border-dark-30') ]);
|
||||
return element;
|
||||
}
|
||||
export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HTMLElement, value: boolean) => void, disabled?: boolean, class?: { container?: Class, icon?: Class } })
|
||||
export function checkbox(settings?: { defaultValue?: boolean, change?: (this: HTMLElement, value: boolean) => void, disabled?: Reactive<boolean>, class?: { container?: Class, icon?: Class } })
|
||||
{
|
||||
let state = settings?.defaultValue ?? false;
|
||||
const element = dom("div", { class: [`group w-6 h-6 box-content flex items-center justify-center border border-light-50 dark:border-dark-50 bg-light-20 dark:bg-dark-20
|
||||
|
||||
Reference in New Issue
Block a user