@joewinke/jatui 0.2.5 → 0.2.7
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 +19 -2
- package/dist/components/AnnotationPanel.svelte +72 -59
- package/dist/components/AnnotationPanel.svelte.d.ts +6 -0
- package/dist/components/ImageLightbox.svelte +5 -0
- package/dist/components/ImageLightbox.svelte.d.ts +2 -0
- package/dist/components/SearchDropdown.svelte +13 -0
- package/dist/components/SearchDropdown.svelte.d.ts +2 -0
- package/dist/dnd/types.d.ts +0 -9
- package/package.json +2 -2
package/CLAUDE.md
CHANGED
|
@@ -106,9 +106,9 @@ Then import:
|
|
|
106
106
|
|-----------|---------|-----------|
|
|
107
107
|
| `MarkupEditor` | Fullscreen canvas markup tool — draw arrows, boxes, ellipses, revision clouds, freehand, text over an image | `imageUrl`, `initialShapes?: MarkupShape[]`, `onsave: (shapes) => void`, `oncancel: () => void` |
|
|
108
108
|
| `MarkupOverlay` | Read-only canvas layer that renders saved `MarkupShape[]` over an image | `shapes: MarkupShape[]`, `imageUrl` |
|
|
109
|
-
| `ImageLightbox` | Inline image viewer with fullscreen modal, zoom controls, optional markup + annotation overlays | `imageUrl`, `filename?`, `markupShapes?`, `annotations?`, `activeAnnotationId?`, `annotationMode?`, `onAnnotationClick?`, `onImageClick?`, `onClose?` |
|
|
109
|
+
| `ImageLightbox` | Inline image viewer with fullscreen modal, zoom controls, optional markup + annotation overlays | `imageUrl`, `filename?`, `markupShapes?`, `annotations?`, `activeAnnotationId?`, `annotationMode?`, `showResolved?` (forwarded to the internal `AnnotationLayer`; default false), `onAnnotationClick?`, `onImageClick?`, `onClose?` |
|
|
110
110
|
| `AnnotationLayer` | Absolute-positioned pin overlay — renders threaded annotation pins at `x_percent`/`y_percent` coordinates | `annotations: Annotation[]`, `activeAnnotationId?`, `annotationMode?`, `showResolved?`, `onAnnotationClick?`, `onImageClick?` |
|
|
111
|
-
| `AnnotationPanel` | Sidebar thread list for annotations — reply, resolve, edit, delete. Uses callback props for async ops. | `annotations`, `profiles`, `activeAnnotationId?`, `currentUserId`, `callbacks: AnnotationCallbacks`, `onAnnotationSelect?`, `onAnnotationsChanged?` |
|
|
111
|
+
| `AnnotationPanel` | Sidebar thread list for annotations — reply, resolve, edit, delete. Uses callback props for async ops. | `annotations`, `profiles`, `activeAnnotationId?`, `currentUserId`, `callbacks: AnnotationCallbacks`, `readOnly?`, `title?` (panel heading, default "Annotations"; pass "Comments" to match host vocabulary), `emptyText?` (empty-state line, default "No annotations yet"), `onAnnotationSelect?`, `onAnnotationsChanged?` |
|
|
112
112
|
|
|
113
113
|
**Types & utilities:**
|
|
114
114
|
```typescript
|
|
@@ -239,6 +239,23 @@ If the project uses JST (Joe's Software Template), browse component demos at `/c
|
|
|
239
239
|
- Check the component source in `src/lib/components/` for full prop types
|
|
240
240
|
- Prefer jatui components over building custom ones when there's a match
|
|
241
241
|
|
|
242
|
+
## Releasing — never run `npm publish`
|
|
243
|
+
|
|
244
|
+
**To cut a release: bump `version` in `package.json`, commit, push to master. That's the whole job.**
|
|
245
|
+
|
|
246
|
+
`.github/workflows/publish.yml` publishes to npm on any push to master that touches
|
|
247
|
+
`package.json`, authenticating via npm Trusted Publishing (OIDC) — no token, and every
|
|
248
|
+
release carries a provenance attestation. The workflow is idempotent: if the version is
|
|
249
|
+
already on the registry it exits green without republishing, so re-running it is safe.
|
|
250
|
+
|
|
251
|
+
Publishing from a dev box instead does two bad things: the release is attributed to a
|
|
252
|
+
person rather than the attested `GitHub Actions` identity (**provenance is lost**), and it
|
|
253
|
+
races the workflow — that is exactly how 0.2.5 shipped by hand at 16:16:21Z and left CI red
|
|
254
|
+
with an `E403: cannot publish over the previously published version`.
|
|
255
|
+
|
|
256
|
+
`prepublishOnly` now runs `scripts/guard-ci-publish.mjs`, which hard-fails any publish
|
|
257
|
+
outside CI. If you hit that error, you are doing it wrong — bump the version and push.
|
|
258
|
+
|
|
242
259
|
# Layering Policy (DaisyUI is an implementation detail)
|
|
243
260
|
|
|
244
261
|
Three rules keep DaisyUI swappable instead of load-bearing:
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
activeAnnotationId?: string | null
|
|
8
8
|
currentUserId: string
|
|
9
9
|
callbacks: AnnotationCallbacks
|
|
10
|
+
/** Hides reply/resolve/edit/delete affordances — thread list stays readable */
|
|
11
|
+
readOnly?: boolean
|
|
12
|
+
/** Panel heading. Defaults to "Annotations"; pass e.g. "Comments" to match host vocabulary. */
|
|
13
|
+
title?: string
|
|
14
|
+
/** Empty-state line. Defaults to "No annotations yet". */
|
|
15
|
+
emptyText?: string
|
|
10
16
|
onAnnotationSelect?: (annotation: Annotation | null) => void
|
|
11
17
|
onAnnotationsChanged?: () => void
|
|
12
18
|
}
|
|
@@ -17,6 +23,9 @@
|
|
|
17
23
|
activeAnnotationId = null,
|
|
18
24
|
currentUserId,
|
|
19
25
|
callbacks,
|
|
26
|
+
readOnly = false,
|
|
27
|
+
title = "Annotations",
|
|
28
|
+
emptyText = "No annotations yet",
|
|
20
29
|
onAnnotationSelect,
|
|
21
30
|
onAnnotationsChanged,
|
|
22
31
|
}: Props = $props()
|
|
@@ -132,7 +141,7 @@
|
|
|
132
141
|
<!-- Header -->
|
|
133
142
|
<div class="flex items-center justify-between px-3 py-2 border-b border-base-300 flex-shrink-0">
|
|
134
143
|
<div class="flex items-center gap-2">
|
|
135
|
-
<h4 class="text-sm font-semibold">
|
|
144
|
+
<h4 class="text-sm font-semibold">{title}</h4>
|
|
136
145
|
{#if totalRoots > 0}
|
|
137
146
|
<span class="badge badge-xs badge-ghost">{totalRoots - resolvedCount} open</span>
|
|
138
147
|
{/if}
|
|
@@ -156,7 +165,7 @@
|
|
|
156
165
|
<svg class="mx-auto h-10 w-10 text-base-content/20" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
157
166
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
|
|
158
167
|
</svg>
|
|
159
|
-
<p class="text-sm text-base-content/40 mt-2">
|
|
168
|
+
<p class="text-sm text-base-content/40 mt-2">{emptyText}</p>
|
|
160
169
|
<p class="text-xs text-base-content/30 mt-1">Click the pin icon then click on the image to add one</p>
|
|
161
170
|
</div>
|
|
162
171
|
{:else}
|
|
@@ -202,37 +211,39 @@
|
|
|
202
211
|
{#if isActive}
|
|
203
212
|
<div class="px-3 pb-3 space-y-2">
|
|
204
213
|
<!-- Actions for root -->
|
|
205
|
-
|
|
206
|
-
<
|
|
207
|
-
class="btn btn-ghost btn-xs gap-1 {thread.root.resolved ? 'text-warning' : 'text-success'}"
|
|
208
|
-
onclick={() => toggleResolve(thread.root)}
|
|
209
|
-
>
|
|
210
|
-
{#if thread.root.resolved}
|
|
211
|
-
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
212
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
213
|
-
</svg>
|
|
214
|
-
Reopen
|
|
215
|
-
{:else}
|
|
216
|
-
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
217
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
218
|
-
</svg>
|
|
219
|
-
Resolve
|
|
220
|
-
{/if}
|
|
221
|
-
</button>
|
|
222
|
-
{#if thread.root.user_id === currentUserId}
|
|
223
|
-
<button
|
|
224
|
-
class="btn btn-ghost btn-xs text-base-content/50"
|
|
225
|
-
onclick={() => startEdit(thread.root)}
|
|
226
|
-
>Edit</button>
|
|
214
|
+
{#if !readOnly}
|
|
215
|
+
<div class="flex items-center gap-1 ml-8">
|
|
227
216
|
<button
|
|
228
|
-
class="btn btn-ghost btn-xs text-
|
|
229
|
-
onclick={() =>
|
|
230
|
-
>
|
|
231
|
-
|
|
232
|
-
|
|
217
|
+
class="btn btn-ghost btn-xs gap-1 {thread.root.resolved ? 'text-warning' : 'text-success'}"
|
|
218
|
+
onclick={() => toggleResolve(thread.root)}
|
|
219
|
+
>
|
|
220
|
+
{#if thread.root.resolved}
|
|
221
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
222
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
223
|
+
</svg>
|
|
224
|
+
Reopen
|
|
225
|
+
{:else}
|
|
226
|
+
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
227
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
228
|
+
</svg>
|
|
229
|
+
Resolve
|
|
230
|
+
{/if}
|
|
231
|
+
</button>
|
|
232
|
+
{#if thread.root.user_id === currentUserId}
|
|
233
|
+
<button
|
|
234
|
+
class="btn btn-ghost btn-xs text-base-content/50"
|
|
235
|
+
onclick={() => startEdit(thread.root)}
|
|
236
|
+
>Edit</button>
|
|
237
|
+
<button
|
|
238
|
+
class="btn btn-ghost btn-xs text-error/70"
|
|
239
|
+
onclick={() => deleteAnnotation(thread.root.id)}
|
|
240
|
+
>Delete</button>
|
|
241
|
+
{/if}
|
|
242
|
+
</div>
|
|
243
|
+
{/if}
|
|
233
244
|
|
|
234
245
|
<!-- Edit form for root -->
|
|
235
|
-
{#if editingId === thread.root.id}
|
|
246
|
+
{#if !readOnly && editingId === thread.root.id}
|
|
236
247
|
<div class="ml-8 space-y-1.5">
|
|
237
248
|
<textarea
|
|
238
249
|
class="textarea textarea-bordered textarea-sm w-full"
|
|
@@ -257,7 +268,7 @@
|
|
|
257
268
|
<span class="text-xs font-medium">{getAuthorName(reply.user_id)}</span>
|
|
258
269
|
<span class="text-[10px] text-base-content/40">{formatTime(reply.created_at)}</span>
|
|
259
270
|
</div>
|
|
260
|
-
{#if editingId === reply.id}
|
|
271
|
+
{#if !readOnly && editingId === reply.id}
|
|
261
272
|
<div class="mt-1 space-y-1.5">
|
|
262
273
|
<textarea
|
|
263
274
|
class="textarea textarea-bordered textarea-xs w-full"
|
|
@@ -271,7 +282,7 @@
|
|
|
271
282
|
</div>
|
|
272
283
|
{:else}
|
|
273
284
|
<p class="text-sm text-base-content/80 mt-0.5">{reply.content}</p>
|
|
274
|
-
{#if reply.user_id === currentUserId}
|
|
285
|
+
{#if !readOnly && reply.user_id === currentUserId}
|
|
275
286
|
<div class="flex gap-1 mt-1">
|
|
276
287
|
<button class="btn btn-ghost btn-xs text-base-content/40 h-5 min-h-0" onclick={() => startEdit(reply)}>Edit</button>
|
|
277
288
|
<button class="btn btn-ghost btn-xs text-error/50 h-5 min-h-0" onclick={() => deleteAnnotation(reply.id)}>Delete</button>
|
|
@@ -283,33 +294,35 @@
|
|
|
283
294
|
{/each}
|
|
284
295
|
|
|
285
296
|
<!-- Reply input -->
|
|
286
|
-
|
|
287
|
-
<
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
e.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
297
|
+
{#if !readOnly}
|
|
298
|
+
<div class="ml-8 flex gap-2">
|
|
299
|
+
<textarea
|
|
300
|
+
class="textarea textarea-bordered textarea-sm flex-1 min-h-[36px]"
|
|
301
|
+
rows="1"
|
|
302
|
+
placeholder="Reply..."
|
|
303
|
+
bind:value={replyContent}
|
|
304
|
+
onkeydown={(e) => {
|
|
305
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
306
|
+
e.preventDefault()
|
|
307
|
+
submitReply(thread.root.id)
|
|
308
|
+
}
|
|
309
|
+
}}
|
|
310
|
+
></textarea>
|
|
311
|
+
<button
|
|
312
|
+
class="btn btn-sm btn-primary self-end"
|
|
313
|
+
disabled={!replyContent.trim() || isSubmitting}
|
|
314
|
+
onclick={() => submitReply(thread.root.id)}
|
|
315
|
+
>
|
|
316
|
+
{#if isSubmitting}
|
|
317
|
+
<span class="loading loading-spinner loading-xs"></span>
|
|
318
|
+
{:else}
|
|
319
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
320
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
|
321
|
+
</svg>
|
|
322
|
+
{/if}
|
|
323
|
+
</button>
|
|
324
|
+
</div>
|
|
325
|
+
{/if}
|
|
313
326
|
</div>
|
|
314
327
|
{/if}
|
|
315
328
|
</div>
|
|
@@ -5,6 +5,12 @@ interface Props {
|
|
|
5
5
|
activeAnnotationId?: string | null;
|
|
6
6
|
currentUserId: string;
|
|
7
7
|
callbacks: AnnotationCallbacks;
|
|
8
|
+
/** Hides reply/resolve/edit/delete affordances — thread list stays readable */
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
/** Panel heading. Defaults to "Annotations"; pass e.g. "Comments" to match host vocabulary. */
|
|
11
|
+
title?: string;
|
|
12
|
+
/** Empty-state line. Defaults to "No annotations yet". */
|
|
13
|
+
emptyText?: string;
|
|
8
14
|
onAnnotationSelect?: (annotation: Annotation | null) => void;
|
|
9
15
|
onAnnotationsChanged?: () => void;
|
|
10
16
|
}
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
activeAnnotationId?: string | null
|
|
18
18
|
/** Whether annotation placement mode is active */
|
|
19
19
|
annotationMode?: boolean
|
|
20
|
+
/** Whether resolved annotation pins are shown on the image (default false, matching AnnotationLayer) */
|
|
21
|
+
showResolved?: boolean
|
|
20
22
|
/** Called when a pin is clicked */
|
|
21
23
|
onAnnotationClick?: (annotation: Annotation) => void
|
|
22
24
|
/** Called when the image is clicked in annotation mode */
|
|
@@ -32,6 +34,7 @@
|
|
|
32
34
|
annotations = [],
|
|
33
35
|
activeAnnotationId = null,
|
|
34
36
|
annotationMode = false,
|
|
37
|
+
showResolved = false,
|
|
35
38
|
onAnnotationClick,
|
|
36
39
|
onImageClick,
|
|
37
40
|
onClose,
|
|
@@ -168,6 +171,7 @@
|
|
|
168
171
|
{annotations}
|
|
169
172
|
{activeAnnotationId}
|
|
170
173
|
{annotationMode}
|
|
174
|
+
{showResolved}
|
|
171
175
|
{onAnnotationClick}
|
|
172
176
|
{onImageClick}
|
|
173
177
|
/>
|
|
@@ -265,6 +269,7 @@
|
|
|
265
269
|
{annotations}
|
|
266
270
|
{activeAnnotationId}
|
|
267
271
|
annotationMode={false}
|
|
272
|
+
{showResolved}
|
|
268
273
|
{onAnnotationClick}
|
|
269
274
|
/>
|
|
270
275
|
{/if}
|
|
@@ -13,6 +13,8 @@ interface Props {
|
|
|
13
13
|
activeAnnotationId?: string | null;
|
|
14
14
|
/** Whether annotation placement mode is active */
|
|
15
15
|
annotationMode?: boolean;
|
|
16
|
+
/** Whether resolved annotation pins are shown on the image (default false, matching AnnotationLayer) */
|
|
17
|
+
showResolved?: boolean;
|
|
16
18
|
/** Called when a pin is clicked */
|
|
17
19
|
onAnnotationClick?: (annotation: Annotation) => void;
|
|
18
20
|
/** Called when the image is clicked in annotation mode */
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
disabled = false,
|
|
26
26
|
displayValue,
|
|
27
27
|
colorFn,
|
|
28
|
+
header,
|
|
28
29
|
footer,
|
|
29
30
|
variant = 'default',
|
|
30
31
|
size = 'md',
|
|
@@ -41,6 +42,8 @@
|
|
|
41
42
|
disabled?: boolean;
|
|
42
43
|
displayValue?: string;
|
|
43
44
|
colorFn?: (value: string) => string | undefined;
|
|
45
|
+
/** Rendered above the search input — use for panel titles, descriptions, or controls */
|
|
46
|
+
header?: Snippet;
|
|
44
47
|
footer?: Snippet;
|
|
45
48
|
/** 'chip' renders a compact TopBar-style pill instead of the full-width trigger */
|
|
46
49
|
variant?: 'default' | 'chip';
|
|
@@ -197,6 +200,11 @@
|
|
|
197
200
|
</script>
|
|
198
201
|
|
|
199
202
|
{#snippet panelBody()}
|
|
203
|
+
{#if header}
|
|
204
|
+
<div class="sd-header">
|
|
205
|
+
{@render header()}
|
|
206
|
+
</div>
|
|
207
|
+
{/if}
|
|
200
208
|
<!-- Search input -->
|
|
201
209
|
<div class="sd-search">
|
|
202
210
|
<div class="sd-search-inner">
|
|
@@ -525,6 +533,11 @@
|
|
|
525
533
|
box-shadow: 0 -4px 24px oklch(0 0 0 / 0.4);
|
|
526
534
|
}
|
|
527
535
|
|
|
536
|
+
/* Header section */
|
|
537
|
+
.sd-header {
|
|
538
|
+
border-bottom: 1px solid oklch(0.25 0.02 250);
|
|
539
|
+
}
|
|
540
|
+
|
|
528
541
|
/* Search section */
|
|
529
542
|
.sd-search {
|
|
530
543
|
padding: 0.375rem 0.5rem;
|
|
@@ -16,6 +16,8 @@ type $$ComponentProps = {
|
|
|
16
16
|
disabled?: boolean;
|
|
17
17
|
displayValue?: string;
|
|
18
18
|
colorFn?: (value: string) => string | undefined;
|
|
19
|
+
/** Rendered above the search input — use for panel titles, descriptions, or controls */
|
|
20
|
+
header?: Snippet;
|
|
19
21
|
footer?: Snippet;
|
|
20
22
|
/** 'chip' renders a compact TopBar-style pill instead of the full-width trigger */
|
|
21
23
|
variant?: 'default' | 'chip';
|
package/dist/dnd/types.d.ts
CHANGED
|
@@ -2,16 +2,7 @@ import type { Input } from '@atlaskit/pragmatic-drag-and-drop/types';
|
|
|
2
2
|
export interface DragData extends Record<string | symbol, unknown> {
|
|
3
3
|
readonly type: string;
|
|
4
4
|
}
|
|
5
|
-
/**
|
|
6
|
-
* Module-scoped Symbol brand for board-managed items.
|
|
7
|
-
* ⚠ Symbol identity = module identity. Two copies of this module on one page makes
|
|
8
|
-
* isItemData() return false across the boundary — dnd/ must exist EXACTLY ONCE.
|
|
9
|
-
* Declare all @atlaskit/pragmatic-drag-and-drop* packages as peerDependencies in
|
|
10
|
-
* the host app (host owns the single instance).
|
|
11
|
-
*/
|
|
12
|
-
declare const ITEM: unique symbol;
|
|
13
5
|
export interface ItemDragData<TItem = unknown> extends DragData {
|
|
14
|
-
readonly [ITEM]: true;
|
|
15
6
|
readonly key: string;
|
|
16
7
|
readonly item: TItem;
|
|
17
8
|
readonly container: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joewinke/jatui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Svelte 5 component library for JAT projects",
|
|
6
6
|
"type": "module",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"package": "svelte-package --input src/lib --output dist && node scripts/compile-rune-modules.mjs",
|
|
71
71
|
"check:demos": "node scripts/check-demo-coverage.mjs",
|
|
72
72
|
"guard": "node scripts/check-demo-coverage.mjs",
|
|
73
|
-
"prepublishOnly": "npm run package"
|
|
73
|
+
"prepublishOnly": "node scripts/guard-ci-publish.mjs && npm run package"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
76
|
"@atlaskit/pragmatic-drag-and-drop": "~2.0.0",
|