@tremolo-ui/react 0.7.0 → 0.9.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 (55) hide show
  1. package/dist/index.cjs +666 -1171
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +316 -315
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +316 -315
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +643 -1123
  8. package/dist/index.js.map +1 -1
  9. package/package.json +9 -9
  10. package/src/components/AnimationCanvas/index.tsx +4 -18
  11. package/src/components/DropZone/index.tsx +2 -1
  12. package/src/components/FileInput/index.tsx +11 -13
  13. package/src/components/Knob/ActiveLine.tsx +5 -3
  14. package/src/components/Knob/InactiveLine.tsx +6 -4
  15. package/src/components/Knob/SVGRoot.tsx +3 -3
  16. package/src/components/Knob/Thumb.tsx +21 -20
  17. package/src/components/Knob/context.tsx +3 -74
  18. package/src/components/Knob/index.tsx +99 -52
  19. package/src/components/NumberInput/InputField.tsx +20 -33
  20. package/src/components/NumberInput/Stepper.tsx +76 -100
  21. package/src/components/NumberInput/StepperButton.tsx +15 -8
  22. package/src/components/NumberInput/context.tsx +26 -3
  23. package/src/components/NumberInput/index.tsx +175 -91
  24. package/src/components/Piano/index.tsx +54 -144
  25. package/src/components/PointsEditor/Container.tsx +15 -26
  26. package/src/components/PointsEditor/Point.tsx +94 -110
  27. package/src/components/PointsEditor/context.tsx +8 -60
  28. package/src/components/PointsEditor/index.tsx +41 -324
  29. package/src/components/Slider/Marks.tsx +5 -6
  30. package/src/components/Slider/MarksOption.tsx +9 -19
  31. package/src/components/Slider/Thumb.tsx +20 -11
  32. package/src/components/Slider/Track.tsx +16 -13
  33. package/src/components/Slider/context.tsx +3 -3
  34. package/src/components/Slider/index.tsx +110 -72
  35. package/src/components/XYPad/Area.tsx +27 -49
  36. package/src/components/XYPad/Thumb.tsx +20 -8
  37. package/src/components/XYPad/context.tsx +2 -4
  38. package/src/components/XYPad/index.tsx +102 -83
  39. package/src/components/_util/VisuallyHiddenRangeInput.tsx +1 -1
  40. package/src/hooks/_internal/useChangeGesture.ts +93 -0
  41. package/src/hooks/_internal/useCheckSteps.ts +9 -146
  42. package/src/hooks/useAnimationFrame.ts +37 -15
  43. package/src/hooks/useEventListener.ts +93 -22
  44. package/src/hooks/useLongPress.ts +35 -51
  45. package/src/hooks/useMIDIAccess.ts +0 -8
  46. package/src/hooks/useMIDIInput.ts +0 -3
  47. package/src/hooks/useWheel.ts +4 -4
  48. package/src/index.ts +11 -32
  49. package/src/components/Piano/keyboard-shortcuts.ts +0 -61
  50. package/src/components/Slider/type.ts +0 -46
  51. package/src/components/_util/css-length.ts +0 -13
  52. package/src/components/_util/decimal-digits.ts +0 -23
  53. package/src/components/_util/visually-hidden.ts +0 -25
  54. package/src/hooks/useInterval.ts +0 -27
  55. package/src/input-event.ts +0 -32
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  ComponentPropsWithoutRef,
3
3
  CSSProperties,
4
- useCallback,
5
4
  useEffect,
6
5
  useId,
7
6
  useRef,
@@ -9,38 +8,30 @@ import {
9
8
  } from 'react'
10
9
 
11
10
  import {
12
- applyDelta,
11
+ type ChangeSource,
13
12
  type InputEventOption,
14
- type ModifierState,
15
13
  type ModifierValue,
16
- selectModifier,
14
+ type PointPosition,
15
+ type PointsEditorPoint,
17
16
  } from '@tremolo-ui/dom'
18
- import { clamp } from '@tremolo-ui/functions'
17
+ import {
18
+ arrowKeyMove,
19
+ clampPoint,
20
+ POINT_AXIS,
21
+ selectModifier,
22
+ } from '@tremolo-ui/dom/internal'
19
23
 
20
24
  import { useComposedRefs } from '../../compose-refs'
25
+ import { useChangeGesture } from '../../hooks/_internal/useChangeGesture'
21
26
  import { useDragValue } from '../../hooks/useDragValue'
22
- import { cssLength } from '../_util/css-length'
23
27
  import { useCheckPlacement } from '../_util/Placement'
24
28
  import { VisuallyHiddenRangeInput } from '../_util/VisuallyHiddenRangeInput'
25
29
 
26
- import { type PointRegistration, usePointsEditorContext } from './context'
30
+ import { usePointsEditorContext } from './context'
27
31
 
28
32
  import type { CSSVariables } from '../../css-variables'
29
33
 
30
- export type PointBaseType = { x: number; y: number }
31
-
32
- export function clampPoint(
33
- point: PointBaseType,
34
- min?: Partial<PointBaseType>,
35
- max?: Partial<PointBaseType>,
36
- ) {
37
- const { x, y } = point
38
- const newX = clamp(x, min?.x ?? 0, max?.x ?? 1)
39
- const newY = clamp(y, min?.y ?? 0, max?.y ?? 1)
40
- return { x: newX, y: newY }
41
- }
42
-
43
- export interface PointsEditorPointProps<T extends PointBaseType> {
34
+ export interface PointsEditorPointProps<T extends PointPosition> {
44
35
  /**
45
36
  * Where the point is, as `{ x, y }` from 0 to 1 on each axis, with `y`
46
37
  * growing downwards.
@@ -53,23 +44,17 @@ export interface PointsEditorPointProps<T extends PointBaseType> {
53
44
  */
54
45
  id?: string
55
46
  /** The lowest position the point can take, per axis. An axis left out is 0. */
56
- min?: Partial<PointBaseType>
47
+ min?: Partial<PointPosition>
57
48
  /** The highest position the point can take, per axis. An axis left out is 1. */
58
- max?: Partial<PointBaseType>
49
+ max?: Partial<PointPosition>
59
50
 
60
- /** Width and height at once. Sets both `--width` and `--height`. */
61
- size?: number | string
62
- /** Sets `--width`; the size the theme gives it stands when omitted. */
63
- width?: number | string
64
- /** Sets `--height`; the size the theme gives it stands when omitted. */
65
- height?: number | string
66
51
  /** Sets `--color`, for the theme to colour the point with. */
67
52
  color?: string
68
53
 
69
54
  /** Overrides the `disabled` of `PointsEditor.Root`. */
70
55
  disabled?: boolean
71
- /** Overrides the `readonly` of `PointsEditor.Root`. */
72
- readonly?: boolean
56
+ /** Overrides the `readOnly` of `PointsEditor.Root`. */
57
+ readOnly?: boolean
73
58
 
74
59
  /** Overrides the `wheel` of `PointsEditor.Root`. */
75
60
  wheel?: ModifierValue<InputEventOption> | null
@@ -88,22 +73,30 @@ export interface PointsEditorPointProps<T extends PointBaseType> {
88
73
  * Called with the new position when the point is dragged or moved by the
89
74
  * arrow keys or the wheel, including when it moves along with a selection.
90
75
  */
91
- onChange?: (value: PointBaseType) => void
92
- /** Called when a drag on this point starts, with where the point is. */
93
- onDragStart?: (value: PointBaseType) => void
94
- /** Called when that drag ends, with where the point is. */
95
- onDragEnd?: (value: PointBaseType) => void
76
+ onChange?: (value: PointPosition) => void
77
+ /**
78
+ * Called when a change of this point starts — a drag on it, the first wheel
79
+ * notch or arrow key while it has the focus — with where it is and what the
80
+ * change is made with. The points that move along with a selection report
81
+ * through `onChange` only: the change belongs to the point being operated.
82
+ */
83
+ onChangeStart?: (value: PointPosition, source: ChangeSource) => void
84
+ /**
85
+ * Called when that change ends, with where the point is: on release, or
86
+ * `changeEndDelay` after the last wheel notch or arrow key.
87
+ */
88
+ onChangeEnd?: (value: PointPosition, source: ChangeSource) => void
89
+ /**
90
+ * How long after the last wheel notch or arrow key the change counts as
91
+ * over, in milliseconds. Neither has an event that says it is done.
92
+ *
93
+ * @default 500
94
+ */
95
+ changeEndDelay?: number
96
96
 
97
- style?: CSSProperties &
98
- CSSVariables<'width' | 'height' | 'color' | 'translate'>
97
+ style?: CSSProperties & CSSVariables<'color' | 'translate'>
99
98
  }
100
99
 
101
- /**
102
- * A point is placed by its position within the container, so its value is a
103
- * position: 0..1 on each axis, with y growing downwards.
104
- */
105
- export const AXIS = { min: 0, max: 1 }
106
-
107
100
  /**
108
101
  * One setting for both axes, or one per axis. The point holds a range input
109
102
  * for each, so anything that names or describes it comes in a pair.
@@ -115,27 +108,25 @@ function perAxis(
115
108
  return typeof value === 'string' ? value : value?.[axis]
116
109
  }
117
110
 
118
- export function Point<T extends PointBaseType>({
111
+ export function Point<T extends PointPosition>({
119
112
  value,
120
113
  children,
121
114
  id: idProp,
122
115
  min,
123
116
  max,
124
- size,
125
- width,
126
- height,
127
117
  color,
128
118
 
129
119
  disabled: _disabled,
130
- readonly: _readonly,
120
+ readOnly: _readOnly,
131
121
  wheel: _wheel,
132
122
  keyboard: _keyboard,
133
123
  'aria-label': ariaLabel,
134
124
  'aria-valuetext': ariaValuetext,
135
125
 
136
126
  onChange,
137
- onDragStart,
138
- onDragEnd,
127
+ onChangeStart,
128
+ onChangeEnd,
129
+ changeEndDelay,
139
130
 
140
131
  className,
141
132
  style,
@@ -147,17 +138,14 @@ export function Point<T extends PointBaseType>({
147
138
  Omit<ComponentPropsWithoutRef<'div'>, keyof PointsEditorPointProps<T>>) {
148
139
  const {
149
140
  containerRef,
150
- externalStyles,
141
+ dragCursor,
151
142
  disabled: rootDisabled,
152
- readonly: rootReadonly,
143
+ readOnly: rootReadOnly,
153
144
  wheel: rootWheel,
154
145
  keyboard: rootKeyboard,
155
146
  dragSensitivity,
156
147
  selection,
157
- registerPoint,
158
- beginPointDrag,
159
- movePointDrag,
160
- nudgeSelection,
148
+ editor,
161
149
  } = usePointsEditorContext()
162
150
 
163
151
  const generatedId = useId()
@@ -165,8 +153,8 @@ export function Point<T extends PointBaseType>({
165
153
  const selected = selection.includes(id)
166
154
 
167
155
  const disabled = _disabled ?? rootDisabled
168
- const readonly = _readonly ?? rootReadonly
169
- const inactive = disabled || readonly
156
+ const readOnly = _readOnly ?? rootReadOnly
157
+ const inactive = disabled || readOnly
170
158
  // `null` means "no event" and has to survive the fallback, so `??` is not
171
159
  // enough: only an omitted prop inherits from the root.
172
160
  const wheel = _wheel === undefined ? rootWheel : _wheel
@@ -174,6 +162,19 @@ export function Point<T extends PointBaseType>({
174
162
 
175
163
  useCheckPlacement('PointsEditor.Point', 'PointsEditor.Container')
176
164
 
165
+ const current = clampPoint(value, min, max)
166
+ const gesture = useChangeGesture(
167
+ current,
168
+ { onChangeStart, onChangeEnd, changeEndDelay },
169
+ inactive,
170
+ )
171
+ // Every move reaches the point through here, whatever moved it, so the
172
+ // last one is what `onChangeEnd` gives.
173
+ const handleChange = (next: PointPosition) => {
174
+ gesture.changed(next)
175
+ onChange?.(next)
176
+ }
177
+
177
178
  // Compared against the focus below, so the point needs its own element.
178
179
  const [element, setElement] = useState<HTMLDivElement | null>(null)
179
180
  const xInputRef = useRef<HTMLInputElement>(null)
@@ -182,14 +183,15 @@ export function Point<T extends PointBaseType>({
182
183
  // What the editor needs to move this point along with the rest of a
183
184
  // selection. Rewritten after every render rather than kept in the registry
184
185
  // itself: the value changes on every frame of a drag.
185
- const registration = useRef<PointRegistration>({
186
+ const registration = useRef<PointsEditorPoint>({
186
187
  value,
187
188
  min,
188
189
  max,
189
190
  readonly: inactive,
190
- onChange,
191
+ onChange: onChange ? handleChange : undefined,
191
192
  element,
192
193
  wheel,
194
+ beforeWheel: () => gesture.pulse('wheel'),
193
195
  })
194
196
  useEffect(() => {
195
197
  registration.current = {
@@ -197,25 +199,29 @@ export function Point<T extends PointBaseType>({
197
199
  min,
198
200
  max,
199
201
  readonly: inactive,
200
- onChange,
202
+ onChange: onChange ? handleChange : undefined,
201
203
  element,
202
204
  wheel,
205
+ beforeWheel: () => gesture.pulse('wheel'),
203
206
  }
204
207
  })
205
208
 
206
- useEffect(() => registerPoint(id, registration), [id, registerPoint])
209
+ useEffect(
210
+ () => editor.registerPoint(id, () => registration.current),
211
+ [id, editor],
212
+ )
207
213
 
208
214
  /** Where the pointer was when the drag started, to measure the move from. */
209
- const pointerOrigin = useRef<PointBaseType | null>(null)
215
+ const pointerOrigin = useRef<PointPosition | null>(null)
210
216
 
211
217
  // The value is the position itself: no scaling, and no rounding to a step.
212
218
  const { refCallback: dragRefCallback, dragging } =
213
219
  useDragValue<HTMLDivElement>({
214
- axis: AXIS,
220
+ axis: POINT_AXIS,
215
221
  baseElementRef: containerRef,
216
222
  sensitivity: (state) =>
217
223
  selectModifier(dragSensitivity, state.event).value,
218
- cursor: inactive ? undefined : externalStyles.cursor,
224
+ cursor: inactive ? undefined : dragCursor,
219
225
  shouldStart: () => !disabled,
220
226
  // The value is a move rather than a position: the point keeps the offset
221
227
  // it was grabbed at, and everything else selected moves with it by the
@@ -223,68 +229,48 @@ export function Point<T extends PointBaseType>({
223
229
  onChange: ([x, y]) => {
224
230
  const origin = pointerOrigin.current
225
231
  if (!origin) return
226
- movePointDrag({ x: x - origin.x, y: y - origin.y })
232
+ editor.movePointDrag({ x: x - origin.x, y: y - origin.y })
227
233
  },
228
234
  onDragStart: ([x, y], state) => {
229
235
  // The press decides the selection before the snapshot is taken, so it
230
236
  // happens here rather than in an onPointerDown: a native listener on
231
237
  // the element runs before React's, and the two would disagree.
232
- beginPointDrag(id, state.event)
238
+ editor.beginPointDrag(id, state.event)
233
239
  pointerOrigin.current = { x, y }
234
240
  xInputRef.current?.focus()
235
241
 
236
- if (inactive) return
237
- onDragStart?.(clampPoint(value, min, max))
242
+ if (!inactive) gesture.hold('pointer')
238
243
  },
239
244
  onDragEnd: () => {
240
245
  pointerOrigin.current = null
241
-
242
- if (inactive) return
243
- onDragEnd?.(clampPoint(value, min, max))
246
+ gesture.end()
244
247
  },
245
248
  })
246
249
 
247
- const nudge = useCallback(
248
- (
249
- axis: 'x' | 'y',
250
- direction: number,
251
- option: ModifierValue<InputEventOption>,
252
- modifiers: ModifierState,
253
- ) => {
254
- const next = applyDelta(value[axis], direction, option, AXIS, modifiers)
255
- // As a move, so that the rest of the selection comes along and the whole
256
- // group stops together at the edge.
257
- nudgeSelection(id, {
258
- x: axis === 'x' ? next - value.x : 0,
259
- y: axis === 'y' ? next - value.y : 0,
260
- })
261
- },
262
- [value, id, nudgeSelection],
263
- )
264
-
265
250
  const refCallback = useComposedRefs<HTMLDivElement>(
266
251
  dragRefCallback,
267
252
  setElement,
268
253
  )
269
254
 
270
255
  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
271
- const key = event.key
272
- if (!['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown'].includes(key))
273
- return
274
- // The key picks the axis, whichever of the two inputs holds the focus: a
275
- // point is one control to the person moving it, and the focus lands on the
276
- // x input, so reading the axis off the input would leave the y axis with
277
- // no keys at all.
278
- const axis = key === 'ArrowRight' || key === 'ArrowLeft' ? 'x' : 'y'
256
+ // The key picks the axis, whichever of the two inputs holds the focus:
257
+ // the focus lands on the x input, so reading the axis off the input would
258
+ // leave the y axis with no keys at all. y grows downwards, so ArrowUp
259
+ // moves the point towards 0.
260
+ const move = arrowKeyMove(event.key)
261
+ if (!move) return
279
262
  event.preventDefault()
280
263
  if (!onChange || inactive || !keyboard) return
281
- // y grows downwards, so ArrowUp moves the point towards 0.
282
- const direction = key === 'ArrowLeft' || key === 'ArrowUp' ? -1 : 1
283
- nudge(axis, direction, keyboard, event)
264
+ gesture.pulse('keyboard')
265
+ editor.nudgePoint(
266
+ id,
267
+ move.axis === 0 ? 'x' : 'y',
268
+ move.direction,
269
+ keyboard,
270
+ event,
271
+ )
284
272
  }
285
273
 
286
- const current = clampPoint(value, min, max)
287
-
288
274
  return (
289
275
  // The visual point has two values, so its semantics live on the two range
290
276
  // inputs nested inside it rather than on this drag handle.
@@ -293,7 +279,7 @@ export function Point<T extends PointBaseType>({
293
279
  ref={refCallback}
294
280
  className={className}
295
281
  data-disabled={disabled ? '' : undefined}
296
- data-readonly={readonly ? '' : undefined}
282
+ data-readonly={readOnly ? '' : undefined}
297
283
  // A press lands on the point, which cannot hold focus, and the browser
298
284
  // answers that by clearing the focus to the body — undoing the focus the
299
285
  // drag just gave the input. Taking the focus here keeps it inside, and
@@ -304,8 +290,6 @@ export function Point<T extends PointBaseType>({
304
290
  style={
305
291
  {
306
292
  '--color': color,
307
- '--width': cssLength(size ?? width),
308
- '--height': cssLength(size ?? height),
309
293
  // The mechanics of the position below: a point is placed by its
310
294
  // position in the container, measured from its own centre.
311
295
  position: 'absolute',
@@ -341,17 +325,17 @@ export function Point<T extends PointBaseType>({
341
325
  max={max?.[axis] ?? 1}
342
326
  step="any"
343
327
  disabled={disabled}
344
- aria-readonly={readonly}
328
+ aria-readonly={readOnly}
345
329
  aria-orientation={axis === 'x' ? 'horizontal' : 'vertical'}
346
330
  aria-label={perAxis(ariaLabel, axis) ?? axis}
347
331
  aria-valuetext={perAxis(ariaValuetext, axis)}
348
332
  onChange={(event) => {
349
- if (readonly) {
333
+ if (readOnly) {
350
334
  event.currentTarget.value = String(current[axis])
351
335
  return
352
336
  }
353
337
  const delta = event.currentTarget.valueAsNumber - value[axis]
354
- nudgeSelection(id, {
338
+ editor.nudgeSelection(id, {
355
339
  x: axis === 'x' ? delta : 0,
356
340
  y: axis === 'y' ? delta : 0,
357
341
  })
@@ -2,91 +2,39 @@ import { createContext, CSSProperties, RefObject, useContext } from 'react'
2
2
 
3
3
  import {
4
4
  type InputEventOption,
5
- type ModifierState,
6
5
  type ModifierValue,
6
+ type PointsEditorInstance,
7
7
  type SelectionBoxRect,
8
8
  } from '@tremolo-ui/dom'
9
9
 
10
- import type { PointBaseType } from './Point'
11
-
12
- /**
13
- * What a `Point` tells the editor about itself, so that a selection can be
14
- * moved without the editor knowing how the points are stored.
15
- *
16
- * Held behind a ref and rewritten on every render: the value changes on every
17
- * frame of a drag, and a registry keyed on it would be rebuilt just as often.
18
- */
19
- export interface PointRegistration {
20
- value: PointBaseType
21
- min?: Partial<PointBaseType>
22
- max?: Partial<PointBaseType>
23
- readonly: boolean
24
- onChange?: (value: PointBaseType) => void
25
- /** The point's own element, to match the focus against. */
26
- element: HTMLElement | null
27
- /** The wheel option this point resolved, root inheritance applied. */
28
- wheel: ModifierValue<InputEventOption> | null
29
- }
30
-
31
10
  export type PointsEditorContextValue = {
32
11
  disabled: boolean
33
- readonly: boolean
12
+ readOnly: boolean
34
13
  /** Inherited by every `Point`; `null` turns the wheel off. */
35
14
  wheel: ModifierValue<InputEventOption> | null
36
15
  /** Inherited by every `Point`; `null` turns the keyboard off. */
37
16
  keyboard: ModifierValue<InputEventOption> | null
38
17
  /** Inherited by every `Point`. See `PointsEditorProps.dragSensitivity`. */
39
18
  dragSensitivity: ModifierValue<number>
40
- externalStyles: {
41
- cursor?: CSSProperties['cursor']
42
- }
43
-
19
+ /** Inherited by every `Point`. See `PointsEditorProps.dragCursor`. */
20
+ dragCursor: CSSProperties['cursor']
44
21
  /**
45
22
  * `Container` registers its element here; `Point` normalizes the pointer
46
23
  * against it, so a point is placed by its position within the container.
47
24
  */
48
25
  containerRef: RefObject<HTMLDivElement | null>
49
-
50
26
  /** Whether points can be selected at all. See `PointsEditorProps.selectable`. */
51
27
  selectable: boolean
52
28
  /** Ids of the points currently selected. Always empty while `selectable` is off. */
53
29
  selection: readonly string[]
54
- /** Register a point so that a selection can move it with the rest. */
55
- registerPoint: (id: string, entry: RefObject<PointRegistration>) => () => void
56
30
  /**
57
- * Whether the element is a point, or inside one. The registry knows which
58
- * elements the points are, so nothing has to be recognised by a class name.
31
+ * The selection and the moves, from `createPointsEditor` in
32
+ * `@tremolo-ui/dom`: every `Point` registers with it, and a press, a drag,
33
+ * a key or the wheel goes through it so that a selection moves as one.
59
34
  */
60
- isPointElement: (element: Element | null | undefined) => boolean
61
- /**
62
- * A pointer went down on a point: works out the new selection and takes the
63
- * snapshot the move will be measured against.
64
- */
65
- beginPointDrag: (id: string, modifiers: ModifierState) => void
66
- /** Move everything the drag picked up, by one amount, clamped as one. */
67
- movePointDrag: (delta: PointBaseType) => void
68
- /**
69
- * Move the selection by an amount that did not come from a drag — an arrow
70
- * key or a wheel notch. The current values are the starting point.
71
- */
72
- nudgeSelection: (id: string, delta: PointBaseType) => void
73
- /**
74
- * Move the point that currently holds focus by one notch. The wheel listener
75
- * belongs to `Container` rather than to every `Point`, so the lookup of who
76
- * has focus happens here, where the registry is. Returns whether a point
77
- * took it, so the caller knows whether to consume the event.
78
- */
79
- nudgeFocusedPoint: (
80
- axis: 'x' | 'y',
81
- direction: number,
82
- modifiers: ModifierState,
83
- ) => boolean
84
-
35
+ editor: PointsEditorInstance
85
36
  /** The selection box, while one is being dragged. */
86
37
  selectionBox: SelectionBoxRect | null
87
- beginSelectionBox: (at: PointBaseType, modifiers: ModifierState) => void
88
- moveSelectionBox: (to: PointBaseType) => void
89
- endSelectionBox: () => void
90
38
  }
91
39
 
92
40
  const PointsEditorContext =