@destyler/edit 0.0.7-beta.1

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/dist/index.mjs ADDED
@@ -0,0 +1,520 @@
1
+ import { createAnatomy } from '@zag-js/anatomy';
2
+ import { createScope, dataAttr, ariaAttr, isComposingEvent, isApple, contains, raf } from '@zag-js/dom-query';
3
+ import { createMachine } from '@zag-js/core';
4
+ import { trackInteractOutside } from '@zag-js/interact-outside';
5
+ import { compact, isEqual, createSplitProps } from '@zag-js/utils';
6
+ import { createProps } from '@zag-js/types';
7
+
8
+ const anatomy = createAnatomy("edit").parts(
9
+ "root",
10
+ "area",
11
+ "label",
12
+ "preview",
13
+ "input",
14
+ "editTrigger",
15
+ "submitTrigger",
16
+ "cancelTrigger",
17
+ "control"
18
+ );
19
+ const parts = anatomy.build();
20
+
21
+ const dom = createScope({
22
+ getRootId: (ctx) => ctx.ids?.root ?? `editable:${ctx.id}`,
23
+ getAreaId: (ctx) => ctx.ids?.area ?? `editable:${ctx.id}:area`,
24
+ getLabelId: (ctx) => ctx.ids?.label ?? `editable:${ctx.id}:label`,
25
+ getPreviewId: (ctx) => ctx.ids?.preview ?? `editable:${ctx.id}:preview`,
26
+ getInputId: (ctx) => ctx.ids?.input ?? `editable:${ctx.id}:input`,
27
+ getControlId: (ctx) => ctx.ids?.control ?? `editable:${ctx.id}:control`,
28
+ getSubmitTriggerId: (ctx) => ctx.ids?.submitTrigger ?? `editable:${ctx.id}:submit`,
29
+ getCancelTriggerId: (ctx) => ctx.ids?.cancelTrigger ?? `editable:${ctx.id}:cancel`,
30
+ getEditTriggerId: (ctx) => ctx.ids?.editTrigger ?? `editable:${ctx.id}:edit`,
31
+ getInputEl: (ctx) => dom.getById(ctx, dom.getInputId(ctx)),
32
+ getPreviewEl: (ctx) => dom.getById(ctx, dom.getPreviewId(ctx)),
33
+ getSubmitTriggerEl: (ctx) => dom.getById(ctx, dom.getSubmitTriggerId(ctx)),
34
+ getCancelTriggerEl: (ctx) => dom.getById(ctx, dom.getCancelTriggerId(ctx)),
35
+ getEditTriggerEl: (ctx) => dom.getById(ctx, dom.getEditTriggerId(ctx))
36
+ });
37
+
38
+ function connect(state, send, normalize) {
39
+ const disabled = state.context.disabled;
40
+ const interactive = state.context.isInteractive;
41
+ const readOnly = state.context.readOnly;
42
+ const invalid = state.context.invalid;
43
+ const autoResize = state.context.autoResize;
44
+ const translations = state.context.translations;
45
+ const editing = state.matches("edit");
46
+ const placeholderProp = state.context.placeholder;
47
+ const placeholder = typeof placeholderProp === "string" ? { edit: placeholderProp, preview: placeholderProp } : placeholderProp;
48
+ const value = state.context.value;
49
+ const empty = value.trim() === "";
50
+ const valueText = empty ? placeholder?.preview ?? "" : value;
51
+ return {
52
+ editing,
53
+ empty,
54
+ value,
55
+ valueText,
56
+ setValue(value2) {
57
+ send({ type: "VALUE.SET", value: value2, src: "setValue" });
58
+ },
59
+ clearValue() {
60
+ send({ type: "VALUE.SET", value: "", src: "clearValue" });
61
+ },
62
+ edit() {
63
+ if (!interactive)
64
+ return;
65
+ send("EDIT");
66
+ },
67
+ cancel() {
68
+ if (!interactive)
69
+ return;
70
+ send("CANCEL");
71
+ },
72
+ submit() {
73
+ if (!interactive)
74
+ return;
75
+ send("SUBMIT");
76
+ },
77
+ getRootProps() {
78
+ return normalize.element({
79
+ ...parts.root.attrs,
80
+ id: dom.getRootId(state.context),
81
+ dir: state.context.dir
82
+ });
83
+ },
84
+ getAreaProps() {
85
+ return normalize.element({
86
+ ...parts.area.attrs,
87
+ "id": dom.getAreaId(state.context),
88
+ "dir": state.context.dir,
89
+ "style": autoResize ? { display: "inline-grid" } : void 0,
90
+ "data-focus": dataAttr(editing),
91
+ "data-disabled": dataAttr(disabled),
92
+ "data-placeholder-shown": dataAttr(empty)
93
+ });
94
+ },
95
+ getLabelProps() {
96
+ return normalize.label({
97
+ ...parts.label.attrs,
98
+ "id": dom.getLabelId(state.context),
99
+ "dir": state.context.dir,
100
+ "htmlFor": dom.getInputId(state.context),
101
+ "data-focus": dataAttr(editing),
102
+ "data-invalid": dataAttr(invalid),
103
+ onClick() {
104
+ if (editing)
105
+ return;
106
+ const previewEl = dom.getPreviewEl(state.context);
107
+ previewEl?.focus({ preventScroll: true });
108
+ }
109
+ });
110
+ },
111
+ getInputProps() {
112
+ return normalize.input({
113
+ ...parts.input.attrs,
114
+ "dir": state.context.dir,
115
+ "aria-label": translations.input,
116
+ "name": state.context.name,
117
+ "form": state.context.form,
118
+ "id": dom.getInputId(state.context),
119
+ "hidden": autoResize ? void 0 : !editing,
120
+ "placeholder": placeholder?.edit,
121
+ "maxLength": state.context.maxLength,
122
+ "required": state.context.required,
123
+ "disabled": disabled,
124
+ "data-disabled": dataAttr(disabled),
125
+ "readOnly": readOnly,
126
+ "data-readonly": dataAttr(readOnly),
127
+ "aria-invalid": ariaAttr(invalid),
128
+ "data-invalid": dataAttr(invalid),
129
+ "data-autoresize": dataAttr(autoResize),
130
+ "defaultValue": value,
131
+ "size": autoResize ? 1 : void 0,
132
+ onChange(event) {
133
+ send({ type: "VALUE.SET", src: "input.change", value: event.currentTarget.value });
134
+ },
135
+ onKeyDown(event) {
136
+ if (event.defaultPrevented)
137
+ return;
138
+ if (isComposingEvent(event))
139
+ return;
140
+ const keyMap = {
141
+ Escape() {
142
+ send("CANCEL");
143
+ event.preventDefault();
144
+ },
145
+ Enter(event2) {
146
+ if (!state.context.submitOnEnter)
147
+ return;
148
+ const { localName } = event2.currentTarget;
149
+ if (localName === "textarea") {
150
+ const submitMod = isApple() ? event2.metaKey : event2.ctrlKey;
151
+ if (!submitMod)
152
+ return;
153
+ send({ type: "SUBMIT", src: "keydown.enter" });
154
+ return;
155
+ }
156
+ if (localName === "input" && !event2.shiftKey && !event2.metaKey) {
157
+ send({ type: "SUBMIT", src: "keydown.enter" });
158
+ event2.preventDefault();
159
+ }
160
+ }
161
+ };
162
+ const exec = keyMap[event.key];
163
+ if (exec) {
164
+ exec(event);
165
+ }
166
+ },
167
+ "style": autoResize ? {
168
+ gridArea: "1 / 1 / auto / auto",
169
+ visibility: !editing ? "hidden" : void 0
170
+ } : void 0
171
+ });
172
+ },
173
+ getPreviewProps() {
174
+ return normalize.element({
175
+ "id": dom.getPreviewId(state.context),
176
+ ...parts.preview.attrs,
177
+ "dir": state.context.dir,
178
+ "data-placeholder-shown": dataAttr(empty),
179
+ "aria-readonly": ariaAttr(readOnly),
180
+ "data-readonly": dataAttr(disabled),
181
+ "data-disabled": dataAttr(disabled),
182
+ "aria-disabled": ariaAttr(disabled),
183
+ "aria-invalid": ariaAttr(invalid),
184
+ "data-invalid": dataAttr(invalid),
185
+ "aria-label": translations.edit,
186
+ "data-autoresize": dataAttr(autoResize),
187
+ "children": valueText,
188
+ "hidden": autoResize ? void 0 : editing,
189
+ "tabIndex": interactive ? 0 : void 0,
190
+ onClick() {
191
+ if (!interactive)
192
+ return;
193
+ if (state.context.activationMode !== "click")
194
+ return;
195
+ send({ type: "EDIT", src: "click" });
196
+ },
197
+ onFocus() {
198
+ if (!interactive)
199
+ return;
200
+ if (state.context.activationMode !== "focus")
201
+ return;
202
+ send({ type: "EDIT", src: "focus" });
203
+ },
204
+ onDoubleClick(event) {
205
+ if (event.defaultPrevented)
206
+ return;
207
+ if (!interactive)
208
+ return;
209
+ if (state.context.activationMode !== "dblclick")
210
+ return;
211
+ send({ type: "EDIT", src: "dblclick" });
212
+ },
213
+ "style": autoResize ? {
214
+ whiteSpace: "pre",
215
+ userSelect: "none",
216
+ gridArea: "1 / 1 / auto / auto",
217
+ visibility: editing ? "hidden" : void 0,
218
+ // in event the preview overflow's the parent element
219
+ overflow: "hidden",
220
+ textOverflow: "ellipsis"
221
+ } : void 0
222
+ });
223
+ },
224
+ getEditTriggerProps() {
225
+ return normalize.button({
226
+ ...parts.editTrigger.attrs,
227
+ "id": dom.getEditTriggerId(state.context),
228
+ "dir": state.context.dir,
229
+ "aria-label": translations.edit,
230
+ "hidden": editing,
231
+ "type": "button",
232
+ "disabled": disabled,
233
+ onClick(event) {
234
+ if (event.defaultPrevented)
235
+ return;
236
+ if (!interactive)
237
+ return;
238
+ send({ type: "EDIT", src: "edit.click" });
239
+ }
240
+ });
241
+ },
242
+ getControlProps() {
243
+ return normalize.element({
244
+ id: dom.getControlId(state.context),
245
+ ...parts.control.attrs,
246
+ dir: state.context.dir
247
+ });
248
+ },
249
+ getSubmitTriggerProps() {
250
+ return normalize.button({
251
+ ...parts.submitTrigger.attrs,
252
+ "dir": state.context.dir,
253
+ "id": dom.getSubmitTriggerId(state.context),
254
+ "aria-label": translations.submit,
255
+ "hidden": !editing,
256
+ "disabled": disabled,
257
+ "type": "button",
258
+ onClick(event) {
259
+ if (event.defaultPrevented)
260
+ return;
261
+ if (!interactive)
262
+ return;
263
+ send({ type: "SUBMIT", src: "submit.click" });
264
+ }
265
+ });
266
+ },
267
+ getCancelTriggerProps() {
268
+ return normalize.button({
269
+ ...parts.cancelTrigger.attrs,
270
+ "dir": state.context.dir,
271
+ "aria-label": translations.cancel,
272
+ "id": dom.getCancelTriggerId(state.context),
273
+ "hidden": !editing,
274
+ "type": "button",
275
+ "disabled": disabled,
276
+ onClick(event) {
277
+ if (event.defaultPrevented)
278
+ return;
279
+ if (!interactive)
280
+ return;
281
+ send({ type: "CANCEL", src: "cancel.click" });
282
+ }
283
+ });
284
+ }
285
+ };
286
+ }
287
+
288
+ const sync = {
289
+ value: (ctx) => {
290
+ const inputEl = dom.getInputEl(ctx);
291
+ dom.setValue(inputEl, ctx.value);
292
+ }
293
+ };
294
+ const invoke = {
295
+ change(ctx) {
296
+ ctx.onValueChange?.({ value: ctx.value });
297
+ sync.value(ctx);
298
+ }
299
+ };
300
+ const set = {
301
+ value(ctx, value) {
302
+ if (isEqual(ctx.value, value))
303
+ return;
304
+ ctx.value = value;
305
+ invoke.change(ctx);
306
+ }
307
+ };
308
+ const submitOnEnter = (ctx) => ["both", "enter"].includes(ctx.submitMode);
309
+ const submitOnBlur = (ctx) => ["both", "blur"].includes(ctx.submitMode);
310
+ function machine(userContext) {
311
+ const ctx = compact(userContext);
312
+ return createMachine(
313
+ {
314
+ id: "editable",
315
+ initial: ctx.edit ? "edit" : "preview",
316
+ entry: ctx.edit ? ["focusInput"] : void 0,
317
+ context: {
318
+ activationMode: "focus",
319
+ submitMode: "both",
320
+ value: "",
321
+ previousValue: "",
322
+ selectOnFocus: true,
323
+ disabled: false,
324
+ readOnly: false,
325
+ ...ctx,
326
+ translations: {
327
+ input: "editable input",
328
+ edit: "edit",
329
+ submit: "submit",
330
+ cancel: "cancel",
331
+ ...ctx.translations
332
+ }
333
+ },
334
+ watch: {
335
+ value: ["syncInputValue"],
336
+ edit: ["toggleEditing"]
337
+ },
338
+ computed: {
339
+ submitOnEnter,
340
+ submitOnBlur,
341
+ isInteractive: (ctx2) => !(ctx2.disabled || ctx2.readOnly)
342
+ },
343
+ on: {
344
+ "VALUE.SET": {
345
+ actions: "setValue"
346
+ }
347
+ },
348
+ states: {
349
+ preview: {
350
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=559561
351
+ entry: ["blurInputIfNeeded"],
352
+ on: {
353
+ "CONTROLLED.EDIT": {
354
+ target: "edit",
355
+ actions: ["setPreviousValue", "focusInput"]
356
+ },
357
+ "EDIT": [
358
+ {
359
+ guard: "isEditControlled",
360
+ actions: ["invokeOnEdit"]
361
+ },
362
+ {
363
+ target: "edit",
364
+ actions: ["setPreviousValue", "focusInput", "invokeOnEdit"]
365
+ }
366
+ ]
367
+ }
368
+ },
369
+ edit: {
370
+ activities: ["trackInteractOutside"],
371
+ on: {
372
+ "CONTROLLED.PREVIEW": [
373
+ {
374
+ guard: "isSubmitEvent",
375
+ target: "preview",
376
+ actions: ["setPreviousValue", "restoreFocus", "invokeOnSubmit"]
377
+ },
378
+ {
379
+ target: "preview",
380
+ actions: ["revertValue", "restoreFocus", "invokeOnCancel"]
381
+ }
382
+ ],
383
+ "CANCEL": [
384
+ {
385
+ guard: "isEditControlled",
386
+ actions: ["invokeOnPreview"]
387
+ },
388
+ {
389
+ target: "preview",
390
+ actions: ["revertValue", "restoreFocus", "invokeOnCancel", "invokeOnPreview"]
391
+ }
392
+ ],
393
+ "SUBMIT": [
394
+ {
395
+ guard: "isEditControlled",
396
+ actions: ["invokeOnPreview"]
397
+ },
398
+ {
399
+ target: "preview",
400
+ actions: ["setPreviousValue", "restoreFocus", "invokeOnSubmit", "invokeOnPreview"]
401
+ }
402
+ ]
403
+ }
404
+ }
405
+ }
406
+ },
407
+ {
408
+ guards: {
409
+ isEditControlled: (ctx2) => !!ctx2["edit.controlled"],
410
+ isSubmitEvent: (_ctx, evt) => evt.previousEvent?.type === "SUBMIT"
411
+ },
412
+ activities: {
413
+ trackInteractOutside(ctx2, _evt, { send }) {
414
+ return trackInteractOutside(dom.getInputEl(ctx2), {
415
+ exclude(target) {
416
+ const ignore = [dom.getCancelTriggerEl(ctx2), dom.getSubmitTriggerEl(ctx2)];
417
+ return ignore.some((el) => contains(el, target));
418
+ },
419
+ onFocusOutside: ctx2.onFocusOutside,
420
+ onPointerDownOutside: ctx2.onPointerDownOutside,
421
+ onInteractOutside(event) {
422
+ ctx2.onInteractOutside?.(event);
423
+ if (event.defaultPrevented)
424
+ return;
425
+ const { focusable } = event.detail;
426
+ send({ type: submitOnBlur(ctx2) ? "SUBMIT" : "CANCEL", src: "interact-outside", focusable });
427
+ }
428
+ });
429
+ }
430
+ },
431
+ actions: {
432
+ restoreFocus(ctx2, evt) {
433
+ if (evt.focusable)
434
+ return;
435
+ raf(() => {
436
+ const finalEl = ctx2.finalFocusEl?.() ?? dom.getEditTriggerEl(ctx2);
437
+ finalEl?.focus({ preventScroll: true });
438
+ });
439
+ },
440
+ focusInput(ctx2) {
441
+ raf(() => {
442
+ const inputEl = dom.getInputEl(ctx2);
443
+ if (!inputEl)
444
+ return;
445
+ if (ctx2.selectOnFocus) {
446
+ inputEl.select();
447
+ } else {
448
+ inputEl.focus({ preventScroll: true });
449
+ }
450
+ });
451
+ },
452
+ invokeOnCancel(ctx2) {
453
+ ctx2.onValueRevert?.({ value: ctx2.previousValue });
454
+ },
455
+ invokeOnSubmit(ctx2) {
456
+ ctx2.onValueCommit?.({ value: ctx2.value });
457
+ },
458
+ invokeOnEdit(ctx2) {
459
+ ctx2.onEditChange?.({ edit: true });
460
+ },
461
+ invokeOnPreview(ctx2) {
462
+ ctx2.onEditChange?.({ edit: false });
463
+ },
464
+ toggleEditing(ctx2, evt, { send }) {
465
+ send({ type: ctx2.edit ? "CONTROLLED.EDIT" : "CONTROLLED.PREVIEW", previousEvent: evt });
466
+ },
467
+ syncInputValue(ctx2) {
468
+ sync.value(ctx2);
469
+ },
470
+ setValue(ctx2, evt) {
471
+ const value = ctx2.maxLength != null ? evt.value.slice(0, ctx2.maxLength) : evt.value;
472
+ set.value(ctx2, value);
473
+ },
474
+ setPreviousValue(ctx2) {
475
+ ctx2.previousValue = ctx2.value;
476
+ },
477
+ revertValue(ctx2) {
478
+ set.value(ctx2, ctx2.previousValue);
479
+ },
480
+ blurInputIfNeeded(ctx2) {
481
+ dom.getInputEl(ctx2)?.blur();
482
+ }
483
+ }
484
+ }
485
+ );
486
+ }
487
+
488
+ const props = createProps()([
489
+ "activationMode",
490
+ "autoResize",
491
+ "dir",
492
+ "disabled",
493
+ "finalFocusEl",
494
+ "form",
495
+ "getRootNode",
496
+ "id",
497
+ "ids",
498
+ "invalid",
499
+ "maxLength",
500
+ "name",
501
+ "onEditChange",
502
+ "onFocusOutside",
503
+ "onInteractOutside",
504
+ "onPointerDownOutside",
505
+ "onValueChange",
506
+ "onValueCommit",
507
+ "onValueRevert",
508
+ "placeholder",
509
+ "readOnly",
510
+ "required",
511
+ "selectOnFocus",
512
+ "edit",
513
+ "edit.controlled",
514
+ "submitMode",
515
+ "translations",
516
+ "value"
517
+ ]);
518
+ const splitProps = createSplitProps(props);
519
+
520
+ export { anatomy, connect, machine, props, splitProps };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@destyler/edit",
3
+ "type": "module",
4
+ "version": "0.0.7-beta.1",
5
+ "description": "a component that allows users to edit text in place.",
6
+ "author": "Elone Hoo <elonehoo@gmail.com>",
7
+ "license": "MIT",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.mts",
11
+ "default": "./dist/index.mjs"
12
+ },
13
+ "./*": "./*"
14
+ },
15
+ "main": "dist/index.cjs",
16
+ "module": "dist/index.mjs",
17
+ "types": "dist/index.d.ts",
18
+ "files": [
19
+ "*.d.ts",
20
+ "dist"
21
+ ],
22
+ "dependencies": {
23
+ "@zag-js/anatomy": "^0.80.0",
24
+ "@zag-js/core": "^0.80.0",
25
+ "@zag-js/dom-event": "^0.80.0",
26
+ "@zag-js/dom-query": "^0.80.0",
27
+ "@zag-js/form-utils": "^0.80.0",
28
+ "@zag-js/interact-outside": "^0.80.0",
29
+ "@zag-js/types": "^0.80.0",
30
+ "@zag-js/utils": "^0.80.0"
31
+ },
32
+ "scripts": {
33
+ "clear": "rimraf ./dist",
34
+ "clear:node_modules": "rimraf ./node_modules",
35
+ "build": "pnpm run clear && unbuild",
36
+ "stub": "unbuild --stub"
37
+ }
38
+ }