@wonderyard/tela 0.0.0-stage → 0.1.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/dist/brush.d.ts +21 -0
  3. package/dist/brush.js +84 -0
  4. package/dist/document.d.ts +50 -0
  5. package/dist/document.js +105 -0
  6. package/dist/history.d.ts +16 -0
  7. package/dist/history.js +38 -0
  8. package/dist/image.d.ts +41 -0
  9. package/dist/image.js +94 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.js +399 -0
  12. package/dist/input.d.ts +67 -0
  13. package/dist/input.js +536 -0
  14. package/dist/internals.d.ts +18 -0
  15. package/dist/internals.js +3 -0
  16. package/dist/keymap.d.ts +20 -0
  17. package/dist/keymap.js +266 -0
  18. package/dist/mask.d.ts +27 -0
  19. package/dist/mask.js +96 -0
  20. package/dist/moving.d.ts +102 -0
  21. package/dist/moving.js +215 -0
  22. package/dist/palette.d.ts +21 -0
  23. package/dist/palette.js +47 -0
  24. package/dist/react.d.ts +6 -0
  25. package/dist/react.js +16 -0
  26. package/dist/selection.d.ts +15 -0
  27. package/dist/selection.js +76 -0
  28. package/dist/sprite.d.ts +20 -0
  29. package/dist/sprite.js +49 -0
  30. package/dist/store.d.ts +4 -0
  31. package/dist/store.js +20 -0
  32. package/dist/tools.d.ts +43 -0
  33. package/dist/tools.js +113 -0
  34. package/dist/transactions.d.ts +45 -0
  35. package/dist/transactions.js +135 -0
  36. package/dist/types/document.d.ts +2 -0
  37. package/dist/types/history.d.ts +11 -0
  38. package/dist/types/image.d.ts +7 -0
  39. package/dist/types/index.d.ts +371 -0
  40. package/dist/types/input.d.ts +105 -0
  41. package/dist/types/keymap.d.ts +26 -0
  42. package/dist/types/mask.d.ts +11 -0
  43. package/dist/types/moving.d.ts +26 -0
  44. package/dist/types/selection.d.ts +1 -0
  45. package/dist/types/store.d.ts +4 -0
  46. package/dist/types/tools.d.ts +8 -0
  47. package/dist/types/view.d.ts +14 -0
  48. package/dist/view.d.ts +56 -0
  49. package/dist/view.js +139 -0
  50. package/package.json +72 -3
  51. package/README.md +0 -3
package/dist/index.js ADDED
@@ -0,0 +1,399 @@
1
+ import { crop as e } from "./image.js";
2
+ import { toMask as t } from "./mask.js";
3
+ import { fill as n, stroke as r } from "./tools.js";
4
+ import { createBrush as i } from "./brush.js";
5
+ import { createDocument as a } from "./document.js";
6
+ import { createHistory as o } from "./history.js";
7
+ import { defaultKeymap as s } from "./keymap.js";
8
+ import { createView as ee } from "./view.js";
9
+ import { createInput as te, inputs as ne } from "./input.js";
10
+ import { internals as c } from "./internals.js";
11
+ import { createMoving as l } from "./moving.js";
12
+ import { createPalette as re } from "./palette.js";
13
+ import { invert as u, modify as d, selectAll as f, selectWithTool as p } from "./selection.js";
14
+ import { createSprite as m } from "./sprite.js";
15
+ import { createStore as ie } from "./store.js";
16
+ import { createTransactions as ae } from "./transactions.js";
17
+ // lib/index.ts
18
+ var oe = {
19
+ rectangular_marquee: "Rectangular Marquee Tool",
20
+ lasso: "Lasso Tool",
21
+ magic_wand: "Magic Wand Tool"
22
+ }, h = {
23
+ invert: "Invert Mask",
24
+ modify: "Modify Selection",
25
+ set: "Set Selection"
26
+ }, g = (e, t, n, r) => {
27
+ if (t.length !== n * r) throw Error(`tela: ${e}'s image is ${t.length} pixels, not ${n}×${r}`);
28
+ };
29
+ function _(_) {
30
+ let { width: v, height: y } = _;
31
+ _.image && g("createEditor", _.image, v, y);
32
+ let b = o(), x = ie({
33
+ width: v,
34
+ height: y,
35
+ palette: _.palette,
36
+ selection: null,
37
+ movingPixels: !1,
38
+ tool: "pencil",
39
+ fgColor: 1,
40
+ bgColor: 0,
41
+ brush: {
42
+ type: "circle",
43
+ size: 1
44
+ },
45
+ toolOptions: {
46
+ paint_bucket: {
47
+ contiguous: !0,
48
+ pixelConnectivity: 4
49
+ },
50
+ magic_wand: {
51
+ contiguous: !0,
52
+ pixelConnectivity: 4
53
+ }
54
+ },
55
+ keepBrushIndices: !1,
56
+ selectionMode: "replace",
57
+ holeIndex: null,
58
+ transparentColor: {
59
+ opaque: !1,
60
+ autoOpaque: !0
61
+ },
62
+ undoLimit: 0,
63
+ view: {
64
+ zoom: 1,
65
+ scroll: {
66
+ x: 0,
67
+ y: 0
68
+ }
69
+ },
70
+ zoomWithScrollWheel: !1,
71
+ showPixelGrid: !1,
72
+ brushPreview: "full",
73
+ crosshair: !1,
74
+ systemCursor: !1,
75
+ touchGestures: !1,
76
+ cursorColor: null,
77
+ history: {
78
+ undoSteps: 0,
79
+ redoSteps: 0,
80
+ undoLabel: null,
81
+ redoLabel: null
82
+ },
83
+ running: !1,
84
+ ticking: !1,
85
+ screen: {
86
+ width: 0,
87
+ height: 0,
88
+ dpr: 1
89
+ },
90
+ transform: {
91
+ x: 0,
92
+ y: 0,
93
+ scale: 1
94
+ }
95
+ }), S = ee(x, () => $.focus()), C = a({
96
+ image: _.image ? _.image.slice() : new Uint8Array(v * y),
97
+ width: v,
98
+ height: y,
99
+ palette: _.palette
100
+ }, {
101
+ onChange: _.onChange,
102
+ fresh: () => A.fresh,
103
+ show: (e) => S.update(e)
104
+ }), w = {
105
+ change: /* @__PURE__ */ new Set(),
106
+ movingpixels: /* @__PURE__ */ new Set()
107
+ }, T = (e, n) => {
108
+ let [r, i] = [C.sel, n && t(n, C.width)];
109
+ C.select(i), j(e, C.selChange(r, C.sel));
110
+ }, E = (e) => {
111
+ x.setState({ history: b.state });
112
+ for (let t of w.change) t({ fromUndo: e });
113
+ }, D = () => x.getState().holeIndex ?? x.getState().bgColor, O = (e, t) => {
114
+ b.record(e, t, x.getState().undoLimit * 1024 * 1024), E(!1);
115
+ }, k = 0, A = ae({
116
+ doc: C,
117
+ read: () => _.read(),
118
+ changed: () => {
119
+ let e = A.running || k > 0, { ticking: t } = A, n = x.getState();
120
+ (n.running !== e || n.ticking !== t) && x.setState({
121
+ running: e,
122
+ ticking: t
123
+ });
124
+ },
125
+ add: O,
126
+ boundary: () => ($.end(), B()),
127
+ checkSize: g
128
+ }), { record: j, hold: M } = A, N = (e) => {
129
+ let t = A.blind, n = r(C, e, t);
130
+ return {
131
+ ...n,
132
+ end: () => void (t || j("Pencil Tool", n.end()))
133
+ };
134
+ }, P = (e, t, r) => j("Paint Bucket Tool", n(C, e, t, r)), F = (e, t, n, r = x.getState().toolOptions.magic_wand) => T(oe[e], p(C, e, t, n, r)), I = ({ button: e, color: t, bgColor: n, brush: r }) => {
135
+ let i = x.getState();
136
+ return {
137
+ right: e === "right",
138
+ fg: t ?? i.fgColor,
139
+ bg: n ?? i.bgColor,
140
+ brush: r ?? i.brush
141
+ };
142
+ }, L = () => {}, R = (e) => {
143
+ e === "end" && L();
144
+ for (let t of w.movingpixels) t({ phase: e });
145
+ }, z = l({
146
+ doc: C,
147
+ store: x,
148
+ emit: R,
149
+ holeIndex: D
150
+ }), B = () => {
151
+ let e = z.commit();
152
+ return e && j(e.label, e.change), !!e;
153
+ }, V = async (e = !1) => z.active ? !0 : C.sel ? (R("start"), L = await A.acquire(), z.lift(e), !0) : !1, H = m({
154
+ doc: C,
155
+ holeIndex: D,
156
+ canResize: _.canResize
157
+ }), U = (e, t) => !!t && (j(e, t), !0), W = _.platform ?? (typeof navigator < "u" && /Mac|iP/.test(navigator.platform) ? "mac" : "other"), G = _.maxBrushSize ?? 64, K = (_.keymap ?? ((e) => e))(s(W)), q = i(x, G), J = re({
158
+ doc: C,
159
+ store: x,
160
+ held(e) {
161
+ z.remap(e), q.remap(e);
162
+ }
163
+ }), Y = (e) => {
164
+ e.tool && e.tool !== x.getState().tool && B(), e = q.patch(e), e.view && (e = {
165
+ ...e,
166
+ view: S.setting(e.view)
167
+ }), S.update(e);
168
+ }, X = null, Z = () => {
169
+ let e = X;
170
+ return X = null, e?.end(), e?.done ?? Promise.resolve();
171
+ }, Q, $ = te({
172
+ get editor() {
173
+ return Q;
174
+ },
175
+ store: {
176
+ getState: x.getState,
177
+ subscribe: x.subscribe,
178
+ setState: S.update
179
+ },
180
+ set: Y,
181
+ zoomTo: (e, t) => S.zoomTo(e, t),
182
+ pinch: S.pinch,
183
+ stroke: N,
184
+ fill: (e, t) => P(e, t, x.getState().toolOptions.paint_bucket),
185
+ selectWith: (e, t, n) => F(e, t, n),
186
+ deselect: () => T("Deselect Mask", null),
187
+ hold: M,
188
+ ready: A.ready,
189
+ moving: {
190
+ get: z.shown,
191
+ lift: V,
192
+ drag: z.drag,
193
+ drop: z.drop,
194
+ commit: () => void B()
195
+ },
196
+ pixel: (e, t) => e >= 0 && t >= 0 && e < C.width && t < C.height ? C.image[t * C.width + e] : null,
197
+ keymap: K,
198
+ platform: W,
199
+ maxBrushSize: G
200
+ });
201
+ return Q = {
202
+ get state() {
203
+ return x.getState();
204
+ },
205
+ subscribe: x.subscribe,
206
+ on(e, t) {
207
+ return w[e].add(t), () => w[e].delete(t);
208
+ },
209
+ set: Y,
210
+ command: {
211
+ async ChangeBrush({ change: e }) {
212
+ return q.change(e);
213
+ },
214
+ async NewBrush() {
215
+ B();
216
+ let n = C.bits && t(C.bits, C.width);
217
+ if (!n) return !1;
218
+ let r = await M(() => e(H.picture(), n, 0).data);
219
+ return Y({ brush: q.fromSelection(n, r) }), T("Deselect Mask", null), !0;
220
+ },
221
+ async SwitchColors() {
222
+ B();
223
+ let { fgColor: e, bgColor: t } = x.getState();
224
+ return Y({
225
+ fgColor: t,
226
+ bgColor: e
227
+ }), !0;
228
+ },
229
+ async Undo() {
230
+ let e = $.cancel();
231
+ return z.cancel() || e ? !0 : X && !await Z().then(() => !0, () => !1) ? !1 : A.idle(() => b.undo() && (E(!0), !0));
232
+ },
233
+ async Redo() {
234
+ return A.idle(() => b.redo() && (E(!0), !0));
235
+ },
236
+ async MaskAll() {
237
+ return B(), T("Mask All", f(C)), !0;
238
+ },
239
+ async DeselectMask() {
240
+ return B(), C.bits ? (T("Deselect Mask", null), !0) : !1;
241
+ },
242
+ async InvertMask() {
243
+ return B(), T(h.invert, u(C)), !0;
244
+ },
245
+ async ModifySelection({ modifier: e, quantity: t, brush: n = "circle" }) {
246
+ if (!(t >= 1)) throw Error("tela: ModifySelection's quantity is at least 1");
247
+ return B(), C.bits ? (T(h.modify, d(C, e, t, n)), !0) : !1;
248
+ },
249
+ async Zoom(e) {
250
+ return S.zoom(e);
251
+ },
252
+ async FitScreen() {
253
+ return S.fit(), !0;
254
+ },
255
+ async ScrollCenter() {
256
+ return S.centre(), !0;
257
+ },
258
+ async ShowPixelGrid() {
259
+ return Y({ showPixelGrid: !x.getState().showPixelGrid }), !0;
260
+ },
261
+ async Copy() {
262
+ return M(() => z.copy());
263
+ },
264
+ async Cut() {
265
+ return M(() => z.copy() ? (j("Cut", z.active ? z.discard() : z.clear()), !0) : !1);
266
+ },
267
+ async Paste(e) {
268
+ return z.clipboard.get() ? (B(), L = await A.acquire(), z.paste(e)) : !1;
269
+ },
270
+ async Clear() {
271
+ return M(() => (j("Clear", z.active ? z.discard() : z.clear()), !0));
272
+ },
273
+ async Flip({ target: e = "mask", orientation: t }) {
274
+ let n = t === "horizontal" ? "flip-x" : "flip-y";
275
+ return z.active || e === "mask" && C.sel ? (await V(), z.reshape([n], !1), !0) : M(() => U("Flip Canvas", H.turn([n])));
276
+ },
277
+ async Rotate({ target: e = "mask", angle: t }) {
278
+ let n = "rotate-90cw", r = t === 180 ? ["flip-x", "flip-y"] : t === 90 ? [n] : [
279
+ n,
280
+ n,
281
+ n
282
+ ];
283
+ return e === "mask" && (z.active || C.sel) ? (await V(), z.reshape(r, t !== 180), !0) : (B(), M(() => U("Rotate Canvas", H.turn(r))));
284
+ },
285
+ async MoveMask({ wrap: e, direction: t, quantity: n = 1 }) {
286
+ let [r, i] = {
287
+ left: [-1, 0],
288
+ right: [1, 0],
289
+ up: [0, -1],
290
+ down: [0, 1]
291
+ }[t];
292
+ if (e) {
293
+ if (z.active) z.wrap(r * n, i * n);
294
+ else if (C.sel) await M(() => j("Shift", H.shift(r * n, i * n)));
295
+ else return !1;
296
+ return !0;
297
+ }
298
+ return await V() ? (z.nudge(r * n, i * n), !0) : !1;
299
+ },
300
+ async CanvasSize(e) {
301
+ if (B(), "ui" in e) return _.openDialog?.("CanvasSize"), !0;
302
+ let { left: t = 0, top: n = 0, right: r = 0, bottom: i = 0 } = "bounds" in e ? {} : e, a = "bounds" in e ? e.bounds : {
303
+ x: -t,
304
+ y: -n,
305
+ width: C.width + t + r,
306
+ height: C.height + n + i
307
+ };
308
+ return M(() => U("Canvas Size", H.cropTo(a.x, a.y, a.width, a.height)));
309
+ },
310
+ async CropSprite(e) {
311
+ B();
312
+ let t = e?.bounds ?? C.sel;
313
+ return t ? M(() => U("Sprite Crop", H.cropTo(t.x, t.y, t.width, t.height))) : !1;
314
+ },
315
+ async AutocropSprite() {
316
+ return B(), M(() => U("Trim Sprite", H.trim()));
317
+ },
318
+ async AddColor({ source: e = "color", color: t = "#00000000" }) {
319
+ let n = J.addColor(e, t);
320
+ return n ? (B(), n.change.redo(), j(n.label, n.change), !0) : !1;
321
+ },
322
+ async Apply() {
323
+ return B();
324
+ },
325
+ async Cancel() {
326
+ return !B() && !C.sel ? !1 : (T("Deselect Mask", null), !0);
327
+ }
328
+ },
329
+ async useTool(e) {
330
+ if (B(), !e.points.length) return;
331
+ let t = x.getState();
332
+ if (e.tool === "pencil") {
333
+ await A.ready();
334
+ let t = N(I(e));
335
+ return e.points.forEach((n, r) => t.to(e.points[r - 1] ?? null, n)), t.end();
336
+ }
337
+ if (e.tool === "paint_bucket") return M(() => P(e.points[0], I(e), {
338
+ contiguous: e.contiguous ?? !0,
339
+ pixelConnectivity: e.pixelConnectivity ?? t.toolOptions.paint_bucket.pixelConnectivity
340
+ }));
341
+ F(e.tool, e.points, e.selection ?? t.selectionMode, {
342
+ contiguous: e.contiguous ?? !0,
343
+ pixelConnectivity: e.pixelConnectivity ?? t.toolOptions.magic_wand.pixelConnectivity
344
+ });
345
+ },
346
+ async setSelection(e, t = h.set) {
347
+ B();
348
+ let n = C.sel;
349
+ C.select(e), j(t, C.selChange(n, C.sel));
350
+ },
351
+ async setColor(e, t, n = "Set Color") {
352
+ if (!(e >= 0 && e < C.palette.length)) throw Error(`tela: no palette index ${e}`);
353
+ B(), j(n, J.setColor(e, t));
354
+ },
355
+ async setPalette(e, { remap: t, label: n = "Set Palette" } = {}) {
356
+ B(), t || e.length < C.palette.length ? await M(() => j(n, J.setPalette(e, t))) : j(n, J.setPalette(e, t));
357
+ },
358
+ transaction: (e, t) => A.run(e, t),
359
+ play(e = "Run", t) {
360
+ if (!t && X) return X.done;
361
+ let n, r = new Promise((e) => n = e);
362
+ k++;
363
+ let i = A.run(e, () => (k--, t ? t() : r), !0);
364
+ return A.changed(), t || (X = {
365
+ end: n,
366
+ done: i
367
+ }), i;
368
+ },
369
+ pause: Z,
370
+ async replaceImage(e, t, n) {
371
+ A.requireOpen("replaceImage"), g("replaceImage", e, t, n);
372
+ let r = e.slice(), i = z.commit();
373
+ i && O(i.label, [i.change]);
374
+ let a = t === C.width && n === C.height ? C.sel : null;
375
+ A.push(C.imageChange(r, t, n, a)), C.replace(r.slice(), t, n), C.select(a);
376
+ },
377
+ async addHostChange(e) {
378
+ A.requireOpen("addHostChange"), A.push({
379
+ size: 0,
380
+ ...e
381
+ });
382
+ },
383
+ async open(e) {
384
+ if (!await A.idle(() => !0)) throw Error("tela: open inside a transaction");
385
+ g("open", e.image, e.width, e.height), C.replace(e.image.slice(), e.width, e.height, !1), b = o(), z.reset(), C.setPalette(e.palette, !1), C.select(null), S.centre(1), E(!1);
386
+ },
387
+ async image() {
388
+ return M(() => C.image);
389
+ },
390
+ keymap: K,
391
+ attach: $.attach,
392
+ toPixel: (e, t) => $.toPixel(e, t)
393
+ }, ne.set(Q, $), c.set(Q, {
394
+ clipboard: z.clipboard,
395
+ setScreen: (e) => S.update({ screen: e }),
396
+ moving: z.shown
397
+ }), Q;
398
+ }
399
+ export { _ as createEditor };
@@ -0,0 +1,67 @@
1
+ import { Editor, Point } from './types/index';
2
+ import { InputHost, PointerInput, WheelInput } from './types/input';
3
+ /** Each editor's input seam, for tela's tests. */
4
+ export declare const inputs: WeakMap<Editor, {
5
+ down: (e: PointerInput) => Promise<void>;
6
+ move: (e: PointerInput) => Promise<void>;
7
+ up: (_e?: PointerInput) => Promise<void>;
8
+ /**
9
+ * ⌘Z mid-drag: a stroke ends and records (a known issue); any other drag is cancelled, recording nothing. True when a drag was cancelled.
10
+ */
11
+ cancel(): boolean;
12
+ /** Play and Pause: an open stroke ends and records; the rest of the drag paints nothing. */
13
+ end(): void;
14
+ /** The last key's command, settled: for tests. */
15
+ idle: () => Promise<unknown>;
16
+ /** A key from the keymap: true when it ran an entry. */
17
+ key(e: KeyboardEvent): boolean;
18
+ /**
19
+ * The wheel, one rule for mouse and trackpad: Ctrl+wheel without Control held is a pinch.
20
+ * Zooming moves one step per event whose dx + dy reaches 1.5 CSS px, "scroll up" zooming in;
21
+ * scrolling pans 1:1, or a tenth of the view per line or page event.
22
+ */
23
+ wheel(w: WheelInput, focus: Point): boolean;
24
+ /** The zoom keys' focus: the last pointer position, or the view's centre before any. */
25
+ focus(): Point;
26
+ attach: (element: HTMLElement, overlay: HTMLCanvasElement) => () => void;
27
+ toPixel: (_x: number, _y: number) => Point | null;
28
+ }>;
29
+ /**
30
+ * The Rectangular Marquee's rect from the press to `end`, both included: squared to the shorter
31
+ * side, and centred on the press.
32
+ */
33
+ export declare function marqueeRect(start: Point, end: Point, square: boolean, centre: boolean): {
34
+ x: number;
35
+ y: number;
36
+ }[];
37
+ /**
38
+ * "Angle Snap from Last Point": 0°, 26.6°, 45°, 63.4° or 90°. The longer axis keeps its length; at
39
+ * 26.6° and 63.4° the other is half of it, truncated; at 45° both take the shorter one. From the
40
+ * Line tool's gesture specs.
41
+ */
42
+ export declare function snap(from: Point, to: Point): Point;
43
+ export declare function createInput(host: InputHost): {
44
+ down: (e: PointerInput) => Promise<void>;
45
+ move: (e: PointerInput) => Promise<void>;
46
+ up: (_e?: PointerInput) => Promise<void>;
47
+ /**
48
+ * ⌘Z mid-drag: a stroke ends and records (a known issue); any other drag is cancelled, recording nothing. True when a drag was cancelled.
49
+ */
50
+ cancel(): boolean;
51
+ /** Play and Pause: an open stroke ends and records; the rest of the drag paints nothing. */
52
+ end(): void;
53
+ /** The last key's command, settled: for tests. */
54
+ idle: () => Promise<unknown>;
55
+ /** A key from the keymap: true when it ran an entry. */
56
+ key(e: KeyboardEvent): boolean;
57
+ /**
58
+ * The wheel, one rule for mouse and trackpad: Ctrl+wheel without Control held is a pinch.
59
+ * Zooming moves one step per event whose dx + dy reaches 1.5 CSS px, "scroll up" zooming in;
60
+ * scrolling pans 1:1, or a tenth of the view per line or page event.
61
+ */
62
+ wheel(w: WheelInput, focus: Point): boolean;
63
+ /** The zoom keys' focus: the last pointer position, or the view's centre before any. */
64
+ focus(): Point;
65
+ attach: (element: HTMLElement, overlay: HTMLCanvasElement) => () => void;
66
+ toPixel: (_x: number, _y: number) => Point | null;
67
+ };