@joewinke/jatui 0.1.27 → 0.1.29
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 +3 -3
- package/LICENSE +21 -0
- package/dist/actions/collapse.d.ts +1 -1
- package/dist/actions/collapse.js +16 -30
- package/dist/actions/listNav.d.ts +134 -0
- package/dist/actions/listNav.js +643 -0
- package/dist/actions/railNav.js +78 -89
- package/dist/components/InlineEdit.svelte +26 -1
- package/dist/components/InlineEdit.svelte.d.ts +13 -0
- package/dist/components/PhoneInput.svelte +70 -20
- package/dist/components/PhoneInput.svelte.d.ts +5 -2
- package/dist/components/ResizableDivider.svelte +148 -18
- package/dist/components/ResizableDivider.svelte.d.ts +14 -3
- package/dist/components/SignaturePad.svelte +13 -6
- package/dist/components/SignaturePad.svelte.d.ts +1 -0
- package/dist/components/ThemeSelector.svelte +61 -2
- package/dist/components/ThemeSelector.svelte.d.ts +4 -0
- package/dist/components/markup.js +59 -62
- package/dist/components/session-nav/transcriptModel.js +49 -66
- package/dist/data/emojis.js +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/stores/confirmDialog.svelte.js +5 -5
- package/dist/stores/inputDialog.svelte.js +5 -5
- package/dist/types/booking.js +4 -4
- package/dist/types/comments.js +2 -2
- package/dist/types/messaging.js +3 -3
- package/dist/types/pipeline.js +2 -2
- package/dist/utils/currency.js +2 -4
- package/dist/utils/daisyuiColors.js +25 -27
- package/dist/utils/dateFormatters.js +44 -44
- package/dist/utils/googleMapsLoader.js +46 -100
- package/dist/utils/mentionParser.js +30 -30
- package/dist/utils/phone.js +21 -27
- package/dist/utils/phoneFormat.js +9 -9
- package/dist/utils/taskUtils.js +1 -1
- package/package.json +11 -5
package/CLAUDE.md
CHANGED
|
@@ -24,9 +24,9 @@ Then import:
|
|
|
24
24
|
| `TextInput` | Text input with label, error, icon slots | `label`, `value` (bindable), `placeholder`, `error`, `disabled` |
|
|
25
25
|
| `TextArea` | Multiline text input | `label`, `value` (bindable), `placeholder`, `rows`, `error`, `maxlength` |
|
|
26
26
|
| `SelectInput` | Dropdown select | `label`, `value` (bindable), `options: SelectOption[]`, `placeholder` |
|
|
27
|
-
| `PhoneInput` | Intl phone (country dropdown + tel input, outputs E.164) | `value` (bindable), `name`, `defaultCountry`, `required` |
|
|
27
|
+
| `PhoneInput` | Intl phone (country dropdown + tel input, outputs E.164; caps national digits per country). Fires `onChange(value)` on every change. | `value` (bindable), `name`, `defaultCountry`, `required`, `onChange` |
|
|
28
28
|
| `ChipInput` | Tag/chip entry with suggestions | `chips` (bindable), `suggestions: SuggestionGroup[]`, `placeholder` |
|
|
29
|
-
| `SearchDropdown` | Searchable dropdown with grouped results | `groups: SearchDropdownGroup[]`, `placeholder`, `
|
|
29
|
+
| `SearchDropdown` | Searchable dropdown with grouped results, keyboard nav, avatar/initials options. `appendToBody` escapes table/overflow stacking contexts; `variant='chip'` renders a compact pill; `size='sm'` a smaller trigger; `dropup` opens upward; `clearable` shows an × when a value is set. Exposes `openDropdown()`. | `groups: SearchDropdownGroup[]`, `value`, `onChange`, `placeholder`, `variant`, `size`, `dropup`, `appendToBody`, `clearable`, `colorFn` |
|
|
30
30
|
| `FilterDropdown` | Multi-select filter dropdown | `label`, `options: string[]`, `selected: Set<string>` (bindable) |
|
|
31
31
|
| `SortDropdown` | Sort by column with direction | `options: SortOption[]`, `sortBy` (bindable), `sortDir` (bindable) |
|
|
32
32
|
| `InlineEdit` | Click-to-edit text | `value` (bindable), `segments: DisplaySegment[]`, `onSave` |
|
|
@@ -37,7 +37,7 @@ Then import:
|
|
|
37
37
|
| `SmartImageEditor` | AI-powered image upload with background removal and multi-variant processing pipeline. **Preferred over `ImageUpload` in editor contexts.** Two modes: simple (drop-in replacement, outputs blob URL) and editor (POST multipart to backend → variant grid). | `value` (bindable), `spec?: ImageProcessingSpec`, `uploadEndpoint?`, `onComplete?`, `accept?`, `maxSizeMb?` |
|
|
38
38
|
| `FileDropzone` | Multi-file/folder/ZIP drag-drop upload zone. Handles FileSystem API folder recursion, MIME/size validation, and shows a pending-file grid. Calls `onFilesSelected(files, errors)` with validated `File[]`. Accepts already-uploaded files via `uploadedFiles: FileDropzoneFile[]` for post-upload display. | `accept?: string[]`, `maxSize?`, `maxFiles?`, `multiple?`, `uploadedFiles?`, `isUploading?`, `isExtracting?`, `dropLabel?`, `dropSubtext?`, `onFilesSelected?` |
|
|
39
39
|
| `FileThumbnail` | File thumbnail with MIME-aware icon fallback. Shows an `<img>` for image files (with loading/error states) and typed icons for PDF, ZIP, CAD, and generic files. Size variants: `sm`, `md`, `lg`, `xl`. | `name`, `mimeType`, `url?`, `size?` |
|
|
40
|
-
| `SignaturePad` | Draw signature on canvas
|
|
40
|
+
| `SignaturePad` | Draw signature on canvas (mouse + touch). Emits base64 PNG on each stroke-end via bindable `signatureData` or `onSignatureChange(data)`. Exposes `clear()`/`isEmpty()`. Pass `disabled` for a dimmed, non-interactive preview. | `signatureData` (bindable), `onSignatureChange`, `label`, `width`, `height`, `disabled`, `required` |
|
|
41
41
|
|
|
42
42
|
### Scheduling & Time
|
|
43
43
|
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joe Winke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* ─── Usage — action form (most sites) ─────────────────────────────────────────
|
|
34
34
|
*
|
|
35
35
|
* <script>
|
|
36
|
-
* import { collapse } from '
|
|
36
|
+
* import { collapse } from '../../../dist'
|
|
37
37
|
* let collapsible = $state(false)
|
|
38
38
|
* let expanded = $state(false)
|
|
39
39
|
* const bodyId = `collapse-${$props.id()}`
|
package/dist/actions/collapse.js
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* ─── Usage — action form (most sites) ─────────────────────────────────────────
|
|
34
34
|
*
|
|
35
35
|
* <script>
|
|
36
|
-
* import { collapse } from '
|
|
36
|
+
* import { collapse } from '../../../dist'
|
|
37
37
|
* let collapsible = $state(false)
|
|
38
38
|
* let expanded = $state(false)
|
|
39
39
|
* const bodyId = `collapse-${$props.id()}`
|
|
@@ -58,34 +58,22 @@
|
|
|
58
58
|
* state (e.g. a `SvelteSet` of ids, with the id interpolating the row's own id) —
|
|
59
59
|
* one action instance attaches per row element.
|
|
60
60
|
*/
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
return t;
|
|
69
|
-
};
|
|
70
|
-
return __assign.apply(this, arguments);
|
|
71
|
-
};
|
|
72
|
-
var DEFAULTS = { threshold: 120, expanded: false, fadeStart: 55, epsilon: 4 };
|
|
73
|
-
export function collapse(node, options) {
|
|
74
|
-
if (options === void 0) { options = {}; }
|
|
75
|
-
var opts = __assign(__assign({}, DEFAULTS), options);
|
|
76
|
-
var collapsible = false;
|
|
77
|
-
var rafId = 0;
|
|
78
|
-
var applied = false;
|
|
61
|
+
const DEFAULTS = { threshold: 120, expanded: false, fadeStart: 55, epsilon: 4 };
|
|
62
|
+
export function collapse(node, options = {}) {
|
|
63
|
+
let opts = { ...DEFAULTS, ...options };
|
|
64
|
+
let collapsible = false;
|
|
65
|
+
let rafId = 0;
|
|
66
|
+
let applied = false;
|
|
79
67
|
function gradient() {
|
|
80
|
-
return
|
|
68
|
+
return `linear-gradient(to bottom, black ${opts.fadeStart}%, transparent 100%)`;
|
|
81
69
|
}
|
|
82
70
|
function apply() {
|
|
83
|
-
|
|
71
|
+
const shouldCollapse = collapsible && !opts.expanded;
|
|
84
72
|
if (shouldCollapse === applied)
|
|
85
73
|
return; // idempotent — avoids RO feedback loops
|
|
86
74
|
applied = shouldCollapse;
|
|
87
75
|
if (shouldCollapse) {
|
|
88
|
-
node.style.maxHeight =
|
|
76
|
+
node.style.maxHeight = `${opts.threshold}px`;
|
|
89
77
|
node.style.overflow = 'hidden';
|
|
90
78
|
node.style.maskImage = gradient();
|
|
91
79
|
node.style.webkitMaskImage = gradient();
|
|
@@ -98,12 +86,11 @@ export function collapse(node, options) {
|
|
|
98
86
|
}
|
|
99
87
|
}
|
|
100
88
|
function measure() {
|
|
101
|
-
var _a;
|
|
102
89
|
// scrollHeight is the full content height even while collapsed.
|
|
103
|
-
|
|
90
|
+
const next = node.scrollHeight > opts.threshold + opts.epsilon;
|
|
104
91
|
if (next !== collapsible) {
|
|
105
92
|
collapsible = next;
|
|
106
|
-
|
|
93
|
+
opts.onCollapsible?.(collapsible);
|
|
107
94
|
}
|
|
108
95
|
apply();
|
|
109
96
|
}
|
|
@@ -112,13 +99,12 @@ export function collapse(node, options) {
|
|
|
112
99
|
rafId = requestAnimationFrame(measure);
|
|
113
100
|
}
|
|
114
101
|
// Re-measure when the content's natural size changes (streaming, {@html}, lazy images).
|
|
115
|
-
|
|
102
|
+
const ro = new ResizeObserver(scheduleMeasure);
|
|
116
103
|
ro.observe(node);
|
|
117
104
|
scheduleMeasure();
|
|
118
105
|
return {
|
|
119
|
-
update
|
|
120
|
-
|
|
121
|
-
opts = __assign(__assign({}, DEFAULTS), next);
|
|
106
|
+
update(next = {}) {
|
|
107
|
+
opts = { ...DEFAULTS, ...next };
|
|
122
108
|
// expanded/threshold/fade may have changed — re-measure and let
|
|
123
109
|
// apply() compare the freshly computed shouldCollapse against the
|
|
124
110
|
// real last-applied state. (Force-flipping `applied` here used to make
|
|
@@ -126,7 +112,7 @@ export function collapse(node, options) {
|
|
|
126
112
|
// silently skipping the style update.)
|
|
127
113
|
scheduleMeasure();
|
|
128
114
|
},
|
|
129
|
-
destroy
|
|
115
|
+
destroy() {
|
|
130
116
|
cancelAnimationFrame(rafId);
|
|
131
117
|
ro.disconnect();
|
|
132
118
|
},
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared keyboard-navigation action + composable for list-like UIs.
|
|
3
|
+
*
|
|
4
|
+
* Base motions — j/k / ArrowDown/ArrowUp with wraparound, Enter/Space to
|
|
5
|
+
* trigger select, Escape to clear focus. Typing targets (input, textarea,
|
|
6
|
+
* select, contenteditable) and most modifier combos are skipped so the
|
|
7
|
+
* shortcuts never interfere with typing or app-level keybindings.
|
|
8
|
+
*
|
|
9
|
+
* Vim-motion layer (always on):
|
|
10
|
+
*
|
|
11
|
+
* gg jump to first item
|
|
12
|
+
* G jump to last item
|
|
13
|
+
* {n}j / {n}k move `n` items (count-prefix buffer with 1.5s timeout)
|
|
14
|
+
* {n}G, {n}gg jump to 1-indexed line `n` (clamped to list length)
|
|
15
|
+
* Ctrl+D / Ctrl+U move roughly half a viewport's worth of items
|
|
16
|
+
* zz scroll the focused item to the centre of its viewport
|
|
17
|
+
*
|
|
18
|
+
* Selection layer (opt-in via `selectable: true`):
|
|
19
|
+
*
|
|
20
|
+
* V toggle visual mode (anchor at current focus, selects it)
|
|
21
|
+
* j / k while visual mode is on, also extends the range from anchor
|
|
22
|
+
* x toggle selection of the focused item (no movement)
|
|
23
|
+
* Shift+J / K toggle current + move down/up + toggle new focus (range-build)
|
|
24
|
+
* * toggle select-all of the currently visible items
|
|
25
|
+
* Escape backs out one layer at a time:
|
|
26
|
+
* pending motion → visual mode → selection → focus / onEscape
|
|
27
|
+
*
|
|
28
|
+
* Selected items get the `selected-class` (default `jk-selected`) so pages
|
|
29
|
+
* can style them. The controller exposes selectedIds()/clearSelection()/
|
|
30
|
+
* selectAll()/toggleSelection()/isSelecting() and fires onSelectionChange
|
|
31
|
+
* whenever the set mutates. A floating `BulkActionBar` is the canonical
|
|
32
|
+
* chip for surfacing the count + per-route bulk actions.
|
|
33
|
+
*
|
|
34
|
+
* Usage — action form (simplest):
|
|
35
|
+
*
|
|
36
|
+
* <div use:listNav={{
|
|
37
|
+
* onSelect: (el, idx) => openDetail(el.dataset.navId!),
|
|
38
|
+
* onEscape: () => closeDetail(),
|
|
39
|
+
* }}>
|
|
40
|
+
* {#each items as item}
|
|
41
|
+
* <button data-nav-id={item.id}>{item.title}</button>
|
|
42
|
+
* {/each}
|
|
43
|
+
* </div>
|
|
44
|
+
*
|
|
45
|
+
* Usage — composable form (when you already have a keydown handler):
|
|
46
|
+
*
|
|
47
|
+
* const nav = createListNav({
|
|
48
|
+
* getItems: () => Array.from(container.querySelectorAll('[data-nav-id]')),
|
|
49
|
+
* onSelect: (el) => openDetail(el.dataset.navId!),
|
|
50
|
+
* selectable: true,
|
|
51
|
+
* onSelectionChange: (ids) => { selectedIds = ids; },
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* function onKeydown(e: KeyboardEvent) {
|
|
55
|
+
* if (nav.handleKeydown(e)) return;
|
|
56
|
+
* // ...page-specific shortcuts
|
|
57
|
+
* }
|
|
58
|
+
*/
|
|
59
|
+
import type { Action } from 'svelte/action';
|
|
60
|
+
export interface ListNavOptions {
|
|
61
|
+
/**
|
|
62
|
+
* Return the navigable HTML elements in list order. Called on every
|
|
63
|
+
* navigation event so reactive lists stay in sync. When omitted in the
|
|
64
|
+
* action form, the action queries the node itself using {@link itemSelector}.
|
|
65
|
+
*/
|
|
66
|
+
getItems?: () => HTMLElement[] | ArrayLike<HTMLElement>;
|
|
67
|
+
/** CSS selector used by the action to locate items. Default: `[data-nav-id]`. */
|
|
68
|
+
itemSelector?: string;
|
|
69
|
+
/** Fired when Enter or Space is pressed on the focused item. */
|
|
70
|
+
onSelect?: (item: HTMLElement, index: number) => void;
|
|
71
|
+
/** Fired when Escape is pressed. Default behaviour: clears focus. */
|
|
72
|
+
onEscape?: () => void;
|
|
73
|
+
/** Fired whenever the focused index changes (including cleared focus). */
|
|
74
|
+
onFocusChange?: (item: HTMLElement | null, index: number) => void;
|
|
75
|
+
/** When true, j at the last item wraps to first (and vice-versa). Default: true. */
|
|
76
|
+
wraparound?: boolean;
|
|
77
|
+
/** Class applied to the currently focused item. Default: `jk-focused`. */
|
|
78
|
+
focusedClass?: string;
|
|
79
|
+
/** When false, all shortcuts no-op. Default: true. */
|
|
80
|
+
enabled?: boolean;
|
|
81
|
+
/** Action-only. When true, listen on window; when false, on the node. Default: true. */
|
|
82
|
+
global?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Enable the selection layer (V / Shift+J / Shift+K / x / *). Default: false.
|
|
85
|
+
* When false, those keys pass through unhandled so pages can keep them.
|
|
86
|
+
*/
|
|
87
|
+
selectable?: boolean;
|
|
88
|
+
/** Class applied to selected items. Default: `jk-selected`. */
|
|
89
|
+
selectedClass?: string;
|
|
90
|
+
/** Fires whenever the selection set mutates. Receives a fresh Set snapshot. */
|
|
91
|
+
onSelectionChange?: (ids: Set<string>) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Resolve an item's stable id. Default: `el.dataset.navId`. Override if
|
|
94
|
+
* your items use a different attribute (e.g. `data-rule-nav-id`).
|
|
95
|
+
*/
|
|
96
|
+
getItemId?: (el: HTMLElement) => string | null | undefined;
|
|
97
|
+
}
|
|
98
|
+
export interface KeyboardShortcut {
|
|
99
|
+
key: string;
|
|
100
|
+
description: string;
|
|
101
|
+
}
|
|
102
|
+
export interface ListNavController {
|
|
103
|
+
/** Returns true if the event was handled (preventDefault was called). */
|
|
104
|
+
handleKeydown(event: KeyboardEvent): boolean;
|
|
105
|
+
focusedIndex(): number;
|
|
106
|
+
focus(index: number): void;
|
|
107
|
+
focusFirst(): void;
|
|
108
|
+
focusLast(): void;
|
|
109
|
+
clear(): void;
|
|
110
|
+
/** Re-apply the focused class after items change (e.g. after a filter). */
|
|
111
|
+
refresh(): void;
|
|
112
|
+
/** Read-only snapshot of selected item ids. */
|
|
113
|
+
selectedIds(): Set<string>;
|
|
114
|
+
/** Toggle selection. With no arg, toggles the focused item. */
|
|
115
|
+
toggleSelection(navId?: string): void;
|
|
116
|
+
/** Replace the selection with the given ids (or clear if empty). */
|
|
117
|
+
setSelection(ids: Iterable<string>): void;
|
|
118
|
+
/** Drop the selection and exit visual mode. */
|
|
119
|
+
clearSelection(): void;
|
|
120
|
+
/** Toggle select-all of the currently visible items. */
|
|
121
|
+
selectAll(): void;
|
|
122
|
+
/** True while visual-mode (V) is active. */
|
|
123
|
+
isSelecting(): boolean;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Imperative composable — use when you already own a keydown handler and want
|
|
127
|
+
* listNav to decide whether to consume the event.
|
|
128
|
+
*/
|
|
129
|
+
export declare function createListNav(options: ListNavOptions): ListNavController;
|
|
130
|
+
/**
|
|
131
|
+
* Svelte action — attaches a keydown listener (window by default) and handles
|
|
132
|
+
* j/k/ArrowDown/ArrowUp/Enter/Space/Escape navigation over children of `node`.
|
|
133
|
+
*/
|
|
134
|
+
export declare const listNav: Action<HTMLElement, ListNavOptions | undefined>;
|