@joewinke/jatui 0.22.0 → 0.23.0
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/CLAUDE.md +41 -1
- package/dist/actions/reveal.d.ts +37 -0
- package/dist/actions/reveal.js +42 -0
- package/dist/components/AudioRecorder.svelte +749 -0
- package/dist/components/AudioRecorder.svelte.d.ts +36 -0
- package/dist/components/IconSelector.svelte +417 -0
- package/dist/components/IconSelector.svelte.d.ts +55 -0
- package/dist/components/InfiniteGrid.svelte +305 -0
- package/dist/components/InfiniteGrid.svelte.d.ts +71 -0
- package/dist/components/InfiniteSearchGrid.svelte +131 -0
- package/dist/components/InfiniteSearchGrid.svelte.d.ts +67 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte +40 -3
- package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +31 -16
- package/dist/components/MediaCaptureIcon.svelte +23 -0
- package/dist/components/MediaCaptureIcon.svelte.d.ts +1 -1
- package/dist/components/ThumbnailGrid.svelte +137 -0
- package/dist/components/ThumbnailGrid.svelte.d.ts +42 -0
- package/dist/components/batch/BatchJobTable.svelte +549 -0
- package/dist/components/batch/BatchJobTable.svelte.d.ts +114 -0
- package/dist/components/batch/QuotaMeter.svelte +78 -0
- package/dist/components/batch/QuotaMeter.svelte.d.ts +16 -0
- package/dist/components/batch/batchQueue.d.ts +56 -0
- package/dist/components/batch/batchQueue.js +81 -0
- package/dist/components/batch/elapsedEstimate.d.ts +24 -0
- package/dist/components/batch/elapsedEstimate.js +25 -0
- package/dist/components/batch/pollOperation.d.ts +45 -0
- package/dist/components/batch/pollOperation.js +80 -0
- package/dist/components/batch/useElapsedEstimate.d.ts +10 -0
- package/dist/components/batch/useElapsedEstimate.js +38 -0
- package/dist/components/infiniteGridLoader.d.ts +70 -0
- package/dist/components/infiniteGridLoader.js +141 -0
- package/dist/components/table/columnResize.d.ts +38 -0
- package/dist/components/table/columnResize.js +117 -0
- package/dist/components/table/index.d.ts +2 -0
- package/dist/components/table/index.js +1 -0
- package/dist/components/variant/VariantMatrixPicker.svelte +181 -0
- package/dist/components/variant/VariantMatrixPicker.svelte.d.ts +37 -0
- package/dist/components/variant/variantCombinations.d.ts +75 -0
- package/dist/components/variant/variantCombinations.js +100 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +25 -0
- package/dist/styles/motion.css +100 -0
- package/dist/utils/keyboardShortcutRegistry.d.ts +152 -0
- package/dist/utils/keyboardShortcutRegistry.js +365 -0
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface ColumnResizeOptions {
|
|
2
|
+
/** When true, the resize handle is not added and nothing happens. Default: false */
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
/** Minimum column width in pixels. Default: 40 */
|
|
5
|
+
minWidth?: number;
|
|
6
|
+
/** Called on mousedown — use this to show the guide line (e.g. set resizeGuideX state) */
|
|
7
|
+
onResizeStart?: (initialX: number) => void;
|
|
8
|
+
/** Called on every mousemove with the new width */
|
|
9
|
+
onResize: (width: number) => void;
|
|
10
|
+
/** Called on mouseup with the final width — use this to save settings and hide the guide */
|
|
11
|
+
onResizeEnd?: (width: number) => void;
|
|
12
|
+
/**
|
|
13
|
+
* Return the container element that holds the `.resize-guide` element.
|
|
14
|
+
* If provided, the guide line position is updated on each mousemove via direct DOM manipulation,
|
|
15
|
+
* avoiding reactive overhead.
|
|
16
|
+
*/
|
|
17
|
+
getGuideContainer?: () => HTMLElement | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Svelte action that attaches a column-resize handle to a `<th>` (or any element).
|
|
21
|
+
*
|
|
22
|
+
* Usage:
|
|
23
|
+
* ```svelte
|
|
24
|
+
* <th use:columnResize={{
|
|
25
|
+
* minWidth: col.minWidth,
|
|
26
|
+
* onResize: (w) => { columnWidths[col.id] = w; },
|
|
27
|
+
* onResizeEnd: (w) => { saveColumnSettings(); },
|
|
28
|
+
* getGuideContainer: () => tableContainerEl,
|
|
29
|
+
* }}>
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* The action appends a 4px-wide resize handle div at the right edge of the host element.
|
|
33
|
+
* Dragging it fires `onResize` continuously and `onResizeEnd` on mouse-up.
|
|
34
|
+
*/
|
|
35
|
+
export declare function columnResize(node: HTMLElement, options: ColumnResizeOptions): {
|
|
36
|
+
update(opts: ColumnResizeOptions): void;
|
|
37
|
+
destroy(): void;
|
|
38
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte action that attaches a column-resize handle to a `<th>` (or any element).
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```svelte
|
|
6
|
+
* <th use:columnResize={{
|
|
7
|
+
* minWidth: col.minWidth,
|
|
8
|
+
* onResize: (w) => { columnWidths[col.id] = w; },
|
|
9
|
+
* onResizeEnd: (w) => { saveColumnSettings(); },
|
|
10
|
+
* getGuideContainer: () => tableContainerEl,
|
|
11
|
+
* }}>
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* The action appends a 4px-wide resize handle div at the right edge of the host element.
|
|
15
|
+
* Dragging it fires `onResize` continuously and `onResizeEnd` on mouse-up.
|
|
16
|
+
*/
|
|
17
|
+
export function columnResize(node, options) {
|
|
18
|
+
let opts = options;
|
|
19
|
+
// If disabled, return a no-op handle
|
|
20
|
+
if (opts.disabled) {
|
|
21
|
+
return {
|
|
22
|
+
update(newOptions) { opts = newOptions; },
|
|
23
|
+
destroy() { },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// Build the resize handle element
|
|
27
|
+
const handle = document.createElement('div');
|
|
28
|
+
handle.className = 'col-resize-handle';
|
|
29
|
+
handle.style.cssText = [
|
|
30
|
+
'position: absolute',
|
|
31
|
+
'right: 0',
|
|
32
|
+
'top: 0',
|
|
33
|
+
'bottom: 0',
|
|
34
|
+
'width: 5px',
|
|
35
|
+
'cursor: col-resize',
|
|
36
|
+
'z-index: 2',
|
|
37
|
+
].join(';');
|
|
38
|
+
// Make the host element position-relative if it isn't already
|
|
39
|
+
const hostPosition = getComputedStyle(node).position;
|
|
40
|
+
if (hostPosition === 'static') {
|
|
41
|
+
node.style.position = 'relative';
|
|
42
|
+
}
|
|
43
|
+
node.appendChild(handle);
|
|
44
|
+
// Resize tracking state
|
|
45
|
+
let startX = 0;
|
|
46
|
+
let startWidth = 0;
|
|
47
|
+
let currentWidth = 0;
|
|
48
|
+
let resizing = false;
|
|
49
|
+
function updateGuide(clientX) {
|
|
50
|
+
const container = opts.getGuideContainer?.();
|
|
51
|
+
if (!container)
|
|
52
|
+
return;
|
|
53
|
+
const guide = container.querySelector('.resize-guide');
|
|
54
|
+
if (!guide)
|
|
55
|
+
return;
|
|
56
|
+
const rect = container.getBoundingClientRect();
|
|
57
|
+
guide.style.left = `${clientX - rect.left + container.scrollLeft}px`;
|
|
58
|
+
}
|
|
59
|
+
function onMouseMove(e) {
|
|
60
|
+
const minWidth = opts.minWidth ?? 40;
|
|
61
|
+
const diff = e.clientX - startX;
|
|
62
|
+
const newWidth = Math.max(minWidth, startWidth + diff);
|
|
63
|
+
currentWidth = newWidth;
|
|
64
|
+
opts.onResize(newWidth);
|
|
65
|
+
updateGuide(e.clientX);
|
|
66
|
+
}
|
|
67
|
+
function onMouseUp() {
|
|
68
|
+
document.body.style.cursor = '';
|
|
69
|
+
document.body.style.userSelect = '';
|
|
70
|
+
// Call onResizeEnd — the parent is responsible for clearing the guide line
|
|
71
|
+
// (typically by setting resizeGuideX = null in its onResizeEnd handler)
|
|
72
|
+
opts.onResizeEnd?.(currentWidth);
|
|
73
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
74
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
75
|
+
// Suppress the click event that fires after mouseup on the host <th>,
|
|
76
|
+
// which would otherwise trigger a column sort.
|
|
77
|
+
resizing = true;
|
|
78
|
+
requestAnimationFrame(() => { resizing = false; });
|
|
79
|
+
}
|
|
80
|
+
function onMouseDown(e) {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
startX = e.clientX;
|
|
84
|
+
startWidth = node.offsetWidth;
|
|
85
|
+
currentWidth = startWidth;
|
|
86
|
+
document.body.style.cursor = 'col-resize';
|
|
87
|
+
document.body.style.userSelect = 'none';
|
|
88
|
+
opts.onResizeStart?.(e.clientX);
|
|
89
|
+
updateGuide(e.clientX);
|
|
90
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
91
|
+
document.addEventListener('mouseup', onMouseUp);
|
|
92
|
+
}
|
|
93
|
+
// Suppress click events on the host element that fire right after a resize ends.
|
|
94
|
+
// Without this, mouseup after dragging the resize handle triggers the host's onclick (e.g. sort).
|
|
95
|
+
function onHostClick(e) {
|
|
96
|
+
if (resizing) {
|
|
97
|
+
e.stopImmediatePropagation();
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
handle.addEventListener('mousedown', onMouseDown);
|
|
102
|
+
node.addEventListener('click', onHostClick, true);
|
|
103
|
+
return {
|
|
104
|
+
update(newOptions) {
|
|
105
|
+
opts = newOptions;
|
|
106
|
+
},
|
|
107
|
+
destroy() {
|
|
108
|
+
handle.removeEventListener('mousedown', onMouseDown);
|
|
109
|
+
node.removeEventListener('click', onHostClick, true);
|
|
110
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
111
|
+
document.removeEventListener('mouseup', onMouseUp);
|
|
112
|
+
if (handle.parentNode === node) {
|
|
113
|
+
node.removeChild(handle);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { default as DataTable } from './DataTable.svelte';
|
|
2
2
|
export { flexRender } from './flexRender';
|
|
3
|
+
export { columnResize } from './columnResize';
|
|
4
|
+
export type { ColumnResizeOptions } from './columnResize';
|
|
3
5
|
export type { DataTableProps, SelectionEvent, ColumnDef, Row } from './types';
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* VariantMatrixPicker Component
|
|
4
|
+
*
|
|
5
|
+
* Category/option chip grid for building a combinatorial variant matrix
|
|
6
|
+
* (jatui-m4uz3.8, ported from vid-gen's FrameVariantGenerator selection UI,
|
|
7
|
+
* lines 72–197 / 500–620, with the hardcoded presets stripped).
|
|
8
|
+
*
|
|
9
|
+
* Every category renders as a tab with its options as toggleable chips.
|
|
10
|
+
* Each category carries an `additive`/`multiplicative` badge:
|
|
11
|
+
* - multiplicative: each selection multiplies the total combination count
|
|
12
|
+
* (cartesian product across multiplicative categories);
|
|
13
|
+
* - additive: selections stack flat onto every combination.
|
|
14
|
+
* A live combination count renders in the footer via `countCombinations`.
|
|
15
|
+
*
|
|
16
|
+
* The algebra lives in `./variantCombinations.ts` (pure, unit-tested);
|
|
17
|
+
* the host passes `categories` and reads/binds `selected`.
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
countCombinations,
|
|
21
|
+
variantCategoryMode,
|
|
22
|
+
type VariantCategory,
|
|
23
|
+
type VariantSelection,
|
|
24
|
+
type VariantSelectionMode
|
|
25
|
+
} from './variantCombinations';
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
/** Category/option matrix — caller-supplied, no presets inside the component. */
|
|
29
|
+
categories: VariantCategory[];
|
|
30
|
+
/** categoryId → selected option ids. Bind to drive the matrix externally. */
|
|
31
|
+
selected: VariantSelection;
|
|
32
|
+
/** Force every category to one mode (`null` respects each category's own). */
|
|
33
|
+
forceMode?: VariantSelectionMode | null;
|
|
34
|
+
/** Show the live combination count footer (default true). */
|
|
35
|
+
showCount?: boolean;
|
|
36
|
+
/** Optional label for the count footer (default "combinations"). */
|
|
37
|
+
countLabel?: string;
|
|
38
|
+
/** Fired after every selection change with the new selection. */
|
|
39
|
+
onChange?: (selected: VariantSelection) => void;
|
|
40
|
+
/** Additional CSS class for the container. */
|
|
41
|
+
class?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
categories,
|
|
46
|
+
selected = $bindable({}),
|
|
47
|
+
forceMode = null,
|
|
48
|
+
showCount = true,
|
|
49
|
+
countLabel = 'combinations',
|
|
50
|
+
onChange,
|
|
51
|
+
class: className = ''
|
|
52
|
+
}: Props = $props();
|
|
53
|
+
|
|
54
|
+
let activeTab = $state('');
|
|
55
|
+
|
|
56
|
+
// Follow the category list if it changes identity.
|
|
57
|
+
$effect(() => {
|
|
58
|
+
if (!categories.some((c) => c.id === activeTab)) {
|
|
59
|
+
activeTab = categories[0]?.id ?? '';
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const combinationCount = $derived(countCombinations(categories, selected, forceMode));
|
|
64
|
+
const selectedCount = (categoryId: string) => selected[categoryId]?.length ?? 0;
|
|
65
|
+
const activeCategory = $derived(categories.find((c) => c.id === activeTab));
|
|
66
|
+
|
|
67
|
+
function toggleOption(categoryId: string, optionId: string) {
|
|
68
|
+
const current = selected[categoryId] ?? [];
|
|
69
|
+
const next = current.includes(optionId)
|
|
70
|
+
? current.filter((id) => id !== optionId)
|
|
71
|
+
: [...current, optionId];
|
|
72
|
+
selected = { ...selected, [categoryId]: next };
|
|
73
|
+
onChange?.(selected);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function selectAll(categoryId: string) {
|
|
77
|
+
const category = categories.find((c) => c.id === categoryId);
|
|
78
|
+
if (!category) return;
|
|
79
|
+
selected = { ...selected, [categoryId]: category.options.map((o) => o.id) };
|
|
80
|
+
onChange?.(selected);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function clearCategory(categoryId: string) {
|
|
84
|
+
selected = { ...selected, [categoryId]: [] };
|
|
85
|
+
onChange?.(selected);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clearAll() {
|
|
89
|
+
const next: VariantSelection = {};
|
|
90
|
+
for (const c of categories) next[c.id] = [];
|
|
91
|
+
selected = next;
|
|
92
|
+
onChange?.(next);
|
|
93
|
+
}
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<div class="{className}">
|
|
97
|
+
<!-- Category tabs: icon, label, live selection count -->
|
|
98
|
+
<div role="tablist" aria-label="Variant categories" class="flex flex-wrap gap-1 mb-3">
|
|
99
|
+
{#each categories as category (category.id)}
|
|
100
|
+
{@const count = selectedCount(category.id)}
|
|
101
|
+
<button
|
|
102
|
+
role="tab"
|
|
103
|
+
aria-selected={activeTab === category.id}
|
|
104
|
+
class="btn btn-sm gap-2 {activeTab === category.id ? 'btn-primary' : 'btn-ghost'}"
|
|
105
|
+
onclick={() => (activeTab = category.id)}
|
|
106
|
+
>
|
|
107
|
+
{#if category.icon}<span class="text-base">{category.icon}</span>{/if}
|
|
108
|
+
<span>{category.label}</span>
|
|
109
|
+
{#if count > 0}<span class="badge badge-sm">{count}</span>{/if}
|
|
110
|
+
</button>
|
|
111
|
+
{/each}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
{#if activeCategory}
|
|
115
|
+
{@const count = selectedCount(activeCategory.id)}
|
|
116
|
+
{@const isAdditive = variantCategoryMode(activeCategory, forceMode) === 'additive'}
|
|
117
|
+
<div class="card bg-base-200 shadow-sm">
|
|
118
|
+
<div class="card-body p-4">
|
|
119
|
+
<!-- Category header: mode badge + bulk actions -->
|
|
120
|
+
<div class="flex items-center justify-between gap-2 mb-3">
|
|
121
|
+
<div class="flex items-center gap-2">
|
|
122
|
+
{#if activeCategory.icon}<span class="text-xl">{activeCategory.icon}</span>{/if}
|
|
123
|
+
<h3 class="font-bold">{activeCategory.label}</h3>
|
|
124
|
+
<span
|
|
125
|
+
class="badge badge-sm {isAdditive ? 'badge-success' : 'badge-info'}"
|
|
126
|
+
title={isAdditive
|
|
127
|
+
? 'Additive — selections stack onto every combination'
|
|
128
|
+
: 'Multiplicative — each selection multiplies the combination count'}
|
|
129
|
+
>
|
|
130
|
+
{isAdditive ? '➕ Additive' : '✖️ Multiplicative'}
|
|
131
|
+
</span>
|
|
132
|
+
{#if count > 0}<span class="badge badge-sm badge-primary">{count}</span>{/if}
|
|
133
|
+
</div>
|
|
134
|
+
<div class="flex items-center gap-1">
|
|
135
|
+
<button
|
|
136
|
+
class="btn btn-xs"
|
|
137
|
+
onclick={() => selectAll(activeCategory.id)}
|
|
138
|
+
disabled={activeCategory.options.length === 0}
|
|
139
|
+
>
|
|
140
|
+
Select all
|
|
141
|
+
</button>
|
|
142
|
+
{#if count > 0}
|
|
143
|
+
<button class="btn btn-xs btn-ghost" onclick={() => clearCategory(activeCategory.id)}>
|
|
144
|
+
Clear
|
|
145
|
+
</button>
|
|
146
|
+
{/if}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<!-- Option chip grid -->
|
|
151
|
+
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
|
|
152
|
+
{#each activeCategory.options as option (option.id)}
|
|
153
|
+
{@const isSelected = selected[activeCategory.id]?.includes(option.id)}
|
|
154
|
+
<button
|
|
155
|
+
class="btn btn-sm normal-case justify-start gap-1 {isSelected
|
|
156
|
+
? 'btn-info'
|
|
157
|
+
: 'btn-outline'}"
|
|
158
|
+
aria-pressed={isSelected}
|
|
159
|
+
onclick={() => toggleOption(activeCategory.id, option.id)}
|
|
160
|
+
>
|
|
161
|
+
{isSelected ? '✓' : '➕'}
|
|
162
|
+
<span class="truncate">{option.label}</span>
|
|
163
|
+
</button>
|
|
164
|
+
{/each}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
{/if}
|
|
169
|
+
|
|
170
|
+
<!-- Footer: live combination count -->
|
|
171
|
+
{#if showCount}
|
|
172
|
+
<div class="flex items-center justify-between mt-3 text-sm">
|
|
173
|
+
<span class="text-base-content/60">
|
|
174
|
+
<strong class="text-base-content">{combinationCount}</strong>
|
|
175
|
+
{countLabel}
|
|
176
|
+
{#if combinationCount === 0}<span class="text-base-content/40"> — select an option to start</span>{/if}
|
|
177
|
+
</span>
|
|
178
|
+
<button class="btn btn-xs btn-ghost" onclick={clearAll}>Clear all</button>
|
|
179
|
+
</div>
|
|
180
|
+
{/if}
|
|
181
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VariantMatrixPicker Component
|
|
3
|
+
*
|
|
4
|
+
* Category/option chip grid for building a combinatorial variant matrix
|
|
5
|
+
* (jatui-m4uz3.8, ported from vid-gen's FrameVariantGenerator selection UI,
|
|
6
|
+
* lines 72–197 / 500–620, with the hardcoded presets stripped).
|
|
7
|
+
*
|
|
8
|
+
* Every category renders as a tab with its options as toggleable chips.
|
|
9
|
+
* Each category carries an `additive`/`multiplicative` badge:
|
|
10
|
+
* - multiplicative: each selection multiplies the total combination count
|
|
11
|
+
* (cartesian product across multiplicative categories);
|
|
12
|
+
* - additive: selections stack flat onto every combination.
|
|
13
|
+
* A live combination count renders in the footer via `countCombinations`.
|
|
14
|
+
*
|
|
15
|
+
* The algebra lives in `./variantCombinations.ts` (pure, unit-tested);
|
|
16
|
+
* the host passes `categories` and reads/binds `selected`.
|
|
17
|
+
*/
|
|
18
|
+
import { type VariantCategory, type VariantSelection, type VariantSelectionMode } from './variantCombinations';
|
|
19
|
+
interface Props {
|
|
20
|
+
/** Category/option matrix — caller-supplied, no presets inside the component. */
|
|
21
|
+
categories: VariantCategory[];
|
|
22
|
+
/** categoryId → selected option ids. Bind to drive the matrix externally. */
|
|
23
|
+
selected: VariantSelection;
|
|
24
|
+
/** Force every category to one mode (`null` respects each category's own). */
|
|
25
|
+
forceMode?: VariantSelectionMode | null;
|
|
26
|
+
/** Show the live combination count footer (default true). */
|
|
27
|
+
showCount?: boolean;
|
|
28
|
+
/** Optional label for the count footer (default "combinations"). */
|
|
29
|
+
countLabel?: string;
|
|
30
|
+
/** Fired after every selection change with the new selection. */
|
|
31
|
+
onChange?: (selected: VariantSelection) => void;
|
|
32
|
+
/** Additional CSS class for the container. */
|
|
33
|
+
class?: string;
|
|
34
|
+
}
|
|
35
|
+
declare const VariantMatrixPicker: import("svelte").Component<Props, {}, "selected">;
|
|
36
|
+
type VariantMatrixPicker = ReturnType<typeof VariantMatrixPicker>;
|
|
37
|
+
export default VariantMatrixPicker;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure combination algebra behind `VariantMatrixPicker` (jatui-m4uz3.8).
|
|
3
|
+
*
|
|
4
|
+
* Ported from vid-gen's `variantPresets.js` (generateCombinations + friends),
|
|
5
|
+
* with the hardcoded apparel/background/lighting preset map stripped out:
|
|
6
|
+
* every category and option is caller-supplied, so the algebra works for any
|
|
7
|
+
* combinatorial domain (image variants, A/B matrices, test permutations…).
|
|
8
|
+
*
|
|
9
|
+
* Two category modes drive the math:
|
|
10
|
+
* - `multiplicative`: each selected option pairs with every selection in the
|
|
11
|
+
* other multiplicative categories → cartesian product, one new variant
|
|
12
|
+
* per pick.
|
|
13
|
+
* - `additive`: all selected options stack flat into every combination —
|
|
14
|
+
* they ride along instead of multiplying.
|
|
15
|
+
*
|
|
16
|
+
* No DOM, no runes — kept separate so the math is unit-testable under plain
|
|
17
|
+
* node:test (mirrors the `batch/` module split from jatui-m4uz3.7).
|
|
18
|
+
*/
|
|
19
|
+
export type VariantSelectionMode = 'multiplicative' | 'additive';
|
|
20
|
+
export interface VariantOption {
|
|
21
|
+
id: string;
|
|
22
|
+
label: string;
|
|
23
|
+
/** Optional host payload — vid-gen used image-edit prompts; joined with ', ' into `VariantCombination.prompt`. */
|
|
24
|
+
prompt?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface VariantCategory {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
/** Emoji or short glyph shown on the category tab. */
|
|
30
|
+
icon?: string;
|
|
31
|
+
/** Defaults to `multiplicative`. */
|
|
32
|
+
mode?: VariantSelectionMode;
|
|
33
|
+
options: VariantOption[];
|
|
34
|
+
}
|
|
35
|
+
/** categoryId → selected option ids (mirror of vid-gen's selectedModifiers). */
|
|
36
|
+
export type VariantSelection = Record<string, string[]>;
|
|
37
|
+
export interface VariantCombination {
|
|
38
|
+
/** Flat list of every option id in the combination (multiplicative picks + stacked additive picks). */
|
|
39
|
+
modifierIds: string[];
|
|
40
|
+
/** All `prompt`s joined with ', ' — empty string when no option carries a prompt. */
|
|
41
|
+
prompt: string;
|
|
42
|
+
/** Resolved option objects, in category order. */
|
|
43
|
+
options: VariantOption[];
|
|
44
|
+
}
|
|
45
|
+
export interface GenerateCombinationsOptions {
|
|
46
|
+
/** Override every category's mode. `null` (default) respects each category's own mode. */
|
|
47
|
+
forceMode?: VariantSelectionMode | null;
|
|
48
|
+
}
|
|
49
|
+
/** Resolve a category's effective mode: forceMode wins, then the category's own, then multiplicative. */
|
|
50
|
+
export declare function variantCategoryMode(category: VariantCategory, forceMode?: VariantSelectionMode | null): VariantSelectionMode;
|
|
51
|
+
/** Look up an option by category + id across a caller-supplied category list. */
|
|
52
|
+
export declare function findVariantOption(categories: VariantCategory[], categoryId: string, optionId: string): VariantOption | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Number of combinations `generateCombinations` would produce — the live count
|
|
55
|
+
* the picker footer renders. Multiplicative categories multiply; a selection
|
|
56
|
+
* with only additive categories counts as exactly 1 stacked combination.
|
|
57
|
+
*/
|
|
58
|
+
export declare function countCombinations(categories: VariantCategory[], selected: VariantSelection, forceMode?: VariantSelectionMode | null): number;
|
|
59
|
+
/**
|
|
60
|
+
* Generate all combinations from the current selection.
|
|
61
|
+
*
|
|
62
|
+
* Mirrors vid-gen's `generateCombinations(selectedModifiers, options)`:
|
|
63
|
+
* - categories with nothing selected are skipped entirely (all-empty → []);
|
|
64
|
+
* - multiplicative categories contribute one option per combination via
|
|
65
|
+
* cartesian product; additive selections stack flat onto every combination;
|
|
66
|
+
* - with no multiplicative selections at all, a single additive-only
|
|
67
|
+
* combination is produced.
|
|
68
|
+
*/
|
|
69
|
+
export declare function generateCombinations(categories: VariantCategory[], selected: VariantSelection, options?: GenerateCombinationsOptions): VariantCombination[];
|
|
70
|
+
/**
|
|
71
|
+
* Build a variant filename from an original filename and modifier ids
|
|
72
|
+
* (vid-gen's `generateVariantFilename`, generic — no preset knowledge):
|
|
73
|
+
* `photo.png` + ['a', 'b'] → `photo_a_b.png`, ids sorted for consistency.
|
|
74
|
+
*/
|
|
75
|
+
export declare function generateVariantFilename(originalFilename: string, modifierIds: string[]): string;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure combination algebra behind `VariantMatrixPicker` (jatui-m4uz3.8).
|
|
3
|
+
*
|
|
4
|
+
* Ported from vid-gen's `variantPresets.js` (generateCombinations + friends),
|
|
5
|
+
* with the hardcoded apparel/background/lighting preset map stripped out:
|
|
6
|
+
* every category and option is caller-supplied, so the algebra works for any
|
|
7
|
+
* combinatorial domain (image variants, A/B matrices, test permutations…).
|
|
8
|
+
*
|
|
9
|
+
* Two category modes drive the math:
|
|
10
|
+
* - `multiplicative`: each selected option pairs with every selection in the
|
|
11
|
+
* other multiplicative categories → cartesian product, one new variant
|
|
12
|
+
* per pick.
|
|
13
|
+
* - `additive`: all selected options stack flat into every combination —
|
|
14
|
+
* they ride along instead of multiplying.
|
|
15
|
+
*
|
|
16
|
+
* No DOM, no runes — kept separate so the math is unit-testable under plain
|
|
17
|
+
* node:test (mirrors the `batch/` module split from jatui-m4uz3.7).
|
|
18
|
+
*/
|
|
19
|
+
/** Resolve a category's effective mode: forceMode wins, then the category's own, then multiplicative. */
|
|
20
|
+
export function variantCategoryMode(category, forceMode = null) {
|
|
21
|
+
return forceMode ?? category.mode ?? 'multiplicative';
|
|
22
|
+
}
|
|
23
|
+
/** Look up an option by category + id across a caller-supplied category list. */
|
|
24
|
+
export function findVariantOption(categories, categoryId, optionId) {
|
|
25
|
+
return categories.find((c) => c.id === categoryId)?.options.find((o) => o.id === optionId);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Number of combinations `generateCombinations` would produce — the live count
|
|
29
|
+
* the picker footer renders. Multiplicative categories multiply; a selection
|
|
30
|
+
* with only additive categories counts as exactly 1 stacked combination.
|
|
31
|
+
*/
|
|
32
|
+
export function countCombinations(categories, selected, forceMode = null) {
|
|
33
|
+
const active = categories.filter((c) => (selected[c.id]?.length ?? 0) > 0);
|
|
34
|
+
if (active.length === 0)
|
|
35
|
+
return 0;
|
|
36
|
+
let count = 1;
|
|
37
|
+
let multiplicativeSeen = false;
|
|
38
|
+
for (const category of active) {
|
|
39
|
+
const n = selected[category.id].length;
|
|
40
|
+
if (variantCategoryMode(category, forceMode) === 'multiplicative') {
|
|
41
|
+
multiplicativeSeen = true;
|
|
42
|
+
count *= n;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Only-additive selections produce a single stacked combination, not zero.
|
|
46
|
+
return multiplicativeSeen ? count : 1;
|
|
47
|
+
}
|
|
48
|
+
/** Cartesian product of arrays (vid-gen's helper, verbatim semantics). */
|
|
49
|
+
function cartesian(...arrays) {
|
|
50
|
+
return arrays.reduce((acc, array) => acc.flatMap((x) => array.map((y) => [...x, y])), [[]]);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Generate all combinations from the current selection.
|
|
54
|
+
*
|
|
55
|
+
* Mirrors vid-gen's `generateCombinations(selectedModifiers, options)`:
|
|
56
|
+
* - categories with nothing selected are skipped entirely (all-empty → []);
|
|
57
|
+
* - multiplicative categories contribute one option per combination via
|
|
58
|
+
* cartesian product; additive selections stack flat onto every combination;
|
|
59
|
+
* - with no multiplicative selections at all, a single additive-only
|
|
60
|
+
* combination is produced.
|
|
61
|
+
*/
|
|
62
|
+
export function generateCombinations(categories, selected, options = {}) {
|
|
63
|
+
const { forceMode = null } = options;
|
|
64
|
+
const active = categories.filter((c) => (selected[c.id]?.length ?? 0) > 0);
|
|
65
|
+
if (active.length === 0)
|
|
66
|
+
return [];
|
|
67
|
+
const multiplicativeArrays = active
|
|
68
|
+
.filter((c) => variantCategoryMode(c, forceMode) === 'multiplicative')
|
|
69
|
+
.map((c) => selected[c.id].map((optionId) => ({ categoryId: c.id, optionId })));
|
|
70
|
+
const additivePicks = active
|
|
71
|
+
.filter((c) => variantCategoryMode(c, forceMode) === 'additive')
|
|
72
|
+
.flatMap((c) => selected[c.id].map((optionId) => ({ categoryId: c.id, optionId })));
|
|
73
|
+
const multiplicativeCombos = cartesian(...multiplicativeArrays);
|
|
74
|
+
const combos = multiplicativeArrays.length
|
|
75
|
+
? multiplicativeCombos.map((combo) => [...combo, ...additivePicks])
|
|
76
|
+
: [additivePicks];
|
|
77
|
+
return combos.map((combo) => {
|
|
78
|
+
const resolved = combo
|
|
79
|
+
.map(({ categoryId, optionId }) => findVariantOption(categories, categoryId, optionId))
|
|
80
|
+
.filter((o) => o !== undefined);
|
|
81
|
+
return {
|
|
82
|
+
modifierIds: combo.map(({ optionId }) => optionId),
|
|
83
|
+
prompt: resolved
|
|
84
|
+
.map((o) => o.prompt)
|
|
85
|
+
.filter((p) => p !== undefined)
|
|
86
|
+
.join(', '),
|
|
87
|
+
options: resolved
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build a variant filename from an original filename and modifier ids
|
|
93
|
+
* (vid-gen's `generateVariantFilename`, generic — no preset knowledge):
|
|
94
|
+
* `photo.png` + ['a', 'b'] → `photo_a_b.png`, ids sorted for consistency.
|
|
95
|
+
*/
|
|
96
|
+
export function generateVariantFilename(originalFilename, modifierIds) {
|
|
97
|
+
const stem = originalFilename.replace(/\.[^/.]+$/, '');
|
|
98
|
+
const suffix = [...modifierIds].sort().join('_');
|
|
99
|
+
return suffix ? `${stem}_${suffix}.png` : `${stem}.png`;
|
|
100
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,10 @@ export { createDirtyGuard } from './utils/dirtyGuard';
|
|
|
56
56
|
export type { DirtyGuard, DirtyGuardOptions, BeforeNavigateFn } from './utils/dirtyGuard';
|
|
57
57
|
export { default as MediaLightbox } from './components/MediaLightbox.svelte';
|
|
58
58
|
export type { MediaItem } from './components/MediaLightbox.svelte';
|
|
59
|
+
export { default as InfiniteGrid } from './components/InfiniteGrid.svelte';
|
|
60
|
+
export { default as InfiniteSearchGrid } from './components/InfiniteSearchGrid.svelte';
|
|
61
|
+
export { createInfiniteLoader } from './components/infiniteGridLoader';
|
|
62
|
+
export type { InfinitePage, InfiniteLoadContext, InfiniteLoadFn, InfiniteLoader, InfiniteLoaderState, InfiniteStatus } from './components/infiniteGridLoader';
|
|
59
63
|
export { default as Button } from './components/Button.svelte';
|
|
60
64
|
export { default as Badge } from './components/Badge.svelte';
|
|
61
65
|
export { default as Card } from './components/Card.svelte';
|
|
@@ -78,6 +82,8 @@ export { default as LazyImage } from './components/LazyImage.svelte';
|
|
|
78
82
|
export { default as ResizablePanel } from './components/ResizablePanel.svelte';
|
|
79
83
|
export { default as VoicePlayer } from './components/VoicePlayer.svelte';
|
|
80
84
|
export { default as SpeechForm } from './components/SpeechForm.svelte';
|
|
85
|
+
export { default as AudioRecorder } from './components/AudioRecorder.svelte';
|
|
86
|
+
export type { AudioRecordingResult } from './components/AudioRecorder.svelte';
|
|
81
87
|
export { default as ThemeSelector } from './components/ThemeSelector.svelte';
|
|
82
88
|
export { default as Sparkline } from './components/Sparkline.svelte';
|
|
83
89
|
export { default as BurndownChart } from './components/BurndownChart.svelte';
|
|
@@ -93,6 +99,8 @@ export type { FileDropzoneFile } from './components/FileDropzone.svelte';
|
|
|
93
99
|
export { default as VariablePicker } from './components/VariablePicker.svelte';
|
|
94
100
|
export type { TemplateVar, TemplateVarGroup } from './types/templateVars';
|
|
95
101
|
export { default as EmojiPicker } from './components/EmojiPicker.svelte';
|
|
102
|
+
export { default as IconSelector } from './components/IconSelector.svelte';
|
|
103
|
+
export type { IconSelectorOption, IconComponentProps } from './components/IconSelector.svelte';
|
|
96
104
|
export { EMOJI_DATA, ALL_EMOJIS, searchEmojis } from './data/emojis';
|
|
97
105
|
export type { EmojiEntry, EmojiGroup } from './data/emojis';
|
|
98
106
|
export { default as LinkShortener } from './components/LinkShortener.svelte';
|
|
@@ -166,6 +174,8 @@ export type { MarkupTool, Point, MarkupShape, ArrowShape, RectangleShape, Ellips
|
|
|
166
174
|
export type { Annotation, AnnotationProfile, AnnotationThread, AnnotationCallbacks, } from './types/annotation';
|
|
167
175
|
export { default as KeyboardShortcutsOverlay } from './components/KeyboardShortcutsOverlay.svelte';
|
|
168
176
|
export type { KeyboardShortcut, SectionEntry as KeyboardShortcutSectionEntry, ShortcutSection as KeyboardShortcutSection, } from './components/KeyboardShortcutsOverlay.svelte';
|
|
177
|
+
export { createShortcutRegistry, getShortcutRegistry, formatShortcut, parseCombo as parseShortcutCombo, isTypingTarget, detectPlatform as detectShortcutPlatform, SIDEBAR_TOGGLE_KEY, } from './utils/keyboardShortcutRegistry';
|
|
178
|
+
export type { ShortcutRegistry, ShortcutDefinition, ShortcutModifier, ShortcutPlatform, PushContextOptions as ShortcutContextOptions, RegistrySection as ShortcutRegistrySection, ToSectionsOptions as ShortcutSectionsOptions, ShortcutRegistryOptions, ShortcutEventTarget, } from './utils/keyboardShortcutRegistry';
|
|
169
179
|
export { default as MapView } from './components/MapView.svelte';
|
|
170
180
|
export { default as LocationMap } from './components/LocationMap.svelte';
|
|
171
181
|
export { default as GPSTracker } from './components/GPSTracker.svelte';
|
|
@@ -177,6 +187,10 @@ export { staggerEntrance } from './actions/staggerEntrance';
|
|
|
177
187
|
export type { StaggerEntranceOptions } from './actions/staggerEntrance';
|
|
178
188
|
export { attention } from './actions/attention';
|
|
179
189
|
export type { AttentionEffect, AttentionOptions } from './actions/attention';
|
|
190
|
+
export { reveal, REVEAL_HIDDEN_CLASS, REVEAL_BASE_CLASS } from './actions/reveal';
|
|
191
|
+
export type { RevealOptions, RevealVariant } from './actions/reveal';
|
|
192
|
+
export { default as ThumbnailGrid } from './components/ThumbnailGrid.svelte';
|
|
193
|
+
export type { ThumbnailItem } from './components/ThumbnailGrid.svelte';
|
|
180
194
|
export { floating, makeVirtualAnchor } from './actions/floating';
|
|
181
195
|
export type { FloatingOptions, Placement } from './actions/floating';
|
|
182
196
|
export { glide } from './transitions/glide';
|
|
@@ -227,3 +241,17 @@ export { default as Slider } from './components/basic/Slider.svelte';
|
|
|
227
241
|
export { default as Pagination } from './components/basic/Pagination.svelte';
|
|
228
242
|
export { MAX_UNDO_DEPTH, createHistory, pushState, beginChange, endChange, untracked, undo, redo, clearHistory } from './history';
|
|
229
243
|
export type { History } from './history';
|
|
244
|
+
export { default as BatchJobTable } from './components/batch/BatchJobTable.svelte';
|
|
245
|
+
export type { JobContext, RowControls, BatchControls, BatchPhase } from './components/batch/BatchJobTable.svelte';
|
|
246
|
+
export { default as QuotaMeter } from './components/batch/QuotaMeter.svelte';
|
|
247
|
+
export { toJobRows, nextJobsToStart, isProcessLimitReached, moveJob, summarizeJobs, formatElapsed } from './components/batch/batchQueue';
|
|
248
|
+
export type { JobRow, JobStatus, BatchSummary, DispatchLimits } from './components/batch/batchQueue';
|
|
249
|
+
export { rollingAverage, estimateRemainingMs } from './components/batch/elapsedEstimate';
|
|
250
|
+
export type { EtaInput } from './components/batch/elapsedEstimate';
|
|
251
|
+
export { useElapsedEstimate } from './components/batch/useElapsedEstimate';
|
|
252
|
+
export type { ElapsedEstimate } from './components/batch/useElapsedEstimate';
|
|
253
|
+
export { pollOperation, PollTimeoutError } from './components/batch/pollOperation';
|
|
254
|
+
export type { PollStep, PollContext, PollOptions } from './components/batch/pollOperation';
|
|
255
|
+
export { default as VariantMatrixPicker } from './components/variant/VariantMatrixPicker.svelte';
|
|
256
|
+
export { countCombinations, generateCombinations, generateVariantFilename, findVariantOption, variantCategoryMode } from './components/variant/variantCombinations';
|
|
257
|
+
export type { VariantCategory, VariantOption, VariantSelection, VariantSelectionMode, VariantCombination } from './components/variant/variantCombinations';
|