@webx-ui/core 0.12.0 → 0.14.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.
@@ -19,6 +19,10 @@ export declare const builtinIcons: {
19
19
  readonly search: "<circle cx=\"11\" cy=\"11\" r=\"6.5\"/><path d=\"m16 16 4.5 4.5\"/>";
20
20
  readonly filter: "<path d=\"M4 5.5h16l-6.2 7.2v5.1l-3.6 2v-7.1z\"/>";
21
21
  readonly crop: "<path d=\"M6.5 2.5v13a2 2 0 0 0 2 2h13\"/><path d=\"M2.5 6.5h13a2 2 0 0 1 2 2v13\"/>";
22
+ readonly 'rotate-left': "<path d=\"M4.5 9.5A8 8 0 1 1 4 13.5\"/><path d=\"M9.5 9.5h-5v-5\"/>";
23
+ readonly 'rotate-right': "<path d=\"M19.5 9.5A8 8 0 1 0 20 13.5\"/><path d=\"M14.5 9.5h5v-5\"/>";
24
+ readonly 'flip-horizontal': "<path d=\"M12 3.5v17\" stroke-dasharray=\"2.5 2.5\"/><path d=\"M9.5 7.5v9L4 14.5v-5z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M14.5 7.5v9l5.5-2v-5z\"/>";
25
+ readonly 'flip-vertical': "<path d=\"M3.5 12h17\" stroke-dasharray=\"2.5 2.5\"/><path d=\"M7.5 9.5h9L14.5 4h-5z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M7.5 14.5h9L14.5 20h-5z\"/>";
22
26
  readonly refresh: "<path d=\"M4 12a8 8 0 0 1 13.7-5.6L20 8.5\"/><path d=\"M20 4.5v4h-4\"/><path d=\"M20 12a8 8 0 0 1-13.7 5.6L4 15.5\"/><path d=\"M4 19.5v-4h4\"/>";
23
27
  readonly upload: "<path d=\"M12 16V4m0 0-4.5 4.5M12 4l4.5 4.5\"/><path d=\"M4 15v3.5A1.5 1.5 0 0 0 5.5 20h13a1.5 1.5 0 0 0 1.5-1.5V15\"/>";
24
28
  readonly download: "<path d=\"M12 4v12m0 0-4.5-4.5M12 16l4.5-4.5\"/><path d=\"M4 15v3.5A1.5 1.5 0 0 0 5.5 20h13a1.5 1.5 0 0 0 1.5-1.5V15\"/>";
@@ -0,0 +1,76 @@
1
+ import { ImageEditorCrop, ImageEditorProps, ImageEditorRatio, ImageEditorRatioOption, ImageEditorResult } from './types';
2
+ /**
3
+ * Draws the result and hands it over. Exposed, so a page with a footer of its own can
4
+ * ask for it without the editor carrying one.
5
+ */
6
+ declare function apply(): Promise<ImageEditorResult | undefined>;
7
+ declare const _default: import('vue').DefineComponent<ImageEditorProps, {
8
+ /** Draws the result, emits `save` and answers with it. */
9
+ apply: typeof apply;
10
+ /** Back to the whole picture, the right way up. */
11
+ reset: () => void;
12
+ crop: import('vue').ComputedRef<ImageEditorCrop>;
13
+ }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
14
+ crop: (crop: ImageEditorCrop) => any;
15
+ error: (error: unknown) => any;
16
+ cancel: () => any;
17
+ load: (size: {
18
+ width: number;
19
+ height: number;
20
+ }) => any;
21
+ save: (result: ImageEditorResult) => any;
22
+ }, string, import('vue').PublicProps, Readonly<ImageEditorProps> & Readonly<{
23
+ onCrop?: ((crop: ImageEditorCrop) => any) | undefined;
24
+ onError?: ((error: unknown) => any) | undefined;
25
+ onCancel?: (() => any) | undefined;
26
+ onLoad?: ((size: {
27
+ width: number;
28
+ height: number;
29
+ }) => any) | undefined;
30
+ onSave?: ((result: ImageEditorResult) => any) | undefined;
31
+ }>, {
32
+ disabled: boolean;
33
+ footer: boolean;
34
+ background: string;
35
+ format: "auto" | "image/jpeg" | "image/png" | "image/webp";
36
+ filters: boolean;
37
+ resizable: boolean;
38
+ minSize: number;
39
+ maxWidth: number;
40
+ saveLabel: string;
41
+ cancelLabel: string;
42
+ ratio: ImageEditorRatio;
43
+ fileName: string;
44
+ aspect: number;
45
+ ratios: (ImageEditorRatio | ImageEditorRatioOption)[];
46
+ rotatable: boolean;
47
+ flippable: boolean;
48
+ maxHeight: number;
49
+ quality: number;
50
+ crossOrigin: "anonymous" | "use-credentials" | "";
51
+ resetLabel: string;
52
+ rotateLeftLabel: string;
53
+ rotateRightLabel: string;
54
+ flipHorizontalLabel: string;
55
+ flipVerticalLabel: string;
56
+ ratioLabel: string;
57
+ cropLabel: string;
58
+ outputLabel: string;
59
+ outputHint: string;
60
+ widthLabel: string;
61
+ heightLabel: string;
62
+ adjustLabel: string;
63
+ brightnessLabel: string;
64
+ contrastLabel: string;
65
+ saturationLabel: string;
66
+ monoLabel: string;
67
+ freeLabel: string;
68
+ originalLabel: string;
69
+ errorText: string;
70
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
71
+ stage: HTMLDivElement;
72
+ frame: HTMLDivElement;
73
+ image: HTMLImageElement;
74
+ cropBox: HTMLDivElement;
75
+ }, HTMLDivElement>;
76
+ export default _default;
@@ -0,0 +1,46 @@
1
+ import { ImageEditorCrop } from './types';
2
+ /** The eight grips, named after the compass the way every editor names them. */
3
+ export type CropHandle = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
4
+ export interface CropBounds {
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export declare function clamp(value: number, min: number, max: number): number;
9
+ /** `1.7778` → `16:9`. Falls back to a decimal for a ratio that is nobody's convention. */
10
+ export declare function ratioLabelFor(value: number): string;
11
+ /** The whole picture, or the largest rectangle of `ratio` in the middle of it. */
12
+ export declare function fullCrop(bounds: CropBounds, ratio?: number): ImageEditorCrop;
13
+ /**
14
+ * The largest rectangle of `ratio` that fits inside `crop`, kept where `crop` was — used
15
+ * when the ratio changes and when a turn leaves the old crop the wrong shape.
16
+ */
17
+ export declare function fitRatio(crop: ImageEditorCrop, ratio: number, bounds: CropBounds): ImageEditorCrop;
18
+ /** Dragged from inside: the crop slides, and stops at the edges of the picture. */
19
+ export declare function moveCrop(start: ImageEditorCrop, dx: number, dy: number, bounds: CropBounds): ImageEditorCrop;
20
+ /**
21
+ * Dragged by a grip.
22
+ *
23
+ * The opposite corner — or the opposite edge, for the four in the middle — is the anchor
24
+ * and does not move; everything else follows the pointer. With a ratio the rectangle is
25
+ * brought back to that ratio before it is fitted inside the picture, so a locked crop
26
+ * cannot be nudged out of shape by dragging it into a corner.
27
+ */
28
+ export declare function resizeCrop(handle: CropHandle, point: {
29
+ x: number;
30
+ y: number;
31
+ }, start: ImageEditorCrop, bounds: CropBounds, { ratio, min }?: {
32
+ ratio?: number;
33
+ min?: number;
34
+ }): ImageEditorCrop;
35
+ /**
36
+ * The crop follows the picture through a quarter turn, so what was framed stays framed.
37
+ * `bounds` is the space *before* the turn; the space after it has its sides swapped.
38
+ */
39
+ export declare function turnCrop(crop: ImageEditorCrop, bounds: CropBounds, direction: 1 | -1): ImageEditorCrop;
40
+ /** The same, for a mirroring. */
41
+ export declare function mirrorCrop(crop: ImageEditorCrop, bounds: CropBounds, axis: 'x' | 'y'): ImageEditorCrop;
42
+ /** The largest whole-pixel size of the same shape that fits within the caps. */
43
+ export declare function fitInside(width: number, height: number, maxWidth?: number, maxHeight?: number): {
44
+ width: number;
45
+ height: number;
46
+ };
@@ -0,0 +1,23 @@
1
+ import { ImageEditorAdjustments } from './types';
2
+ /** Nothing done to the picture: the reading every slider starts at. */
3
+ export declare const NEUTRAL: ImageEditorAdjustments;
4
+ export declare function isNeutral(adjust: ImageEditorAdjustments): boolean;
5
+ /**
6
+ * The adjustments as a CSS `filter` — the same string the `<img>` is shown through and
7
+ * the canvas is drawn under, which is what keeps the preview honest.
8
+ *
9
+ * Order matters and is the order a reader expects: lighten, then stretch the contrast,
10
+ * then the colour, and black-and-white last so that it wins over whatever the saturation
11
+ * slider was left at.
12
+ */
13
+ export declare function filterString(adjust: ImageEditorAdjustments): string;
14
+ /**
15
+ * The same arithmetic by hand, for a browser whose canvas has no `filter`.
16
+ *
17
+ * Safari only learned `ctx.filter` in 16.4, and where it is missing it is missing
18
+ * silently: the picture on screen is adjusted, the file that comes out is not. Doing the
19
+ * pass ourselves is the difference between a wrong file and a slow one.
20
+ *
21
+ * Written over the pixels in place, in the order `filterString` composes them.
22
+ */
23
+ export declare function applyAdjustments(pixels: Uint8ClampedArray, adjust: ImageEditorAdjustments): void;
@@ -0,0 +1,4 @@
1
+ export { default as WxImageEditor } from './ImageEditor';
2
+ export { ratioLabelFor } from './crop';
3
+ export { filterString } from './filters';
4
+ export type * from './types';
@@ -0,0 +1,158 @@
1
+ /** A rectangle of the picture, in the pixels of the image *after* its quarter turns. */
2
+ export interface ImageEditorCrop {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ /**
9
+ * A ratio the crop can be locked to: a number (width ÷ height), `free` for no lock, or
10
+ * `original` for whatever the picture itself is.
11
+ */
12
+ export type ImageEditorRatio = number | 'free' | 'original';
13
+ export interface ImageEditorRatioOption {
14
+ value: ImageEditorRatio;
15
+ /** Derived from the number when it is left out — `1.7778` reads as `16:9`. */
16
+ label?: string;
17
+ }
18
+ /**
19
+ * The adjustments, as percentages of what the picture already is: 100 leaves it alone.
20
+ * They are the four a photograph for a website actually wants, and no more — a preset is
21
+ * a name for a set of these numbers, and can be built on top without the editor knowing.
22
+ */
23
+ export interface ImageEditorAdjustments {
24
+ brightness: number;
25
+ contrast: number;
26
+ saturation: number;
27
+ /** Black and white, which overrules whatever the saturation is set to. */
28
+ mono: boolean;
29
+ }
30
+ /** What the editor hands over. Nothing is uploaded; this is a blob and its measurements. */
31
+ export interface ImageEditorResult {
32
+ blob: Blob;
33
+ /** The same bytes named, ready to be posted straight to a server. */
34
+ file: File;
35
+ /** MIME type of the blob. */
36
+ type: string;
37
+ width: number;
38
+ height: number;
39
+ /**
40
+ * The crop, in the pixels of the turned picture — turn by `rotation`, mirror by the
41
+ * flips, then cut this out, and a server arrives at the same picture.
42
+ */
43
+ crop: ImageEditorCrop;
44
+ /** Quarter turns, clockwise: 0, 90, 180 or 270. */
45
+ rotation: number;
46
+ flipX: boolean;
47
+ flipY: boolean;
48
+ /** The adjustments as they stood, and the CSS `filter` that says the same thing. */
49
+ adjustments: ImageEditorAdjustments;
50
+ /** Empty when the picture was left alone. A server can apply the same numbers. */
51
+ filter: string;
52
+ }
53
+ export interface ImageEditorProps {
54
+ /** The picture: a URL, or the `File` an upload field just handed you. */
55
+ src: string | Blob;
56
+ /**
57
+ * Name for the file in the result. Taken from `src` when it is a URL or a `File`, and
58
+ * its extension is put right to match the format actually written.
59
+ */
60
+ fileName?: string;
61
+ /**
62
+ * Locks the crop to one ratio and takes the picker away — `1` for an avatar. The
63
+ * ratios offered stay if `ratios` is given as well.
64
+ */
65
+ aspect?: number;
66
+ /** The ratios offered. Empty hides the picker and leaves the crop free. */
67
+ ratios?: (ImageEditorRatio | ImageEditorRatioOption)[];
68
+ /** Which of them the editor opens on. Defaults to the first. */
69
+ ratio?: ImageEditorRatio;
70
+ /** Quarter turns, left and right. */
71
+ rotatable?: boolean;
72
+ /** Mirroring, across and down. */
73
+ flippable?: boolean;
74
+ /** Offers the output size — the fields that scale the cut-out down before it is written. */
75
+ resizable?: boolean;
76
+ /**
77
+ * Offers the adjustments: brightness, contrast, saturation and black-and-white, behind
78
+ * one button. Off by default, so an editor asked for as a cropper stays one.
79
+ */
80
+ filters?: boolean;
81
+ /** Largest output, in pixels. A bigger crop is scaled down to fit inside it. */
82
+ maxWidth?: number;
83
+ maxHeight?: number;
84
+ /** Smallest crop, in the picture's own pixels. */
85
+ minSize?: number;
86
+ /**
87
+ * What to write. `auto` keeps the source's format where it is one a browser can write
88
+ * (JPEG, PNG, WebP) and falls back to JPEG.
89
+ */
90
+ format?: 'auto' | 'image/jpeg' | 'image/png' | 'image/webp';
91
+ /** 0 to 1, for the formats that have a quality. */
92
+ quality?: number;
93
+ /**
94
+ * Painted behind the picture when the format has no transparency, since transparent
95
+ * turns black in a JPEG.
96
+ */
97
+ background?: string;
98
+ /**
99
+ * `crossorigin` for the `<img>`. Anonymous by default: a canvas that has drawn a
100
+ * picture from a host which sent no CORS header cannot be read back, so the export
101
+ * would fail — this way it fails at loading, where it can be seen.
102
+ */
103
+ crossOrigin?: 'anonymous' | 'use-credentials' | '';
104
+ /** The editor's own Cancel and Save. Off when the panel around it has its own. */
105
+ footer?: boolean;
106
+ disabled?: boolean;
107
+ saveLabel?: string;
108
+ cancelLabel?: string;
109
+ resetLabel?: string;
110
+ rotateLeftLabel?: string;
111
+ rotateRightLabel?: string;
112
+ flipHorizontalLabel?: string;
113
+ flipVerticalLabel?: string;
114
+ /** Accessible name of the ratio picker, and of the crop box itself. */
115
+ ratioLabel?: string;
116
+ cropLabel?: string;
117
+ /** The output-size row: its caption, the tip on that caption, and the two fields. */
118
+ outputLabel?: string;
119
+ outputHint?: string;
120
+ widthLabel?: string;
121
+ heightLabel?: string;
122
+ /** The adjustments: the button, and the four things inside its panel. */
123
+ adjustLabel?: string;
124
+ brightnessLabel?: string;
125
+ contrastLabel?: string;
126
+ saturationLabel?: string;
127
+ monoLabel?: string;
128
+ freeLabel?: string;
129
+ originalLabel?: string;
130
+ /** Shown in place of the picture when it will not load. */
131
+ errorText?: string;
132
+ }
133
+ export interface ImageEditorEmits {
134
+ /** The picture, cut and turned. */
135
+ save: [result: ImageEditorResult];
136
+ cancel: [];
137
+ /** The picture loaded, at this size. */
138
+ load: [size: {
139
+ width: number;
140
+ height: number;
141
+ }];
142
+ /** It did not load, or could not be written. */
143
+ error: [error: unknown];
144
+ /** The crop moved or was resized, in the pixels of the turned picture. */
145
+ crop: [crop: ImageEditorCrop];
146
+ }
147
+ /**
148
+ * What `openImageEditor` takes: the editor's own props, and the panel it arrives in.
149
+ *
150
+ * A type rather than an interface so that it can be handed to `createModal`, which asks
151
+ * for something a bag of props can be made of.
152
+ */
153
+ export type ImageEditorModalProps = ImageEditorProps & {
154
+ /** Heading of the panel. */
155
+ title?: string;
156
+ /** Width of the panel: a number in pixels, or any CSS length. */
157
+ width?: number | string;
158
+ };
@@ -6,7 +6,7 @@ declare const _default: <T extends KanbanCard = KanbanCard>(__VLS_props: NonNull
6
6
  readonly "onUpdate:collapsed"?: ((value: KanbanId[]) => any) | undefined;
7
7
  readonly "onColumn-move"?: ((move: import('./types').KanbanColumnMove<T>) => any) | undefined;
8
8
  readonly "onAdd-column"?: (() => any) | undefined;
9
- } & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onAdd" | "onMove" | "onUpdate:collapsed" | "onColumn-move" | "onAdd-column"> & ({
9
+ } & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps, never>, "onMove" | "onAdd" | "onUpdate:collapsed" | "onColumn-move" | "onAdd-column"> & ({
10
10
  collapsed?: KanbanId[];
11
11
  } & KanbanProps<T>) & Partial<{}>> & import('vue').PublicProps;
12
12
  expose(exposed: import('vue').ShallowUnwrapRef<{
@@ -29,8 +29,8 @@ declare const __VLS_component: import('vue').DefineComponent<ProgressProps, {},
29
29
  max: number;
30
30
  indeterminate: boolean;
31
31
  formatter: (value: number, max: number) => string;
32
- thickness: number;
33
32
  showValue: boolean;
33
+ thickness: number;
34
34
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, HTMLDivElement>;
35
35
  declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
36
36
  export default _default;
@@ -19,8 +19,8 @@ declare const _default: import('vue').DefineComponent<__VLS_PublicProps, {}, {},
19
19
  range: boolean;
20
20
  min: number;
21
21
  step: number;
22
+ minStepsBetweenThumbs: number;
22
23
  showValue: boolean;
23
24
  marks: Record<number, string>;
24
- minStepsBetweenThumbs: number;
25
25
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
26
26
  export default _default;
@@ -14,9 +14,9 @@ declare const __VLS_component: import('vue').DefineComponent<TextProps, {}, {},
14
14
  tone: import('./types').TextTone;
15
15
  align: import('./types').TextAlign;
16
16
  truncate: boolean | number;
17
+ mono: boolean;
17
18
  weight: import('./types').TextWeight;
18
19
  italic: boolean;
19
- mono: boolean;
20
20
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
21
21
  declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
22
22
  export default _default;
@@ -42,6 +42,7 @@ export * from './Header';
42
42
  export * from './Heading';
43
43
  export * from './Icon';
44
44
  export * from './Image';
45
+ export * from './ImageEditor';
45
46
  export * from './Indicator';
46
47
  export * from './Input';
47
48
  export * from './InputNumber';
@@ -0,0 +1,19 @@
1
+ import { ImageEditorModalProps, ImageEditorResult } from '../components/ImageEditor/types';
2
+ /**
3
+ * Opens the picture in a panel and answers with what came out of it.
4
+ *
5
+ * ```ts
6
+ * const edited = await openImageEditor({ src: file.url, aspect: 16 / 9 })
7
+ * if (!edited) return
8
+ * await upload(edited.file)
9
+ * ```
10
+ *
11
+ * Nothing is uploaded and nothing is replaced: the answer is a blob, its measurements and
12
+ * the crop it was cut from, and the screen that asked decides where all that goes. Closed
13
+ * without saving — the ✕, escape, Cancel — it answers `undefined`, which is not an error.
14
+ *
15
+ * It is [`createModal`](/guide/modals) over `WxImageEditor` in a dialog, and worth reading
16
+ * as the example: one line names the component, the type of its answer and the event that
17
+ * carries it, and every call site is a single `await`.
18
+ */
19
+ export declare const openImageEditor: (props?: ImageEditorModalProps | undefined, options?: import('./useModal').ModalOptions) => import('./useModal').ModalPromise<ImageEditorResult>;
@@ -58,8 +58,11 @@ export declare function openModal<T = unknown>(component: Component, options?: M
58
58
  *
59
59
  * const product = await productBrowser({ multiple: false })
60
60
  * ```
61
+ *
62
+ * `P` is any object — an `interface` of props included, which a `Record<string, unknown>`
63
+ * would have turned away for want of an index signature.
61
64
  */
62
- export declare function createModal<T = unknown, P extends Record<string, unknown> = Record<string, never>>(component: Component, defaults?: ModalOptions): (props?: P, options?: ModalOptions) => ModalPromise<T>;
65
+ export declare function createModal<T = unknown, P extends object = Record<string, never>>(component: Component, defaults?: ModalOptions): (props?: P, options?: ModalOptions) => ModalPromise<T>;
63
66
  /**
64
67
  * The handle of the modal a component is being shown in. Outside one it answers all the
65
68
  * same, with `isModal: false` and calls that do nothing — so a component can be written
package/dist/index.d.ts CHANGED
@@ -6,4 +6,5 @@ export { useElementWidth } from './composables/useElementWidth';
6
6
  export { useToast, toast, toastQueue, dismissToast, removeToast, clearToasts, type ToastApi, type ToastHandle, type ToastOptions, type ToastRecord, type ToastType, type ToastAction, } from './composables/useToast';
7
7
  export { openModal, createModal, useModal, connectModals, modalKey, type ModalHandle, type ModalOptions, type ModalPromise, } from './composables/useModal';
8
8
  export { confirm, type ConfirmOptions } from './composables/confirm';
9
+ export { openImageEditor } from './composables/imageEditor';
9
10
  export { useResponsiveShell, shellLayoutFor, type ResponsiveShell, type ResponsiveShellOptions, type ShellLayout, } from './composables/useResponsiveShell';