@chai-ui/core 0.6.1 → 0.7.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.
Files changed (42) hide show
  1. package/README.md +3 -2
  2. package/dist/composer-view.d.ts +93 -0
  3. package/dist/composer-view.d.ts.map +1 -0
  4. package/dist/composer-view.js +142 -0
  5. package/dist/composer-view.js.map +1 -0
  6. package/dist/edit-card.d.ts +53 -0
  7. package/dist/edit-card.d.ts.map +1 -0
  8. package/dist/edit-card.js +119 -0
  9. package/dist/edit-card.js.map +1 -0
  10. package/dist/index.d.ts +4 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/media-actions.d.ts +19 -0
  15. package/dist/media-actions.d.ts.map +1 -0
  16. package/dist/media-actions.js +57 -0
  17. package/dist/media-actions.js.map +1 -0
  18. package/dist/media-analyzer.d.ts +47 -1
  19. package/dist/media-analyzer.d.ts.map +1 -1
  20. package/dist/media-analyzer.js +91 -0
  21. package/dist/media-analyzer.js.map +1 -1
  22. package/dist/region-edit.d.ts +2 -0
  23. package/dist/region-edit.d.ts.map +1 -1
  24. package/dist/region-edit.js +6 -0
  25. package/dist/region-edit.js.map +1 -1
  26. package/dist/result-format.d.ts +11 -0
  27. package/dist/result-format.d.ts.map +1 -0
  28. package/dist/result-format.js +30 -0
  29. package/dist/result-format.js.map +1 -0
  30. package/dist/unique-id.d.ts +3 -0
  31. package/dist/unique-id.d.ts.map +1 -0
  32. package/dist/unique-id.js +7 -0
  33. package/dist/unique-id.js.map +1 -0
  34. package/package.json +2 -1
  35. package/src/composer-view.ts +222 -0
  36. package/src/edit-card.ts +138 -0
  37. package/src/index.ts +4 -0
  38. package/src/media-actions.ts +55 -0
  39. package/src/media-analyzer.ts +112 -1
  40. package/src/region-edit.ts +8 -0
  41. package/src/result-format.ts +34 -0
  42. package/src/unique-id.ts +6 -0
package/README.md CHANGED
@@ -77,8 +77,8 @@ Set `FAL_KEY` and `OPENROUTER_API_KEY` in the server's environment (never
77
77
  with a `VITE_` or `NEXT_PUBLIC_` prefix). A missing key comes back as a
78
78
  clear error, e.g. "FAL_KEY isn't set on the server."
79
79
 
80
- **Where it runs.** Chai's components are React-only, so the route lives in
81
- your React app's framework or on the API server behind it. Mount it at
80
+ **Where it runs.** The route lives in your app's framework (React or Vue)
81
+ or on the API server behind it. Mount it at
82
82
  `/api/chai`, where the components send their requests. `handle(request)`
83
83
  takes a standard web `Request` and returns a `Response`; `GET`, `POST` and
84
84
  `PUT` (Fal's cancel call) are the same function. When the browser
@@ -88,6 +88,7 @@ disconnects, the provider call is aborted too.
88
88
  |---|---|
89
89
  | Next.js (App Router) | `export const { GET, POST, PUT } = createChaiHandler();` in `app/api/chai/[...path]/route.ts` |
90
90
  | Remix / React Router | `export const loader = ({ request }) => chai.handle(request); export const action = loader;` in `app/routes/api.chai.$.ts` |
91
+ | Nuxt | `export default defineEventHandler((event) => chai.handle(toWebRequest(event)));` in `server/api/chai/[...path].ts` |
91
92
  | Hono | `app.all("/api/chai/*", (c) => chai.handle(c.req.raw))` |
92
93
  | Bun, Deno | route `/api/chai/*` to `chai.handle` in `Bun.serve` / `Deno.serve` |
93
94
  | Cloudflare Workers | `createChaiHandler({ falKey: env.FAL_KEY, openRouterKey: env.OPENROUTER_API_KEY }).handle(request)` |
@@ -0,0 +1,93 @@
1
+ /**
2
+ * What the Composer component shows and submits, worked out from its props:
3
+ * the same in every framework binding. Pure; nothing here renders.
4
+ */
5
+ import type { ComposerAttachment, ComposerSubmitPayload, ComposerUseCase } from "./composer.js";
6
+ import type { EditRegion } from "./region-edit.js";
7
+ import type { MediaKind, ModelOption, ParameterOption } from "./types.js";
8
+ export type ComposerAttachMenuAction = "add-media" | "create-image" | "create-video" | "edit-media";
9
+ /** The `+` menu, in order. "Add media" is the only item with a divider after it. */
10
+ export declare const COMPOSER_ATTACH_MENU_ITEMS: {
11
+ action: ComposerAttachMenuAction;
12
+ label: string;
13
+ divider?: boolean;
14
+ }[];
15
+ /**
16
+ * Past this length a rewritten prompt appears at once: a long reveal reads
17
+ * as sluggish, and a long prompt is when a person wants the result now.
18
+ */
19
+ export declare const MAX_ANIMATE_ENHANCE_LENGTH = 260;
20
+ /** How long the Enhance reveal takes: time-driven, between 300ms and 900ms. */
21
+ export declare function enhanceRevealDurationMs(charCount: number): number;
22
+ /** The Composer props `composerView` reads. Callbacks only matter by being there. */
23
+ export interface ComposerViewInput {
24
+ value: string;
25
+ placeholder?: string;
26
+ attachments?: ComposerAttachment[];
27
+ useCase?: ComposerUseCase | null;
28
+ defaultUseCase?: ComposerUseCase;
29
+ multiSelectUseCases?: boolean;
30
+ useCases?: ComposerUseCase[];
31
+ modelsByKind?: Partial<Record<MediaKind, ModelOption[]>>;
32
+ multiSelectModels?: boolean;
33
+ modelIds?: string[];
34
+ models?: ModelOption[];
35
+ modelId?: string | null;
36
+ onModelChange?: unknown;
37
+ onModelIdsChange?: unknown;
38
+ autoSelectModel?: boolean;
39
+ aspectRatios?: ParameterOption<string>[];
40
+ aspectRatio?: string | null;
41
+ submitError?: string | null;
42
+ submitDisabled?: boolean;
43
+ submitting?: boolean;
44
+ onAbort?: unknown;
45
+ regions?: EditRegion[];
46
+ disabled?: boolean;
47
+ }
48
+ export declare function composerView(props: ComposerViewInput): {
49
+ pickedUseCases: ComposerUseCase[];
50
+ activeUseCases: ComposerUseCase[];
51
+ editing: boolean;
52
+ isSubmitDisabled: boolean;
53
+ placeholder: string;
54
+ showStop: boolean;
55
+ multiModel: boolean;
56
+ modelSections: {
57
+ options: ModelOption[];
58
+ suggested: boolean;
59
+ label: string;
60
+ kind: MediaKind;
61
+ }[];
62
+ pickedModelIds: string[];
63
+ canPickModel: boolean;
64
+ modelTriggerLabel: string;
65
+ showAspectRatio: boolean;
66
+ aspectOptions: ParameterOption<string>[];
67
+ offeredAspect: string | null;
68
+ selectedAspect: ParameterOption<string> | null;
69
+ /** The model ids after a row click: the one picked, or with multi-select that one toggled. */
70
+ toggledModelIds: (id: string) => string[];
71
+ /** Everything a submit sends. */
72
+ payload: () => ComposerSubmitPayload;
73
+ };
74
+ export type ComposerView = ReturnType<typeof composerView>;
75
+ /** The `+` menu items that can work: "Add media" needs somewhere to put files, the use-case items a handler. */
76
+ export declare function composerAttachMenuItems(actions: ComposerAttachMenuAction[] | undefined, { canAddMedia, canSelect }: {
77
+ canAddMedia: boolean;
78
+ canSelect: boolean;
79
+ }): {
80
+ action: ComposerAttachMenuAction;
81
+ label: string;
82
+ divider?: boolean;
83
+ }[];
84
+ /**
85
+ * The attachments after picking files: each image, video or audio file
86
+ * added in the order picked. Editing works on one image, so its first image
87
+ * replaces what's there (and with none, every file is unsupported).
88
+ */
89
+ export declare function composerAttachmentsFromFiles(files: Iterable<File>, current: ComposerAttachment[], editing: boolean): Promise<{
90
+ attachments: ComposerAttachment[] | null;
91
+ unsupported: string[];
92
+ }>;
93
+ //# sourceMappingURL=composer-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer-view.d.ts","sourceRoot":"","sources":["../src/composer-view.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,142 @@
1
+ import { uniqueId } from "./unique-id.js";
2
+ import { TEXT_USE_CASE } from "./composer.js";
3
+ import { readMediaFiles } from "./media-analyzer.js";
4
+ import { deriveGenerationUseCase } from "./run.js";
5
+ import { suggestedModels } from "./suggested-models.js";
6
+ /** The `+` menu, in order. "Add media" is the only item with a divider after it. */
7
+ export const COMPOSER_ATTACH_MENU_ITEMS = [
8
+ { action: "add-media", label: "Add media", divider: true },
9
+ { action: "create-image", label: "Create image" },
10
+ { action: "create-video", label: "Create video" },
11
+ { action: "edit-media", label: "Edit image" },
12
+ ];
13
+ /**
14
+ * Past this length a rewritten prompt appears at once: a long reveal reads
15
+ * as sluggish, and a long prompt is when a person wants the result now.
16
+ */
17
+ export const MAX_ANIMATE_ENHANCE_LENGTH = 260;
18
+ /** How long the Enhance reveal takes: time-driven, between 300ms and 900ms. */
19
+ export function enhanceRevealDurationMs(charCount) {
20
+ return Math.min(900, Math.max(300, charCount * 6));
21
+ }
22
+ export function composerView(props) {
23
+ const { value, attachments = [], useCase = null, defaultUseCase = TEXT_USE_CASE, multiSelectUseCases = false, useCases = [], modelsByKind, multiSelectModels = false, modelIds = [], models, modelId = null, autoSelectModel = false, aspectRatios, aspectRatio = null, submitError = null, submitting = false, regions = [], disabled = false, } = props;
24
+ // What the person picked, and what actually runs: with nothing picked, the
25
+ // builder's default use case (a text request unless they chose otherwise).
26
+ const pickedUseCases = multiSelectUseCases ? useCases : useCase ? [useCase] : [];
27
+ const activeUseCases = pickedUseCases.length > 0 ? pickedUseCases : [defaultUseCase];
28
+ // An edit needs an image, then a change: a prompt for the whole image, a
29
+ // region with its own instruction, or both.
30
+ const editing = activeUseCases.some((u) => u.edit);
31
+ const hasImage = attachments.some((a) => a.kind === "image");
32
+ const hasContent = editing
33
+ ? hasImage && (value.trim().length > 0 || regions.length > 0)
34
+ : value.trim().length > 0 || attachments.length > 0;
35
+ const isSubmitDisabled = submitError ? disabled : (props.submitDisabled ?? (disabled || !hasContent));
36
+ const placeholder = props.placeholder ??
37
+ (editing
38
+ ? hasImage
39
+ ? "Describe a change across the whole image, or leave this empty"
40
+ : "Attach an image to edit"
41
+ : "Describe media to create");
42
+ // The stop icon only appears with somewhere for its click to go.
43
+ const showStop = submitting && Boolean(props.onAbort);
44
+ const multiModel = multiSelectModels || multiSelectUseCases;
45
+ const rawPickedIds = multiModel ? modelIds : modelId ? [modelId] : [];
46
+ // The app's list for a use case, or Chai's suggested models when it gave none.
47
+ const rosterFor = (u) => {
48
+ const given = multiSelectUseCases ? modelsByKind?.[u.kind] : models;
49
+ if (given)
50
+ return { options: given, suggested: false };
51
+ return { options: suggestedModels(u.edit ? "image-edit" : deriveGenerationUseCase(u, attachments)), suggested: true };
52
+ };
53
+ // One menu section per use case with multi-use-case select; otherwise one flat roster.
54
+ const modelSections = (multiSelectUseCases ? activeUseCases : activeUseCases.slice(0, 1))
55
+ .map((u) => ({ label: multiSelectUseCases ? u.label : "", kind: u.kind, ...rosterFor(u) }))
56
+ .filter((s) => s.options.length > 0);
57
+ const allModels = modelSections.flatMap((s) => s.options);
58
+ // A pick that isn't in the current list (e.g. the edit model after leaving
59
+ // edit mode) is shown as unpicked and never submitted. With no list at
60
+ // all, the app's pick is passed through as given. With nothing picked
61
+ // from a suggested list (and auto-select off), its first model is the pick.
62
+ const validPicks = allModels.length > 0 ? rawPickedIds.filter((id) => allModels.some((m) => m.id === id)) : rawPickedIds;
63
+ const pickedModelIds = validPicks.length > 0 || autoSelectModel ? validPicks : modelSections.filter((s) => s.suggested).map((s) => s.options[0].id);
64
+ const pickedModels = allModels.filter((m) => pickedModelIds.includes(m.id));
65
+ // No menu that can't change anything: without its handler, the Model menu hides (the default pick still runs).
66
+ const canPickModel = Boolean(multiModel ? props.onModelIdsChange : props.onModelChange);
67
+ const modelTriggerLabel = pickedModels.length > 1
68
+ ? `${pickedModels.length} models`
69
+ : (pickedModels[0]?.label ?? (autoSelectModel ? "Auto-select" : "Select models"));
70
+ // An edit keeps the source image's shape.
71
+ const showAspectRatio = activeUseCases.some((u) => !u.edit && (u.kind === "image" || u.kind === "video"));
72
+ // Only ratios every model that might run takes: the picked ones, or with
73
+ // none picked (or auto-select on) the whole list, so whichever runs can
74
+ // honor it. A model with no `aspectRatios` sets its own shape: no menu.
75
+ const ratioModels = pickedModels.length > 0 && !autoSelectModel ? pickedModels : allModels;
76
+ const sharedRatios = ratioModels.reduce((shared, m) => (shared ?? m.aspectRatios ?? []).filter((r) => m.aspectRatios?.includes(r)), null) ??
77
+ [];
78
+ const aspectOptions = aspectRatios
79
+ ? aspectRatios.filter((o) => sharedRatios.includes(o.value))
80
+ : sharedRatios.map((r) => ({ value: r, label: r }));
81
+ // A pick the current models don't take is shown as unpicked and never sent.
82
+ const offeredAspect = showAspectRatio && aspectOptions.some((o) => o.value === aspectRatio) ? aspectRatio : null;
83
+ const selectedAspect = aspectOptions.find((o) => o.value === offeredAspect) ?? null;
84
+ const selections = () => activeUseCases.map((u) => ({
85
+ useCase: u,
86
+ models: rosterFor(u).options,
87
+ modelIds: multiSelectUseCases ? pickedModelIds.filter((id) => rosterFor(u).options.some((m) => m.id === id)) : pickedModelIds,
88
+ }));
89
+ return {
90
+ pickedUseCases,
91
+ activeUseCases,
92
+ editing,
93
+ isSubmitDisabled,
94
+ placeholder,
95
+ showStop,
96
+ multiModel,
97
+ modelSections,
98
+ pickedModelIds,
99
+ canPickModel,
100
+ modelTriggerLabel,
101
+ showAspectRatio,
102
+ aspectOptions,
103
+ offeredAspect,
104
+ selectedAspect,
105
+ /** The model ids after a row click: the one picked, or with multi-select that one toggled. */
106
+ toggledModelIds: (id) => (modelIds.includes(id) ? modelIds.filter((m) => m !== id) : [...modelIds, id]),
107
+ /** Everything a submit sends. */
108
+ payload: () => ({
109
+ value,
110
+ attachments,
111
+ useCase: activeUseCases[0],
112
+ modelId: pickedModelIds[0] ?? null,
113
+ aspectRatio: offeredAspect,
114
+ selections: selections(),
115
+ autoSelectModel,
116
+ regions: editing ? regions : [],
117
+ }),
118
+ };
119
+ }
120
+ /** The `+` menu items that can work: "Add media" needs somewhere to put files, the use-case items a handler. */
121
+ export function composerAttachMenuItems(actions, { canAddMedia, canSelect }) {
122
+ return COMPOSER_ATTACH_MENU_ITEMS.filter((item) => (actions ?? COMPOSER_ATTACH_MENU_ITEMS.map((i) => i.action)).includes(item.action) &&
123
+ (item.action === "add-media" ? canAddMedia : canSelect));
124
+ }
125
+ /**
126
+ * The attachments after picking files: each image, video or audio file
127
+ * added in the order picked. Editing works on one image, so its first image
128
+ * replaces what's there (and with none, every file is unsupported).
129
+ */
130
+ export async function composerAttachmentsFromFiles(files, current, editing) {
131
+ const list = [...files];
132
+ if (editing) {
133
+ const image = list.find((f) => f.type.startsWith("image/"));
134
+ if (!image)
135
+ return { attachments: null, unsupported: list.map((f) => f.name) };
136
+ const { media, unsupported } = await readMediaFiles([image]);
137
+ return { attachments: media.length > 0 ? media.map((m) => ({ ...m, id: uniqueId() })) : null, unsupported };
138
+ }
139
+ const { media, unsupported } = await readMediaFiles(list);
140
+ return { attachments: [...current, ...media.map((m) => ({ ...m, id: uniqueId() }))], unsupported };
141
+ }
142
+ //# sourceMappingURL=composer-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"composer-view.js","sourceRoot":"","sources":["../src/composer-view.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,uBAAuB,EAAE,MAAM,UAAU,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAKxD,oFAAoF;AACpF,MAAM,CAAC,MAAM,0BAA0B,GAA6E;IAClH,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE;IAC1D,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,EAAE;IACjD,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,cAAc,EAAE;IACjD,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE;CAC9C,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C,+EAA+E;AAC/E,MAAM,UAAU,uBAAuB,CAAC,SAAiB;IACvD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC;AA6BD,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EACJ,KAAK,EACL,WAAW,GAAG,EAAE,EAChB,OAAO,GAAG,IAAI,EACd,cAAc,GAAG,aAAa,EAC9B,mBAAmB,GAAG,KAAK,EAC3B,QAAQ,GAAG,EAAE,EACb,YAAY,EACZ,iBAAiB,GAAG,KAAK,EACzB,QAAQ,GAAG,EAAE,EACb,MAAM,EACN,OAAO,GAAG,IAAI,EACd,eAAe,GAAG,KAAK,EACvB,YAAY,EACZ,WAAW,GAAG,IAAI,EAClB,WAAW,GAAG,IAAI,EAClB,UAAU,GAAG,KAAK,EAClB,OAAO,GAAG,EAAE,EACZ,QAAQ,GAAG,KAAK,GACjB,GAAG,KAAK,CAAC;IAEV,2EAA2E;IAC3E,2EAA2E;IAC3E,MAAM,cAAc,GAAG,mBAAmB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,MAAM,cAAc,GAAG,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACrF,yEAAyE;IACzE,4CAA4C;IAC5C,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;IAC7D,MAAM,UAAU,GAAG,OAAO;QACxB,CAAC,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QAC7D,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;IACtD,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IACtG,MAAM,WAAW,GACf,KAAK,CAAC,WAAW;QACjB,CAAC,OAAO;YACN,CAAC,CAAC,QAAQ;gBACR,CAAC,CAAC,+DAA+D;gBACjE,CAAC,CAAC,yBAAyB;YAC7B,CAAC,CAAC,0BAA0B,CAAC,CAAC;IAClC,iEAAiE;IACjE,MAAM,QAAQ,GAAG,UAAU,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAEtD,MAAM,UAAU,GAAG,iBAAiB,IAAI,mBAAmB,CAAC;IAC5D,MAAM,YAAY,GAAG,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACtE,+EAA+E;IAC/E,MAAM,SAAS,GAAG,CAAC,CAAkB,EAAkD,EAAE;QACvF,MAAM,KAAK,GAAG,mBAAmB,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;QACpE,IAAI,KAAK;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;QACvD,OAAO,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACxH,CAAC,CAAC;IACF,uFAAuF;IACvF,MAAM,aAAa,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;SACtF,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC1F,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC1D,2EAA2E;IAC3E,uEAAuE;IACvE,sEAAsE;IACtE,4EAA4E;IAC5E,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IACzH,MAAM,cAAc,GAClB,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,eAAe,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC;IAChI,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5E,+GAA+G;IAC/G,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IACxF,MAAM,iBAAiB,GACrB,YAAY,CAAC,MAAM,GAAG,CAAC;QACrB,CAAC,CAAC,GAAG,YAAY,CAAC,MAAM,SAAS;QACjC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtF,0CAA0C;IAC1C,MAAM,eAAe,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC;IAC1G,yEAAyE;IACzE,wEAAwE;IACxE,wEAAwE;IACxE,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3F,MAAM,YAAY,GAChB,WAAW,CAAC,MAAM,CAAkB,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC;QACrI,EAAE,CAAC;IACL,MAAM,aAAa,GAAG,YAAY;QAChC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC5D,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACtD,4EAA4E;IAC5E,MAAM,aAAa,GAAG,eAAe,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;IACjH,MAAM,cAAc,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,aAAa,CAAC,IAAI,IAAI,CAAC;IAEpF,MAAM,UAAU,GAAG,GAAwB,EAAE,CAC3C,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzB,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO;QAC5B,QAAQ,EAAE,mBAAmB,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc;KAC9H,CAAC,CAAC,CAAC;IAEN,OAAO;QACL,cAAc;QACd,cAAc;QACd,OAAO;QACP,gBAAgB;QAChB,WAAW;QACX,QAAQ;QACR,UAAU;QACV,aAAa;QACb,cAAc;QACd,YAAY;QACZ,iBAAiB;QACjB,eAAe;QACf,aAAa;QACb,aAAa;QACb,cAAc;QACd,8FAA8F;QAC9F,eAAe,EAAE,CAAC,EAAU,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC/G,iCAAiC;QACjC,OAAO,EAAE,GAA0B,EAAE,CAAC,CAAC;YACrC,KAAK;YACL,WAAW;YACX,OAAO,EAAE,cAAc,CAAC,CAAC,CAAE;YAC3B,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,IAAI,IAAI;YAClC,WAAW,EAAE,aAAa;YAC1B,UAAU,EAAE,UAAU,EAAE;YACxB,eAAe;YACf,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;SAChC,CAAC;KACH,CAAC;AACJ,CAAC;AAID,gHAAgH;AAChH,MAAM,UAAU,uBAAuB,CACrC,OAA+C,EAC/C,EAAE,WAAW,EAAE,SAAS,EAAgD;IAExE,OAAO,0BAA0B,CAAC,MAAM,CACtC,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,OAAO,IAAI,0BAA0B,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC;QAClF,CAAC,IAAI,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAC1D,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAqB,EACrB,OAA6B,EAC7B,OAAgB;IAEhB,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IACxB,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/E,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,cAAc,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7D,OAAO,EAAE,WAAW,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC;IAC9G,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC1D,OAAO,EAAE,WAAW,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;AACrG,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { type EditRegion, type RegionBox } from "./region-edit.js";
2
+ export declare const EDIT_ZOOM_STEPS: number[];
3
+ export type RegionCorner = "nw" | "ne" | "sw" | "se";
4
+ export declare const REGION_CORNERS: RegionCorner[];
5
+ export interface Size {
6
+ width: number;
7
+ height: number;
8
+ }
9
+ export interface Pan {
10
+ x: number;
11
+ y: number;
12
+ }
13
+ /** Keeps a box inside the image and at least the minimum size. */
14
+ export declare function fitBox(box: RegionBox): RegionBox;
15
+ /** A box resized by dragging one corner; the opposite corner stays put. */
16
+ export declare function resizeBox(box: RegionBox, corner: RegionCorner, dx: number, dy: number): RegionBox;
17
+ /** A region's box after a key: arrows move it (Shift resizes, Alt takes bigger steps). `null` for any other key. */
18
+ export declare function regionBoxForKey(box: RegionBox, key: string, { shift, alt }?: {
19
+ shift?: boolean | undefined;
20
+ alt?: boolean | undefined;
21
+ }): RegionBox | null;
22
+ /** Pan limits at a zoom level: the image always covers the viewport. */
23
+ export declare function clampPan(pan: Pan, zoom: number, size: Size): Pan;
24
+ /** The pan that zooms around the middle of the view, so what's centered stays centered. */
25
+ export declare function panForZoom(pan: Pan, zoom: number, next: number, size: Size): Pan;
26
+ /** The pan after an arrow key on the zoomed image, or `null` for any other key. */
27
+ export declare function panForKey(pan: Pan, key: string, zoom: number, size: Size): Pan | null;
28
+ /** The lowest number no current region uses, so freed numbers (and colors) come back first. */
29
+ export declare function freeRegionNumber(regions: EditRegion[]): number;
30
+ /** A new region, centered in the part of the image in view, so it's visible when zoomed in. */
31
+ export declare function newRegion(regions: EditRegion[], zoom: number, pan: Pan, size: Size): EditRegion;
32
+ /**
33
+ * Where the instruction field goes: above its region, or below it when
34
+ * there isn't room above and it clears the controls along the bottom edge.
35
+ */
36
+ export declare function regionPopoverPosition(box: RegionBox, zoom: number, pan: Pan, size: Size, paginated: boolean): {
37
+ left: number;
38
+ top: number;
39
+ above: boolean;
40
+ };
41
+ /**
42
+ * The card's width: from `scale` (of the image's own width), else `width`,
43
+ * else `maxWidth`, then fitted inside `maxWidth` × `maxHeight` keeping the image's shape.
44
+ */
45
+ export declare function editCardWidth(natural: Size | null, { width, scale, maxWidth, maxHeight }: {
46
+ width?: number;
47
+ scale?: number;
48
+ maxWidth: number;
49
+ maxHeight: number;
50
+ }): number;
51
+ /** A region's place on the image, as percentages, and its color. */
52
+ export declare function regionBoxStyle(r: EditRegion): Record<string, string>;
53
+ //# sourceMappingURL=edit-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-card.d.ts","sourceRoot":"","sources":["../src/edit-card.ts"],"names":[],"mappings":"AAMA,OAAO,EAAe,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAEhF,eAAO,MAAM,eAAe,UAA0B,CAAC;AAQvD,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACrD,eAAO,MAAM,cAAc,EAAE,YAAY,EAA6B,CAAC;AACvE,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AACD,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAID,kEAAkE;AAClE,wBAAgB,MAAM,CAAC,GAAG,EAAE,SAAS,GAAG,SAAS,CAIhD;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,SAAS,CAUjG;AAED,oHAAoH;AACpH,wBAAgB,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,KAAa,EAAE,GAAW,EAAE;;;CAAK,GAAG,SAAS,GAAG,IAAI,CAMlH;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,CAKhE;AAED,2FAA2F;AAC3F,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,CAKhF;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,CAIrF;AAED,+FAA+F;AAC/F,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAI9D;AAED,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,GAAG,UAAU,CAM/F;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO;;;;EAY3G;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,IAAI,GAAG,IAAI,EACpB,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC7G,MAAM,CAIR;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQpE"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * The EditCard's geometry, the same in every framework binding: region
3
+ * boxes, zoom and pan, where the instruction field goes, and the card's
4
+ * width. Boxes are fractions of the image (0 to 1); pan and sizes are px.
5
+ */
6
+ import { uniqueId } from "./unique-id.js";
7
+ import { regionColor } from "./region-edit.js";
8
+ export const EDIT_ZOOM_STEPS = [1, 1.25, 1.5, 2, 3, 4];
9
+ const MIN_SIZE = 0.03;
10
+ const KEY_STEP = 0.01;
11
+ const KEY_STEP_LARGE = 0.05;
12
+ /** The instruction field's width, and a typical height (title row and a two-line field), for placing it. */
13
+ const POPOVER_WIDTH = 220;
14
+ const POPOVER_HEIGHT = 96;
15
+ export const REGION_CORNERS = ["nw", "ne", "sw", "se"];
16
+ const clamp = (v, min, max) => Math.min(max, Math.max(min, v));
17
+ /** Keeps a box inside the image and at least the minimum size. */
18
+ export function fitBox(box) {
19
+ const width = clamp(box.width, MIN_SIZE, 1);
20
+ const height = clamp(box.height, MIN_SIZE, 1);
21
+ return { x: clamp(box.x, 0, 1 - width), y: clamp(box.y, 0, 1 - height), width, height };
22
+ }
23
+ /** A box resized by dragging one corner; the opposite corner stays put. */
24
+ export function resizeBox(box, corner, dx, dy) {
25
+ let left = box.x;
26
+ let top = box.y;
27
+ let right = box.x + box.width;
28
+ let bottom = box.y + box.height;
29
+ if (corner === "nw" || corner === "sw")
30
+ left = clamp(left + dx, 0, right - MIN_SIZE);
31
+ else
32
+ right = clamp(right + dx, left + MIN_SIZE, 1);
33
+ if (corner === "nw" || corner === "ne")
34
+ top = clamp(top + dy, 0, bottom - MIN_SIZE);
35
+ else
36
+ bottom = clamp(bottom + dy, top + MIN_SIZE, 1);
37
+ return { x: left, y: top, width: right - left, height: bottom - top };
38
+ }
39
+ /** A region's box after a key: arrows move it (Shift resizes, Alt takes bigger steps). `null` for any other key. */
40
+ export function regionBoxForKey(box, key, { shift = false, alt = false } = {}) {
41
+ const step = alt ? KEY_STEP_LARGE : KEY_STEP;
42
+ const delta = { ArrowLeft: [-step, 0], ArrowRight: [step, 0], ArrowUp: [0, -step], ArrowDown: [0, step] }[key];
43
+ if (!delta)
44
+ return null;
45
+ const [dx, dy] = delta;
46
+ return shift ? resizeBox(box, "se", dx, dy) : fitBox({ ...box, x: box.x + dx, y: box.y + dy });
47
+ }
48
+ /** Pan limits at a zoom level: the image always covers the viewport. */
49
+ export function clampPan(pan, zoom, size) {
50
+ return {
51
+ x: clamp(pan.x, size.width - size.width * zoom, 0),
52
+ y: clamp(pan.y, size.height - size.height * zoom, 0),
53
+ };
54
+ }
55
+ /** The pan that zooms around the middle of the view, so what's centered stays centered. */
56
+ export function panForZoom(pan, zoom, next, size) {
57
+ const cx = size.width / 2;
58
+ const cy = size.height / 2;
59
+ const ratio = next / zoom;
60
+ return clampPan({ x: cx - (cx - pan.x) * ratio, y: cy - (cy - pan.y) * ratio }, next, size);
61
+ }
62
+ /** The pan after an arrow key on the zoomed image, or `null` for any other key. */
63
+ export function panForKey(pan, key, zoom, size) {
64
+ const step = 40;
65
+ const delta = { ArrowLeft: [step, 0], ArrowRight: [-step, 0], ArrowUp: [0, step], ArrowDown: [0, -step] }[key];
66
+ return delta ? clampPan({ x: pan.x + delta[0], y: pan.y + delta[1] }, zoom, size) : null;
67
+ }
68
+ /** The lowest number no current region uses, so freed numbers (and colors) come back first. */
69
+ export function freeRegionNumber(regions) {
70
+ let n = 1;
71
+ while (regions.some((r) => r.number === n))
72
+ n += 1;
73
+ return n;
74
+ }
75
+ /** A new region, centered in the part of the image in view, so it's visible when zoomed in. */
76
+ export function newRegion(regions, zoom, pan, size) {
77
+ const view = 1 / zoom;
78
+ const centerX = size.width ? -pan.x / (size.width * zoom) + view / 2 : 0.5;
79
+ const centerY = size.height ? -pan.y / (size.height * zoom) + view / 2 : 0.5;
80
+ const box = fitBox({ x: centerX - (0.3 * view) / 2, y: centerY - (0.2 * view) / 2, width: 0.3 * view, height: 0.2 * view });
81
+ return { id: uniqueId(), number: freeRegionNumber(regions), box, prompt: "" };
82
+ }
83
+ /**
84
+ * Where the instruction field goes: above its region, or below it when
85
+ * there isn't room above and it clears the controls along the bottom edge.
86
+ */
87
+ export function regionPopoverPosition(box, zoom, pan, size, paginated) {
88
+ const left = box.x * size.width * zoom + pan.x;
89
+ const top = box.y * size.height * zoom + pan.y;
90
+ const bottom = (box.y + box.height) * size.height * zoom + pan.y;
91
+ const reserved = paginated ? 100 : 56;
92
+ const fitsBelow = bottom + 8 + POPOVER_HEIGHT <= size.height - reserved;
93
+ const above = top > POPOVER_HEIGHT + 16 || !fitsBelow;
94
+ return {
95
+ left: clamp(left, 8, Math.max(8, size.width - POPOVER_WIDTH - 8)),
96
+ top: above ? Math.max(top - 8, POPOVER_HEIGHT + 8) : bottom + 8,
97
+ above,
98
+ };
99
+ }
100
+ /**
101
+ * The card's width: from `scale` (of the image's own width), else `width`,
102
+ * else `maxWidth`, then fitted inside `maxWidth` × `maxHeight` keeping the image's shape.
103
+ */
104
+ export function editCardWidth(natural, { width, scale, maxWidth, maxHeight }) {
105
+ const base = scale != null && natural ? natural.width * scale : (width ?? maxWidth);
106
+ const heightCap = natural && natural.height > 0 ? (maxHeight * natural.width) / natural.height : Infinity;
107
+ return Math.round(Math.min(base, maxWidth, heightCap));
108
+ }
109
+ /** A region's place on the image, as percentages, and its color. */
110
+ export function regionBoxStyle(r) {
111
+ return {
112
+ left: `${r.box.x * 100}%`,
113
+ top: `${r.box.y * 100}%`,
114
+ width: `${r.box.width * 100}%`,
115
+ height: `${r.box.height * 100}%`,
116
+ "--chai-region-color": regionColor(r.number),
117
+ };
118
+ }
119
+ //# sourceMappingURL=edit-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-card.js","sourceRoot":"","sources":["../src/edit-card.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -11,4 +11,8 @@ export * from "./check-suggested-models.js";
11
11
  export * from "./reasoning.js";
12
12
  export * from "./composer.js";
13
13
  export * from "./media-analyzer.js";
14
+ export * from "./media-actions.js";
15
+ export * from "./result-format.js";
16
+ export * from "./composer-view.js";
17
+ export * from "./edit-card.js";
14
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -11,4 +11,8 @@ export * from "./check-suggested-models.js";
11
11
  export * from "./reasoning.js";
12
12
  export * from "./composer.js";
13
13
  export * from "./media-analyzer.js";
14
+ export * from "./media-actions.js";
15
+ export * from "./result-format.js";
16
+ export * from "./composer-view.js";
17
+ export * from "./edit-card.js";
14
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Download and share for a piece of generated media, shared by the cards in
3
+ * every framework binding.
4
+ *
5
+ * A plain `<a download href={remoteUrl}>` only forces a download for a
6
+ * same-origin or `blob:`/`data:` URL; for a cross-origin CDN URL (what a
7
+ * generated result usually is) browsers ignore `download` and open it
8
+ * instead. Fetching it into a blob first works wherever the source allows
9
+ * cross-origin fetches. When it doesn't, opening the media is the fallback.
10
+ */
11
+ export declare function downloadMedia(src: string, filename: string): Promise<void>;
12
+ /**
13
+ * The native share sheet where there is one, otherwise the media's URL
14
+ * copied to the clipboard. Says which, so the button can confirm a copy.
15
+ */
16
+ export declare function shareMedia(src: string): Promise<"shared" | "copied" | "none">;
17
+ /** A file name for a download: the URL's own extension when it has one, otherwise a guess by kind. */
18
+ export declare function downloadFilename(id: string, kind: string, src: string): string;
19
+ //# sourceMappingURL=media-actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-actions.d.ts","sourceRoot":"","sources":["../src/media-actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAehF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC,CAgBnF;AAED,sGAAsG;AACtG,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAI9E"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Download and share for a piece of generated media, shared by the cards in
3
+ * every framework binding.
4
+ *
5
+ * A plain `<a download href={remoteUrl}>` only forces a download for a
6
+ * same-origin or `blob:`/`data:` URL; for a cross-origin CDN URL (what a
7
+ * generated result usually is) browsers ignore `download` and open it
8
+ * instead. Fetching it into a blob first works wherever the source allows
9
+ * cross-origin fetches. When it doesn't, opening the media is the fallback.
10
+ */
11
+ export async function downloadMedia(src, filename) {
12
+ try {
13
+ const response = await fetch(src);
14
+ const blob = await response.blob();
15
+ const blobUrl = URL.createObjectURL(blob);
16
+ const a = document.createElement("a");
17
+ a.href = blobUrl;
18
+ a.download = filename;
19
+ document.body.appendChild(a);
20
+ a.click();
21
+ a.remove();
22
+ URL.revokeObjectURL(blobUrl);
23
+ }
24
+ catch {
25
+ window.open(src, "_blank", "noopener");
26
+ }
27
+ }
28
+ /**
29
+ * The native share sheet where there is one, otherwise the media's URL
30
+ * copied to the clipboard. Says which, so the button can confirm a copy.
31
+ */
32
+ export async function shareMedia(src) {
33
+ if (navigator.share) {
34
+ try {
35
+ await navigator.share({ url: src });
36
+ return "shared";
37
+ }
38
+ catch {
39
+ // Dismissed, or not shareable: same as closing a native share sheet.
40
+ return "none";
41
+ }
42
+ }
43
+ try {
44
+ await navigator.clipboard.writeText(src);
45
+ return "copied";
46
+ }
47
+ catch {
48
+ return "none";
49
+ }
50
+ }
51
+ /** A file name for a download: the URL's own extension when it has one, otherwise a guess by kind. */
52
+ export function downloadFilename(id, kind, src) {
53
+ const match = /\.([a-zA-Z0-9]{2,5})(?:[?#]|$)/.exec(src);
54
+ const ext = match?.[1] ?? (kind === "video" ? "mp4" : kind === "audio" ? "mp3" : kind === "text" ? "txt" : "png");
55
+ return `${kind}-${id}.${ext}`;
56
+ }
57
+ //# sourceMappingURL=media-actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media-actions.js","sourceRoot":"","sources":["../src/media-actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAW,EAAE,QAAgB;IAC/D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,IAAI,GAAG,OAAO,CAAC;QACjB,CAAC,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACtB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC,CAAC,KAAK,EAAE,CAAC;QACV,CAAC,CAAC,MAAM,EAAE,CAAC;QACX,GAAG,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IACzC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;YACpC,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QACzC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,gBAAgB,CAAC,EAAU,EAAE,IAAY,EAAE,GAAW;IACpE,MAAM,KAAK,GAAG,gCAAgC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzD,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAClH,OAAO,GAAG,IAAI,IAAI,EAAE,IAAI,GAAG,EAAE,CAAC;AAChC,CAAC"}