@atlaskit/editor-plugin-placeholder 3.1.0 → 3.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @atlaskit/editor-plugin-placeholder
2
2
 
3
+ ## 3.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`bc9e17e2197a9`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/bc9e17e2197a9) -
8
+ [ux] add new setAnimatingPlaceholderPrompts API to placeholder plugin
9
+
10
+ ## 3.1.1
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies
15
+
3
16
  ## 3.1.0
4
17
 
5
18
  ### Minor Changes
@@ -13,6 +13,12 @@ var _state = require("@atlaskit/editor-prosemirror/state");
13
13
  var _utils2 = require("@atlaskit/editor-prosemirror/utils");
14
14
  var _view = require("@atlaskit/editor-prosemirror/view");
15
15
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
16
+ // Typewriter animation timing constants
17
+ var TYPEWRITER_TYPE_DELAY = 50; // Delay between typing each character
18
+ var TYPEWRITER_PAUSE_BEFORE_ERASE = 2000; // Pause before starting to erase text
19
+ var TYPEWRITER_ERASE_DELAY = 40; // Delay between erasing each character
20
+ var TYPEWRITER_CYCLE_DELAY = 500; // Delay before starting next cycle
21
+
16
22
  var pluginKey = exports.pluginKey = new _state.PluginKey('placeholderPlugin');
17
23
  function getPlaceholderState(editorState) {
18
24
  return pluginKey.getState(editorState);
@@ -20,10 +26,57 @@ function getPlaceholderState(editorState) {
20
26
  var placeholderTestId = exports.placeholderTestId = 'placeholder-test-id';
21
27
  var nodeTypesWithLongPlaceholderText = ['expand', 'panel'];
22
28
  var nodeTypesWithShortPlaceholderText = ['tableCell', 'tableHeader'];
23
- function createPlaceholderDecoration(editorState, placeholderText) {
24
- var pos = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
29
+ var createTypewriterElement = function createTypewriterElement(placeholderPrompts, activeTypewriterTimeouts) {
30
+ var typewriterElement = document.createElement('span');
31
+ var currentPromptIndex = 0;
32
+ var displayedText = '';
33
+ var animationTimeouts = [];
34
+ var clearAllTimeouts = function clearAllTimeouts() {
35
+ animationTimeouts.forEach(function (timeoutId) {
36
+ return clearTimeout(timeoutId);
37
+ });
38
+ animationTimeouts = [];
39
+ };
40
+ var scheduleTimeout = function scheduleTimeout(callback, delay) {
41
+ var timeoutId = setTimeout(callback, delay);
42
+ animationTimeouts.push(timeoutId);
43
+ return timeoutId;
44
+ };
45
+ var _startAnimationCycle = function startAnimationCycle() {
46
+ var currentPrompt = placeholderPrompts[currentPromptIndex];
47
+ var characterIndex = 0;
48
+ var _typeNextCharacter = function typeNextCharacter() {
49
+ if (characterIndex < currentPrompt.length) {
50
+ displayedText = currentPrompt.substring(0, characterIndex + 1);
51
+ typewriterElement.textContent = displayedText;
52
+ characterIndex++;
53
+ scheduleTimeout(_typeNextCharacter, TYPEWRITER_TYPE_DELAY);
54
+ } else {
55
+ scheduleTimeout(_eraseLastCharacter, TYPEWRITER_PAUSE_BEFORE_ERASE);
56
+ }
57
+ };
58
+ var _eraseLastCharacter = function eraseLastCharacter() {
59
+ if (displayedText.length > 0) {
60
+ displayedText = displayedText.substring(0, displayedText.length - 1);
61
+ typewriterElement.textContent = displayedText;
62
+ scheduleTimeout(_eraseLastCharacter, TYPEWRITER_ERASE_DELAY);
63
+ } else {
64
+ // Move to next prompt. Modulo lets us cycle infinitely
65
+ currentPromptIndex = (currentPromptIndex + 1) % placeholderPrompts.length;
66
+ scheduleTimeout(_startAnimationCycle, TYPEWRITER_CYCLE_DELAY);
67
+ }
68
+ };
69
+ _typeNextCharacter();
70
+ };
71
+ activeTypewriterTimeouts === null || activeTypewriterTimeouts === void 0 || activeTypewriterTimeouts.push(clearAllTimeouts);
72
+ _startAnimationCycle();
73
+ return typewriterElement;
74
+ };
75
+ function createPlaceholderDecoration(editorState, placeholderText, placeholderPrompts, activeTypewriterTimeouts) {
76
+ var pos = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
25
77
  var placeholderDecoration = document.createElement('span');
26
78
  var placeholderNodeWithText = placeholderDecoration;
79
+ var typewriterElement = null;
27
80
  placeholderDecoration.setAttribute('data-testid', placeholderTestId);
28
81
  placeholderDecoration.className = 'placeholder-decoration';
29
82
 
@@ -35,7 +88,12 @@ function createPlaceholderDecoration(editorState, placeholderText) {
35
88
  placeholderDecoration.appendChild(placeholderNode);
36
89
  placeholderNodeWithText = placeholderNode;
37
90
  }
38
- placeholderNodeWithText.textContent = placeholderText || ' ';
91
+ if (placeholderPrompts) {
92
+ typewriterElement = createTypewriterElement(placeholderPrompts, activeTypewriterTimeouts);
93
+ placeholderNodeWithText.appendChild(typewriterElement);
94
+ } else {
95
+ placeholderNodeWithText.textContent = placeholderText || ' ';
96
+ }
39
97
 
40
98
  // ME-2289 Tapping on backspace in empty editor hides and displays the keyboard
41
99
  // Add a editable buff node as the cursor moving forward is inevitable
@@ -59,17 +117,19 @@ function createPlaceholderDecoration(editorState, placeholderText) {
59
117
  key: "placeholder ".concat(placeholderText)
60
118
  })]);
61
119
  }
62
- function setPlaceHolderState(placeholderText, pos) {
120
+ function setPlaceHolderState(placeholderText, pos, placeholderPrompts) {
63
121
  return {
64
122
  hasPlaceholder: true,
65
123
  placeholderText: placeholderText,
124
+ placeholderPrompts: placeholderPrompts,
66
125
  pos: pos ? pos : 1
67
126
  };
68
127
  }
69
- var emptyPlaceholder = function emptyPlaceholder(placeholderText) {
128
+ var emptyPlaceholder = function emptyPlaceholder(placeholderText, placeholderPrompts) {
70
129
  return {
71
130
  hasPlaceholder: false,
72
- placeholderText: placeholderText
131
+ placeholderText: placeholderText,
132
+ placeholderPrompts: placeholderPrompts
73
133
  };
74
134
  };
75
135
  function createPlaceHolderStateFrom(_ref) {
@@ -79,12 +139,13 @@ function createPlaceHolderStateFrom(_ref) {
79
139
  defaultPlaceholderText = _ref.defaultPlaceholderText,
80
140
  intl = _ref.intl,
81
141
  bracketPlaceholderText = _ref.bracketPlaceholderText,
82
- emptyLinePlaceholder = _ref.emptyLinePlaceholder;
142
+ emptyLinePlaceholder = _ref.emptyLinePlaceholder,
143
+ placeholderPrompts = _ref.placeholderPrompts;
83
144
  if (isTypeAheadOpen !== null && isTypeAheadOpen !== void 0 && isTypeAheadOpen(editorState)) {
84
- return emptyPlaceholder(defaultPlaceholderText);
145
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
85
146
  }
86
- if (defaultPlaceholderText && (0, _utils.isEmptyDocument)(editorState.doc)) {
87
- return setPlaceHolderState(defaultPlaceholderText);
147
+ if ((defaultPlaceholderText || placeholderPrompts) && (0, _utils.isEmptyDocument)(editorState.doc)) {
148
+ return setPlaceHolderState(defaultPlaceholderText, 1, placeholderPrompts);
88
149
  }
89
150
  if (isEditorFocused && (0, _experiments.editorExperiment)('platform_editor_controls', 'variant1')) {
90
151
  var _parentNode$firstChil, _parentNode$firstChil2;
@@ -92,7 +153,7 @@ function createPlaceHolderStateFrom(_ref) {
92
153
  $from = _editorState$selectio.$from,
93
154
  $to = _editorState$selectio.$to;
94
155
  if ($from.pos !== $to.pos) {
95
- return emptyPlaceholder(defaultPlaceholderText);
156
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
96
157
  }
97
158
  var parentNode = $from.node($from.depth - 1);
98
159
  var parentType = parentNode === null || parentNode === void 0 ? void 0 : parentNode.type.name;
@@ -109,7 +170,7 @@ function createPlaceHolderStateFrom(_ref) {
109
170
  return node.type === editorState.schema.nodes.table;
110
171
  })(editorState.selection);
111
172
  if (!table) {
112
- return emptyPlaceholder(defaultPlaceholderText);
173
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
113
174
  }
114
175
  var isFirstCell = (table === null || table === void 0 || (_table$node$firstChil = table.node.firstChild) === null || _table$node$firstChil === void 0 ? void 0 : _table$node$firstChil.content.firstChild) === parentNode;
115
176
  if (isFirstCell) {
@@ -119,20 +180,28 @@ function createPlaceHolderStateFrom(_ref) {
119
180
  if (nodeTypesWithLongPlaceholderText.includes(parentType) && isEmptyNode) {
120
181
  return setPlaceHolderState(intl.formatMessage(_messages.placeholderTextMessages.longEmptyNodePlaceholderText), $from.pos);
121
182
  }
122
- return emptyPlaceholder(defaultPlaceholderText);
183
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
123
184
  }
124
185
  if (bracketPlaceholderText && (0, _utils.bracketTyped)(editorState) && isEditorFocused) {
125
186
  var _$from = editorState.selection.$from;
126
187
  // Space is to account for positioning of the bracket
127
188
  var bracketHint = ' ' + bracketPlaceholderText;
128
- return setPlaceHolderState(bracketHint, _$from.pos - 1);
189
+ return setPlaceHolderState(bracketHint, _$from.pos - 1, placeholderPrompts);
129
190
  }
130
- return emptyPlaceholder(defaultPlaceholderText);
191
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
131
192
  }
132
- function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, api) {
133
- if (!defaultPlaceholderText && !bracketPlaceholderText) {
193
+ function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, api) {
194
+ if (!defaultPlaceholderText && !placeholderPrompts && !bracketPlaceholderText) {
134
195
  return;
135
196
  }
197
+ var isDestroyed = false;
198
+ var activeTypewriterTimeouts = [];
199
+ var clearAllTypewriterTimeouts = function clearAllTypewriterTimeouts() {
200
+ activeTypewriterTimeouts.forEach(function (clearFn) {
201
+ return clearFn();
202
+ });
203
+ activeTypewriterTimeouts = [];
204
+ };
136
205
  return new _safePlugin.SafePlugin({
137
206
  key: pluginKey,
138
207
  state: {
@@ -145,34 +214,39 @@ function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, empt
145
214
  defaultPlaceholderText: defaultPlaceholderText,
146
215
  bracketPlaceholderText: bracketPlaceholderText,
147
216
  emptyLinePlaceholder: emptyLinePlaceholder,
217
+ placeholderPrompts: placeholderPrompts,
148
218
  intl: intl
149
219
  });
150
220
  },
151
221
  apply: function apply(tr, placeholderState, _oldEditorState, newEditorState) {
152
- var _api$focus2, _api$typeAhead3, _placeholderState$pla;
222
+ var _api$focus2, _api$typeAhead2, _ref2, _meta$placeholderText, _ref3, _meta$placeholderProm, _api$typeAhead3, _placeholderState$pla, _placeholderState$pla2;
153
223
  var meta = tr.getMeta(pluginKey);
154
224
  var isEditorFocused = Boolean(api === null || api === void 0 || (_api$focus2 = api.focus) === null || _api$focus2 === void 0 || (_api$focus2 = _api$focus2.sharedState.currentState()) === null || _api$focus2 === void 0 ? void 0 : _api$focus2.hasFocus);
155
- if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined) {
156
- var _api$typeAhead2;
157
- return createPlaceHolderStateFrom({
158
- isEditorFocused: isEditorFocused,
159
- editorState: newEditorState,
160
- isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
161
- defaultPlaceholderText: meta.placeholderText,
162
- bracketPlaceholderText: bracketPlaceholderText,
163
- emptyLinePlaceholder: emptyLinePlaceholder,
164
- intl: intl
165
- });
166
- }
167
- return createPlaceHolderStateFrom({
225
+ var newPlaceholderState = (meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined || (meta === null || meta === void 0 ? void 0 : meta.placeholderPrompts) !== undefined ? createPlaceHolderStateFrom({
226
+ isEditorFocused: isEditorFocused,
227
+ editorState: newEditorState,
228
+ isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
229
+ defaultPlaceholderText: (_ref2 = (_meta$placeholderText = meta.placeholderText) !== null && _meta$placeholderText !== void 0 ? _meta$placeholderText : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _ref2 !== void 0 ? _ref2 : defaultPlaceholderText,
230
+ bracketPlaceholderText: bracketPlaceholderText,
231
+ emptyLinePlaceholder: emptyLinePlaceholder,
232
+ placeholderPrompts: (_ref3 = (_meta$placeholderProm = meta.placeholderPrompts) !== null && _meta$placeholderProm !== void 0 ? _meta$placeholderProm : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _ref3 !== void 0 ? _ref3 : placeholderPrompts,
233
+ intl: intl
234
+ }) : createPlaceHolderStateFrom({
168
235
  isEditorFocused: isEditorFocused,
169
236
  editorState: newEditorState,
170
237
  isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead3 = api.typeAhead) === null || _api$typeAhead3 === void 0 ? void 0 : _api$typeAhead3.actions.isOpen,
171
238
  defaultPlaceholderText: (_placeholderState$pla = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _placeholderState$pla !== void 0 ? _placeholderState$pla : defaultPlaceholderText,
172
239
  bracketPlaceholderText: bracketPlaceholderText,
173
240
  emptyLinePlaceholder: emptyLinePlaceholder,
241
+ placeholderPrompts: (_placeholderState$pla2 = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _placeholderState$pla2 !== void 0 ? _placeholderState$pla2 : placeholderPrompts,
174
242
  intl: intl
175
243
  });
244
+
245
+ // Clear timeouts when hasPlaceholder becomes false
246
+ if (!newPlaceholderState.hasPlaceholder) {
247
+ clearAllTypewriterTimeouts();
248
+ }
249
+ return newPlaceholderState;
176
250
  }
177
251
  },
178
252
  props: {
@@ -182,26 +256,40 @@ function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, empt
182
256
  hasPlaceholder = _getPlaceholderState.hasPlaceholder,
183
257
  placeholderText = _getPlaceholderState.placeholderText,
184
258
  pos = _getPlaceholderState.pos;
259
+
260
+ // Decorations is still called after plugin is destroyed
261
+ // So we need to make sure decorations is not called if plugin has been destroyed to prevent the placeholder animations' setTimeouts called infinitely
262
+ if (isDestroyed) {
263
+ return;
264
+ }
185
265
  var compositionPluginState = api === null || api === void 0 || (_api$composition = api.composition) === null || _api$composition === void 0 ? void 0 : _api$composition.sharedState.currentState();
186
266
  var isShowingDiff = Boolean(api === null || api === void 0 || (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 || (_api$showDiff = _api$showDiff.sharedState.currentState()) === null || _api$showDiff === void 0 ? void 0 : _api$showDiff.isDisplayingChanges);
187
- if (hasPlaceholder && placeholderText && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
188
- return createPlaceholderDecoration(editorState, placeholderText, pos);
267
+ if (hasPlaceholder && ((placeholderText !== null && placeholderText !== void 0 ? placeholderText : '') || placeholderPrompts) && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
268
+ return createPlaceholderDecoration(editorState, placeholderText !== null && placeholderText !== void 0 ? placeholderText : '', placeholderPrompts, activeTypewriterTimeouts, pos);
189
269
  }
190
270
  return;
191
271
  }
272
+ },
273
+ view: function view() {
274
+ return {
275
+ destroy: function destroy() {
276
+ clearAllTypewriterTimeouts();
277
+ isDestroyed = true;
278
+ }
279
+ };
192
280
  }
193
281
  });
194
282
  }
195
- var placeholderPlugin = exports.placeholderPlugin = function placeholderPlugin(_ref2) {
196
- var options = _ref2.config,
197
- api = _ref2.api;
283
+ var placeholderPlugin = exports.placeholderPlugin = function placeholderPlugin(_ref4) {
284
+ var options = _ref4.config,
285
+ api = _ref4.api;
198
286
  var currentPlaceholder = options === null || options === void 0 ? void 0 : options.placeholder;
199
287
  return {
200
288
  name: 'placeholder',
201
289
  commands: {
202
290
  setPlaceholder: function setPlaceholder(placeholderText) {
203
- return function (_ref3) {
204
- var tr = _ref3.tr;
291
+ return function (_ref5) {
292
+ var tr = _ref5.tr;
205
293
  if (currentPlaceholder !== placeholderText) {
206
294
  currentPlaceholder = placeholderText;
207
295
  return tr.setMeta(pluginKey, {
@@ -210,14 +298,22 @@ var placeholderPlugin = exports.placeholderPlugin = function placeholderPlugin(_
210
298
  }
211
299
  return null;
212
300
  };
301
+ },
302
+ setAnimatingPlaceholderPrompts: function setAnimatingPlaceholderPrompts(placeholderPrompts) {
303
+ return function (_ref6) {
304
+ var tr = _ref6.tr;
305
+ return tr.setMeta(pluginKey, {
306
+ placeholderPrompts: placeholderPrompts
307
+ });
308
+ };
213
309
  }
214
310
  },
215
311
  pmPlugins: function pmPlugins() {
216
312
  return [{
217
313
  name: 'placeholder',
218
- plugin: function plugin(_ref4) {
219
- var getIntl = _ref4.getIntl;
220
- return createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, api);
314
+ plugin: function plugin(_ref7) {
315
+ var getIntl = _ref7.getIntl;
316
+ return createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, options && options.placeholderPrompts, api);
221
317
  }
222
318
  }];
223
319
  }
@@ -5,6 +5,12 @@ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
5
  import { findParentNode } from '@atlaskit/editor-prosemirror/utils';
6
6
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
+ // Typewriter animation timing constants
9
+ const TYPEWRITER_TYPE_DELAY = 50; // Delay between typing each character
10
+ const TYPEWRITER_PAUSE_BEFORE_ERASE = 2000; // Pause before starting to erase text
11
+ const TYPEWRITER_ERASE_DELAY = 40; // Delay between erasing each character
12
+ const TYPEWRITER_CYCLE_DELAY = 500; // Delay before starting next cycle
13
+
8
14
  export const pluginKey = new PluginKey('placeholderPlugin');
9
15
  function getPlaceholderState(editorState) {
10
16
  return pluginKey.getState(editorState);
@@ -12,9 +18,54 @@ function getPlaceholderState(editorState) {
12
18
  export const placeholderTestId = 'placeholder-test-id';
13
19
  const nodeTypesWithLongPlaceholderText = ['expand', 'panel'];
14
20
  const nodeTypesWithShortPlaceholderText = ['tableCell', 'tableHeader'];
15
- export function createPlaceholderDecoration(editorState, placeholderText, pos = 1) {
21
+ const createTypewriterElement = (placeholderPrompts, activeTypewriterTimeouts) => {
22
+ const typewriterElement = document.createElement('span');
23
+ let currentPromptIndex = 0;
24
+ let displayedText = '';
25
+ let animationTimeouts = [];
26
+ const clearAllTimeouts = () => {
27
+ animationTimeouts.forEach(timeoutId => clearTimeout(timeoutId));
28
+ animationTimeouts = [];
29
+ };
30
+ const scheduleTimeout = (callback, delay) => {
31
+ const timeoutId = setTimeout(callback, delay);
32
+ animationTimeouts.push(timeoutId);
33
+ return timeoutId;
34
+ };
35
+ const startAnimationCycle = () => {
36
+ const currentPrompt = placeholderPrompts[currentPromptIndex];
37
+ let characterIndex = 0;
38
+ const typeNextCharacter = () => {
39
+ if (characterIndex < currentPrompt.length) {
40
+ displayedText = currentPrompt.substring(0, characterIndex + 1);
41
+ typewriterElement.textContent = displayedText;
42
+ characterIndex++;
43
+ scheduleTimeout(typeNextCharacter, TYPEWRITER_TYPE_DELAY);
44
+ } else {
45
+ scheduleTimeout(eraseLastCharacter, TYPEWRITER_PAUSE_BEFORE_ERASE);
46
+ }
47
+ };
48
+ const eraseLastCharacter = () => {
49
+ if (displayedText.length > 0) {
50
+ displayedText = displayedText.substring(0, displayedText.length - 1);
51
+ typewriterElement.textContent = displayedText;
52
+ scheduleTimeout(eraseLastCharacter, TYPEWRITER_ERASE_DELAY);
53
+ } else {
54
+ // Move to next prompt. Modulo lets us cycle infinitely
55
+ currentPromptIndex = (currentPromptIndex + 1) % placeholderPrompts.length;
56
+ scheduleTimeout(startAnimationCycle, TYPEWRITER_CYCLE_DELAY);
57
+ }
58
+ };
59
+ typeNextCharacter();
60
+ };
61
+ activeTypewriterTimeouts === null || activeTypewriterTimeouts === void 0 ? void 0 : activeTypewriterTimeouts.push(clearAllTimeouts);
62
+ startAnimationCycle();
63
+ return typewriterElement;
64
+ };
65
+ export function createPlaceholderDecoration(editorState, placeholderText, placeholderPrompts, activeTypewriterTimeouts, pos = 1) {
16
66
  const placeholderDecoration = document.createElement('span');
17
67
  let placeholderNodeWithText = placeholderDecoration;
68
+ let typewriterElement = null;
18
69
  placeholderDecoration.setAttribute('data-testid', placeholderTestId);
19
70
  placeholderDecoration.className = 'placeholder-decoration';
20
71
 
@@ -26,7 +77,12 @@ export function createPlaceholderDecoration(editorState, placeholderText, pos =
26
77
  placeholderDecoration.appendChild(placeholderNode);
27
78
  placeholderNodeWithText = placeholderNode;
28
79
  }
29
- placeholderNodeWithText.textContent = placeholderText || ' ';
80
+ if (placeholderPrompts) {
81
+ typewriterElement = createTypewriterElement(placeholderPrompts, activeTypewriterTimeouts);
82
+ placeholderNodeWithText.appendChild(typewriterElement);
83
+ } else {
84
+ placeholderNodeWithText.textContent = placeholderText || ' ';
85
+ }
30
86
 
31
87
  // ME-2289 Tapping on backspace in empty editor hides and displays the keyboard
32
88
  // Add a editable buff node as the cursor moving forward is inevitable
@@ -50,16 +106,18 @@ export function createPlaceholderDecoration(editorState, placeholderText, pos =
50
106
  key: `placeholder ${placeholderText}`
51
107
  })]);
52
108
  }
53
- function setPlaceHolderState(placeholderText, pos) {
109
+ function setPlaceHolderState(placeholderText, pos, placeholderPrompts) {
54
110
  return {
55
111
  hasPlaceholder: true,
56
112
  placeholderText,
113
+ placeholderPrompts,
57
114
  pos: pos ? pos : 1
58
115
  };
59
116
  }
60
- const emptyPlaceholder = placeholderText => ({
117
+ const emptyPlaceholder = (placeholderText, placeholderPrompts) => ({
61
118
  hasPlaceholder: false,
62
- placeholderText
119
+ placeholderText,
120
+ placeholderPrompts
63
121
  });
64
122
  function createPlaceHolderStateFrom({
65
123
  isEditorFocused,
@@ -68,13 +126,14 @@ function createPlaceHolderStateFrom({
68
126
  defaultPlaceholderText,
69
127
  intl,
70
128
  bracketPlaceholderText,
71
- emptyLinePlaceholder
129
+ emptyLinePlaceholder,
130
+ placeholderPrompts
72
131
  }) {
73
132
  if (isTypeAheadOpen !== null && isTypeAheadOpen !== void 0 && isTypeAheadOpen(editorState)) {
74
- return emptyPlaceholder(defaultPlaceholderText);
133
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
75
134
  }
76
- if (defaultPlaceholderText && isEmptyDocument(editorState.doc)) {
77
- return setPlaceHolderState(defaultPlaceholderText);
135
+ if ((defaultPlaceholderText || placeholderPrompts) && isEmptyDocument(editorState.doc)) {
136
+ return setPlaceHolderState(defaultPlaceholderText, 1, placeholderPrompts);
78
137
  }
79
138
  if (isEditorFocused && editorExperiment('platform_editor_controls', 'variant1')) {
80
139
  var _parentNode$firstChil, _parentNode$firstChil2;
@@ -83,7 +142,7 @@ function createPlaceHolderStateFrom({
83
142
  $to
84
143
  } = editorState.selection;
85
144
  if ($from.pos !== $to.pos) {
86
- return emptyPlaceholder(defaultPlaceholderText);
145
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
87
146
  }
88
147
  const parentNode = $from.node($from.depth - 1);
89
148
  const parentType = parentNode === null || parentNode === void 0 ? void 0 : parentNode.type.name;
@@ -98,7 +157,7 @@ function createPlaceHolderStateFrom({
98
157
  var _table$node$firstChil;
99
158
  const table = findParentNode(node => node.type === editorState.schema.nodes.table)(editorState.selection);
100
159
  if (!table) {
101
- return emptyPlaceholder(defaultPlaceholderText);
160
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
102
161
  }
103
162
  const isFirstCell = (table === null || table === void 0 ? void 0 : (_table$node$firstChil = table.node.firstChild) === null || _table$node$firstChil === void 0 ? void 0 : _table$node$firstChil.content.firstChild) === parentNode;
104
163
  if (isFirstCell) {
@@ -108,7 +167,7 @@ function createPlaceHolderStateFrom({
108
167
  if (nodeTypesWithLongPlaceholderText.includes(parentType) && isEmptyNode) {
109
168
  return setPlaceHolderState(intl.formatMessage(messages.longEmptyNodePlaceholderText), $from.pos);
110
169
  }
111
- return emptyPlaceholder(defaultPlaceholderText);
170
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
112
171
  }
113
172
  if (bracketPlaceholderText && bracketTyped(editorState) && isEditorFocused) {
114
173
  const {
@@ -116,14 +175,20 @@ function createPlaceHolderStateFrom({
116
175
  } = editorState.selection;
117
176
  // Space is to account for positioning of the bracket
118
177
  const bracketHint = ' ' + bracketPlaceholderText;
119
- return setPlaceHolderState(bracketHint, $from.pos - 1);
178
+ return setPlaceHolderState(bracketHint, $from.pos - 1, placeholderPrompts);
120
179
  }
121
- return emptyPlaceholder(defaultPlaceholderText);
180
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
122
181
  }
123
- export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, api) {
124
- if (!defaultPlaceholderText && !bracketPlaceholderText) {
182
+ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, api) {
183
+ if (!defaultPlaceholderText && !placeholderPrompts && !bracketPlaceholderText) {
125
184
  return;
126
185
  }
186
+ let isDestroyed = false;
187
+ let activeTypewriterTimeouts = [];
188
+ const clearAllTypewriterTimeouts = () => {
189
+ activeTypewriterTimeouts.forEach(clearFn => clearFn());
190
+ activeTypewriterTimeouts = [];
191
+ };
127
192
  return new SafePlugin({
128
193
  key: pluginKey,
129
194
  state: {
@@ -136,34 +201,39 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
136
201
  defaultPlaceholderText,
137
202
  bracketPlaceholderText,
138
203
  emptyLinePlaceholder,
204
+ placeholderPrompts,
139
205
  intl
140
206
  });
141
207
  },
142
208
  apply: (tr, placeholderState, _oldEditorState, newEditorState) => {
143
- var _api$focus2, _api$focus2$sharedSta, _api$typeAhead3, _placeholderState$pla;
209
+ var _api$focus2, _api$focus2$sharedSta, _api$typeAhead2, _ref, _meta$placeholderText, _ref2, _meta$placeholderProm, _api$typeAhead3, _placeholderState$pla, _placeholderState$pla2;
144
210
  const meta = tr.getMeta(pluginKey);
145
211
  const isEditorFocused = Boolean(api === null || api === void 0 ? void 0 : (_api$focus2 = api.focus) === null || _api$focus2 === void 0 ? void 0 : (_api$focus2$sharedSta = _api$focus2.sharedState.currentState()) === null || _api$focus2$sharedSta === void 0 ? void 0 : _api$focus2$sharedSta.hasFocus);
146
- if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined) {
147
- var _api$typeAhead2;
148
- return createPlaceHolderStateFrom({
149
- isEditorFocused,
150
- editorState: newEditorState,
151
- isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
152
- defaultPlaceholderText: meta.placeholderText,
153
- bracketPlaceholderText,
154
- emptyLinePlaceholder,
155
- intl
156
- });
157
- }
158
- return createPlaceHolderStateFrom({
212
+ const newPlaceholderState = (meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined || (meta === null || meta === void 0 ? void 0 : meta.placeholderPrompts) !== undefined ? createPlaceHolderStateFrom({
213
+ isEditorFocused,
214
+ editorState: newEditorState,
215
+ isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
216
+ defaultPlaceholderText: (_ref = (_meta$placeholderText = meta.placeholderText) !== null && _meta$placeholderText !== void 0 ? _meta$placeholderText : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _ref !== void 0 ? _ref : defaultPlaceholderText,
217
+ bracketPlaceholderText,
218
+ emptyLinePlaceholder,
219
+ placeholderPrompts: (_ref2 = (_meta$placeholderProm = meta.placeholderPrompts) !== null && _meta$placeholderProm !== void 0 ? _meta$placeholderProm : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _ref2 !== void 0 ? _ref2 : placeholderPrompts,
220
+ intl
221
+ }) : createPlaceHolderStateFrom({
159
222
  isEditorFocused,
160
223
  editorState: newEditorState,
161
224
  isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead3 = api.typeAhead) === null || _api$typeAhead3 === void 0 ? void 0 : _api$typeAhead3.actions.isOpen,
162
225
  defaultPlaceholderText: (_placeholderState$pla = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _placeholderState$pla !== void 0 ? _placeholderState$pla : defaultPlaceholderText,
163
226
  bracketPlaceholderText,
164
227
  emptyLinePlaceholder,
228
+ placeholderPrompts: (_placeholderState$pla2 = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _placeholderState$pla2 !== void 0 ? _placeholderState$pla2 : placeholderPrompts,
165
229
  intl
166
230
  });
231
+
232
+ // Clear timeouts when hasPlaceholder becomes false
233
+ if (!newPlaceholderState.hasPlaceholder) {
234
+ clearAllTypewriterTimeouts();
235
+ }
236
+ return newPlaceholderState;
167
237
  }
168
238
  },
169
239
  props: {
@@ -174,13 +244,27 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
174
244
  placeholderText,
175
245
  pos
176
246
  } = getPlaceholderState(editorState);
247
+
248
+ // Decorations is still called after plugin is destroyed
249
+ // So we need to make sure decorations is not called if plugin has been destroyed to prevent the placeholder animations' setTimeouts called infinitely
250
+ if (isDestroyed) {
251
+ return;
252
+ }
177
253
  const compositionPluginState = api === null || api === void 0 ? void 0 : (_api$composition = api.composition) === null || _api$composition === void 0 ? void 0 : _api$composition.sharedState.currentState();
178
254
  const isShowingDiff = Boolean(api === null || api === void 0 ? void 0 : (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 ? void 0 : (_api$showDiff$sharedS = _api$showDiff.sharedState.currentState()) === null || _api$showDiff$sharedS === void 0 ? void 0 : _api$showDiff$sharedS.isDisplayingChanges);
179
- if (hasPlaceholder && placeholderText && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
180
- return createPlaceholderDecoration(editorState, placeholderText, pos);
255
+ if (hasPlaceholder && ((placeholderText !== null && placeholderText !== void 0 ? placeholderText : '') || placeholderPrompts) && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
256
+ return createPlaceholderDecoration(editorState, placeholderText !== null && placeholderText !== void 0 ? placeholderText : '', placeholderPrompts, activeTypewriterTimeouts, pos);
181
257
  }
182
258
  return;
183
259
  }
260
+ },
261
+ view() {
262
+ return {
263
+ destroy() {
264
+ clearAllTypewriterTimeouts();
265
+ isDestroyed = true;
266
+ }
267
+ };
184
268
  }
185
269
  });
186
270
  }
@@ -202,6 +286,13 @@ export const placeholderPlugin = ({
202
286
  });
203
287
  }
204
288
  return null;
289
+ },
290
+ setAnimatingPlaceholderPrompts: placeholderPrompts => ({
291
+ tr
292
+ }) => {
293
+ return tr.setMeta(pluginKey, {
294
+ placeholderPrompts: placeholderPrompts
295
+ });
205
296
  }
206
297
  },
207
298
  pmPlugins() {
@@ -209,7 +300,7 @@ export const placeholderPlugin = ({
209
300
  name: 'placeholder',
210
301
  plugin: ({
211
302
  getIntl
212
- }) => createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, api)
303
+ }) => createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, options && options.placeholderPrompts, api)
213
304
  }];
214
305
  }
215
306
  };
@@ -5,6 +5,12 @@ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
5
  import { findParentNode } from '@atlaskit/editor-prosemirror/utils';
6
6
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
7
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
8
+ // Typewriter animation timing constants
9
+ var TYPEWRITER_TYPE_DELAY = 50; // Delay between typing each character
10
+ var TYPEWRITER_PAUSE_BEFORE_ERASE = 2000; // Pause before starting to erase text
11
+ var TYPEWRITER_ERASE_DELAY = 40; // Delay between erasing each character
12
+ var TYPEWRITER_CYCLE_DELAY = 500; // Delay before starting next cycle
13
+
8
14
  export var pluginKey = new PluginKey('placeholderPlugin');
9
15
  function getPlaceholderState(editorState) {
10
16
  return pluginKey.getState(editorState);
@@ -12,10 +18,57 @@ function getPlaceholderState(editorState) {
12
18
  export var placeholderTestId = 'placeholder-test-id';
13
19
  var nodeTypesWithLongPlaceholderText = ['expand', 'panel'];
14
20
  var nodeTypesWithShortPlaceholderText = ['tableCell', 'tableHeader'];
15
- export function createPlaceholderDecoration(editorState, placeholderText) {
16
- var pos = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
21
+ var createTypewriterElement = function createTypewriterElement(placeholderPrompts, activeTypewriterTimeouts) {
22
+ var typewriterElement = document.createElement('span');
23
+ var currentPromptIndex = 0;
24
+ var displayedText = '';
25
+ var animationTimeouts = [];
26
+ var clearAllTimeouts = function clearAllTimeouts() {
27
+ animationTimeouts.forEach(function (timeoutId) {
28
+ return clearTimeout(timeoutId);
29
+ });
30
+ animationTimeouts = [];
31
+ };
32
+ var scheduleTimeout = function scheduleTimeout(callback, delay) {
33
+ var timeoutId = setTimeout(callback, delay);
34
+ animationTimeouts.push(timeoutId);
35
+ return timeoutId;
36
+ };
37
+ var _startAnimationCycle = function startAnimationCycle() {
38
+ var currentPrompt = placeholderPrompts[currentPromptIndex];
39
+ var characterIndex = 0;
40
+ var _typeNextCharacter = function typeNextCharacter() {
41
+ if (characterIndex < currentPrompt.length) {
42
+ displayedText = currentPrompt.substring(0, characterIndex + 1);
43
+ typewriterElement.textContent = displayedText;
44
+ characterIndex++;
45
+ scheduleTimeout(_typeNextCharacter, TYPEWRITER_TYPE_DELAY);
46
+ } else {
47
+ scheduleTimeout(_eraseLastCharacter, TYPEWRITER_PAUSE_BEFORE_ERASE);
48
+ }
49
+ };
50
+ var _eraseLastCharacter = function eraseLastCharacter() {
51
+ if (displayedText.length > 0) {
52
+ displayedText = displayedText.substring(0, displayedText.length - 1);
53
+ typewriterElement.textContent = displayedText;
54
+ scheduleTimeout(_eraseLastCharacter, TYPEWRITER_ERASE_DELAY);
55
+ } else {
56
+ // Move to next prompt. Modulo lets us cycle infinitely
57
+ currentPromptIndex = (currentPromptIndex + 1) % placeholderPrompts.length;
58
+ scheduleTimeout(_startAnimationCycle, TYPEWRITER_CYCLE_DELAY);
59
+ }
60
+ };
61
+ _typeNextCharacter();
62
+ };
63
+ activeTypewriterTimeouts === null || activeTypewriterTimeouts === void 0 || activeTypewriterTimeouts.push(clearAllTimeouts);
64
+ _startAnimationCycle();
65
+ return typewriterElement;
66
+ };
67
+ export function createPlaceholderDecoration(editorState, placeholderText, placeholderPrompts, activeTypewriterTimeouts) {
68
+ var pos = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 1;
17
69
  var placeholderDecoration = document.createElement('span');
18
70
  var placeholderNodeWithText = placeholderDecoration;
71
+ var typewriterElement = null;
19
72
  placeholderDecoration.setAttribute('data-testid', placeholderTestId);
20
73
  placeholderDecoration.className = 'placeholder-decoration';
21
74
 
@@ -27,7 +80,12 @@ export function createPlaceholderDecoration(editorState, placeholderText) {
27
80
  placeholderDecoration.appendChild(placeholderNode);
28
81
  placeholderNodeWithText = placeholderNode;
29
82
  }
30
- placeholderNodeWithText.textContent = placeholderText || ' ';
83
+ if (placeholderPrompts) {
84
+ typewriterElement = createTypewriterElement(placeholderPrompts, activeTypewriterTimeouts);
85
+ placeholderNodeWithText.appendChild(typewriterElement);
86
+ } else {
87
+ placeholderNodeWithText.textContent = placeholderText || ' ';
88
+ }
31
89
 
32
90
  // ME-2289 Tapping on backspace in empty editor hides and displays the keyboard
33
91
  // Add a editable buff node as the cursor moving forward is inevitable
@@ -51,17 +109,19 @@ export function createPlaceholderDecoration(editorState, placeholderText) {
51
109
  key: "placeholder ".concat(placeholderText)
52
110
  })]);
53
111
  }
54
- function setPlaceHolderState(placeholderText, pos) {
112
+ function setPlaceHolderState(placeholderText, pos, placeholderPrompts) {
55
113
  return {
56
114
  hasPlaceholder: true,
57
115
  placeholderText: placeholderText,
116
+ placeholderPrompts: placeholderPrompts,
58
117
  pos: pos ? pos : 1
59
118
  };
60
119
  }
61
- var emptyPlaceholder = function emptyPlaceholder(placeholderText) {
120
+ var emptyPlaceholder = function emptyPlaceholder(placeholderText, placeholderPrompts) {
62
121
  return {
63
122
  hasPlaceholder: false,
64
- placeholderText: placeholderText
123
+ placeholderText: placeholderText,
124
+ placeholderPrompts: placeholderPrompts
65
125
  };
66
126
  };
67
127
  function createPlaceHolderStateFrom(_ref) {
@@ -71,12 +131,13 @@ function createPlaceHolderStateFrom(_ref) {
71
131
  defaultPlaceholderText = _ref.defaultPlaceholderText,
72
132
  intl = _ref.intl,
73
133
  bracketPlaceholderText = _ref.bracketPlaceholderText,
74
- emptyLinePlaceholder = _ref.emptyLinePlaceholder;
134
+ emptyLinePlaceholder = _ref.emptyLinePlaceholder,
135
+ placeholderPrompts = _ref.placeholderPrompts;
75
136
  if (isTypeAheadOpen !== null && isTypeAheadOpen !== void 0 && isTypeAheadOpen(editorState)) {
76
- return emptyPlaceholder(defaultPlaceholderText);
137
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
77
138
  }
78
- if (defaultPlaceholderText && isEmptyDocument(editorState.doc)) {
79
- return setPlaceHolderState(defaultPlaceholderText);
139
+ if ((defaultPlaceholderText || placeholderPrompts) && isEmptyDocument(editorState.doc)) {
140
+ return setPlaceHolderState(defaultPlaceholderText, 1, placeholderPrompts);
80
141
  }
81
142
  if (isEditorFocused && editorExperiment('platform_editor_controls', 'variant1')) {
82
143
  var _parentNode$firstChil, _parentNode$firstChil2;
@@ -84,7 +145,7 @@ function createPlaceHolderStateFrom(_ref) {
84
145
  $from = _editorState$selectio.$from,
85
146
  $to = _editorState$selectio.$to;
86
147
  if ($from.pos !== $to.pos) {
87
- return emptyPlaceholder(defaultPlaceholderText);
148
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
88
149
  }
89
150
  var parentNode = $from.node($from.depth - 1);
90
151
  var parentType = parentNode === null || parentNode === void 0 ? void 0 : parentNode.type.name;
@@ -101,7 +162,7 @@ function createPlaceHolderStateFrom(_ref) {
101
162
  return node.type === editorState.schema.nodes.table;
102
163
  })(editorState.selection);
103
164
  if (!table) {
104
- return emptyPlaceholder(defaultPlaceholderText);
165
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
105
166
  }
106
167
  var isFirstCell = (table === null || table === void 0 || (_table$node$firstChil = table.node.firstChild) === null || _table$node$firstChil === void 0 ? void 0 : _table$node$firstChil.content.firstChild) === parentNode;
107
168
  if (isFirstCell) {
@@ -111,20 +172,28 @@ function createPlaceHolderStateFrom(_ref) {
111
172
  if (nodeTypesWithLongPlaceholderText.includes(parentType) && isEmptyNode) {
112
173
  return setPlaceHolderState(intl.formatMessage(messages.longEmptyNodePlaceholderText), $from.pos);
113
174
  }
114
- return emptyPlaceholder(defaultPlaceholderText);
175
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
115
176
  }
116
177
  if (bracketPlaceholderText && bracketTyped(editorState) && isEditorFocused) {
117
178
  var _$from = editorState.selection.$from;
118
179
  // Space is to account for positioning of the bracket
119
180
  var bracketHint = ' ' + bracketPlaceholderText;
120
- return setPlaceHolderState(bracketHint, _$from.pos - 1);
181
+ return setPlaceHolderState(bracketHint, _$from.pos - 1, placeholderPrompts);
121
182
  }
122
- return emptyPlaceholder(defaultPlaceholderText);
183
+ return emptyPlaceholder(defaultPlaceholderText, placeholderPrompts);
123
184
  }
124
- export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, api) {
125
- if (!defaultPlaceholderText && !bracketPlaceholderText) {
185
+ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, api) {
186
+ if (!defaultPlaceholderText && !placeholderPrompts && !bracketPlaceholderText) {
126
187
  return;
127
188
  }
189
+ var isDestroyed = false;
190
+ var activeTypewriterTimeouts = [];
191
+ var clearAllTypewriterTimeouts = function clearAllTypewriterTimeouts() {
192
+ activeTypewriterTimeouts.forEach(function (clearFn) {
193
+ return clearFn();
194
+ });
195
+ activeTypewriterTimeouts = [];
196
+ };
128
197
  return new SafePlugin({
129
198
  key: pluginKey,
130
199
  state: {
@@ -137,34 +206,39 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
137
206
  defaultPlaceholderText: defaultPlaceholderText,
138
207
  bracketPlaceholderText: bracketPlaceholderText,
139
208
  emptyLinePlaceholder: emptyLinePlaceholder,
209
+ placeholderPrompts: placeholderPrompts,
140
210
  intl: intl
141
211
  });
142
212
  },
143
213
  apply: function apply(tr, placeholderState, _oldEditorState, newEditorState) {
144
- var _api$focus2, _api$typeAhead3, _placeholderState$pla;
214
+ var _api$focus2, _api$typeAhead2, _ref2, _meta$placeholderText, _ref3, _meta$placeholderProm, _api$typeAhead3, _placeholderState$pla, _placeholderState$pla2;
145
215
  var meta = tr.getMeta(pluginKey);
146
216
  var isEditorFocused = Boolean(api === null || api === void 0 || (_api$focus2 = api.focus) === null || _api$focus2 === void 0 || (_api$focus2 = _api$focus2.sharedState.currentState()) === null || _api$focus2 === void 0 ? void 0 : _api$focus2.hasFocus);
147
- if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined) {
148
- var _api$typeAhead2;
149
- return createPlaceHolderStateFrom({
150
- isEditorFocused: isEditorFocused,
151
- editorState: newEditorState,
152
- isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
153
- defaultPlaceholderText: meta.placeholderText,
154
- bracketPlaceholderText: bracketPlaceholderText,
155
- emptyLinePlaceholder: emptyLinePlaceholder,
156
- intl: intl
157
- });
158
- }
159
- return createPlaceHolderStateFrom({
217
+ var newPlaceholderState = (meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined || (meta === null || meta === void 0 ? void 0 : meta.placeholderPrompts) !== undefined ? createPlaceHolderStateFrom({
218
+ isEditorFocused: isEditorFocused,
219
+ editorState: newEditorState,
220
+ isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
221
+ defaultPlaceholderText: (_ref2 = (_meta$placeholderText = meta.placeholderText) !== null && _meta$placeholderText !== void 0 ? _meta$placeholderText : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _ref2 !== void 0 ? _ref2 : defaultPlaceholderText,
222
+ bracketPlaceholderText: bracketPlaceholderText,
223
+ emptyLinePlaceholder: emptyLinePlaceholder,
224
+ placeholderPrompts: (_ref3 = (_meta$placeholderProm = meta.placeholderPrompts) !== null && _meta$placeholderProm !== void 0 ? _meta$placeholderProm : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _ref3 !== void 0 ? _ref3 : placeholderPrompts,
225
+ intl: intl
226
+ }) : createPlaceHolderStateFrom({
160
227
  isEditorFocused: isEditorFocused,
161
228
  editorState: newEditorState,
162
229
  isTypeAheadOpen: api === null || api === void 0 || (_api$typeAhead3 = api.typeAhead) === null || _api$typeAhead3 === void 0 ? void 0 : _api$typeAhead3.actions.isOpen,
163
230
  defaultPlaceholderText: (_placeholderState$pla = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _placeholderState$pla !== void 0 ? _placeholderState$pla : defaultPlaceholderText,
164
231
  bracketPlaceholderText: bracketPlaceholderText,
165
232
  emptyLinePlaceholder: emptyLinePlaceholder,
233
+ placeholderPrompts: (_placeholderState$pla2 = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _placeholderState$pla2 !== void 0 ? _placeholderState$pla2 : placeholderPrompts,
166
234
  intl: intl
167
235
  });
236
+
237
+ // Clear timeouts when hasPlaceholder becomes false
238
+ if (!newPlaceholderState.hasPlaceholder) {
239
+ clearAllTypewriterTimeouts();
240
+ }
241
+ return newPlaceholderState;
168
242
  }
169
243
  },
170
244
  props: {
@@ -174,26 +248,40 @@ export function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderTex
174
248
  hasPlaceholder = _getPlaceholderState.hasPlaceholder,
175
249
  placeholderText = _getPlaceholderState.placeholderText,
176
250
  pos = _getPlaceholderState.pos;
251
+
252
+ // Decorations is still called after plugin is destroyed
253
+ // So we need to make sure decorations is not called if plugin has been destroyed to prevent the placeholder animations' setTimeouts called infinitely
254
+ if (isDestroyed) {
255
+ return;
256
+ }
177
257
  var compositionPluginState = api === null || api === void 0 || (_api$composition = api.composition) === null || _api$composition === void 0 ? void 0 : _api$composition.sharedState.currentState();
178
258
  var isShowingDiff = Boolean(api === null || api === void 0 || (_api$showDiff = api.showDiff) === null || _api$showDiff === void 0 || (_api$showDiff = _api$showDiff.sharedState.currentState()) === null || _api$showDiff === void 0 ? void 0 : _api$showDiff.isDisplayingChanges);
179
- if (hasPlaceholder && placeholderText && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
180
- return createPlaceholderDecoration(editorState, placeholderText, pos);
259
+ if (hasPlaceholder && ((placeholderText !== null && placeholderText !== void 0 ? placeholderText : '') || placeholderPrompts) && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
260
+ return createPlaceholderDecoration(editorState, placeholderText !== null && placeholderText !== void 0 ? placeholderText : '', placeholderPrompts, activeTypewriterTimeouts, pos);
181
261
  }
182
262
  return;
183
263
  }
264
+ },
265
+ view: function view() {
266
+ return {
267
+ destroy: function destroy() {
268
+ clearAllTypewriterTimeouts();
269
+ isDestroyed = true;
270
+ }
271
+ };
184
272
  }
185
273
  });
186
274
  }
187
- export var placeholderPlugin = function placeholderPlugin(_ref2) {
188
- var options = _ref2.config,
189
- api = _ref2.api;
275
+ export var placeholderPlugin = function placeholderPlugin(_ref4) {
276
+ var options = _ref4.config,
277
+ api = _ref4.api;
190
278
  var currentPlaceholder = options === null || options === void 0 ? void 0 : options.placeholder;
191
279
  return {
192
280
  name: 'placeholder',
193
281
  commands: {
194
282
  setPlaceholder: function setPlaceholder(placeholderText) {
195
- return function (_ref3) {
196
- var tr = _ref3.tr;
283
+ return function (_ref5) {
284
+ var tr = _ref5.tr;
197
285
  if (currentPlaceholder !== placeholderText) {
198
286
  currentPlaceholder = placeholderText;
199
287
  return tr.setMeta(pluginKey, {
@@ -202,14 +290,22 @@ export var placeholderPlugin = function placeholderPlugin(_ref2) {
202
290
  }
203
291
  return null;
204
292
  };
293
+ },
294
+ setAnimatingPlaceholderPrompts: function setAnimatingPlaceholderPrompts(placeholderPrompts) {
295
+ return function (_ref6) {
296
+ var tr = _ref6.tr;
297
+ return tr.setMeta(pluginKey, {
298
+ placeholderPrompts: placeholderPrompts
299
+ });
300
+ };
205
301
  }
206
302
  },
207
303
  pmPlugins: function pmPlugins() {
208
304
  return [{
209
305
  name: 'placeholder',
210
- plugin: function plugin(_ref4) {
211
- var getIntl = _ref4.getIntl;
212
- return createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, api);
306
+ plugin: function plugin(_ref7) {
307
+ var getIntl = _ref7.getIntl;
308
+ return createPlugin(getIntl(), options && options.placeholder, options && options.placeholderBracketHint, options && options.emptyLinePlaceholder, options && options.placeholderPrompts, api);
213
309
  }
214
310
  }];
215
311
  }
@@ -7,6 +7,6 @@ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
7
  import type { PlaceholderPlugin } from './placeholderPluginType';
8
8
  export declare const pluginKey: PluginKey<any>;
9
9
  export declare const placeholderTestId = "placeholder-test-id";
10
- export declare function createPlaceholderDecoration(editorState: EditorState, placeholderText: string, pos?: number): DecorationSet;
11
- export declare function createPlugin(intl: IntlShape, defaultPlaceholderText?: string, bracketPlaceholderText?: string, emptyLinePlaceholder?: string, api?: ExtractInjectionAPI<PlaceholderPlugin>): SafePlugin | undefined;
10
+ export declare function createPlaceholderDecoration(editorState: EditorState, placeholderText: string, placeholderPrompts?: string[], activeTypewriterTimeouts?: (() => void)[], pos?: number): DecorationSet;
11
+ export declare function createPlugin(intl: IntlShape, defaultPlaceholderText?: string, bracketPlaceholderText?: string, emptyLinePlaceholder?: string, placeholderPrompts?: string[], api?: ExtractInjectionAPI<PlaceholderPlugin>): SafePlugin | undefined;
12
12
  export declare const placeholderPlugin: PlaceholderPlugin;
@@ -7,11 +7,13 @@ export interface PlaceholderPluginOptions {
7
7
  placeholder?: string;
8
8
  placeholderBracketHint?: string;
9
9
  emptyLinePlaceholder?: string;
10
+ placeholderPrompts?: string[];
10
11
  }
11
12
  export type PlaceholderPlugin = NextEditorPlugin<'placeholder', {
12
13
  pluginConfiguration: PlaceholderPluginOptions | undefined;
13
14
  commands: {
14
15
  setPlaceholder: (placeholder: string) => EditorCommand;
16
+ setAnimatingPlaceholderPrompts: (placeholderPrompts: string[]) => EditorCommand;
15
17
  };
16
18
  dependencies: [FocusPlugin, CompositionPlugin, TypeAheadPlugin, OptionalPlugin<ShowDiffPlugin>];
17
19
  }>;
@@ -7,6 +7,6 @@ import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
7
7
  import type { PlaceholderPlugin } from './placeholderPluginType';
8
8
  export declare const pluginKey: PluginKey<any>;
9
9
  export declare const placeholderTestId = "placeholder-test-id";
10
- export declare function createPlaceholderDecoration(editorState: EditorState, placeholderText: string, pos?: number): DecorationSet;
11
- export declare function createPlugin(intl: IntlShape, defaultPlaceholderText?: string, bracketPlaceholderText?: string, emptyLinePlaceholder?: string, api?: ExtractInjectionAPI<PlaceholderPlugin>): SafePlugin | undefined;
10
+ export declare function createPlaceholderDecoration(editorState: EditorState, placeholderText: string, placeholderPrompts?: string[], activeTypewriterTimeouts?: (() => void)[], pos?: number): DecorationSet;
11
+ export declare function createPlugin(intl: IntlShape, defaultPlaceholderText?: string, bracketPlaceholderText?: string, emptyLinePlaceholder?: string, placeholderPrompts?: string[], api?: ExtractInjectionAPI<PlaceholderPlugin>): SafePlugin | undefined;
12
12
  export declare const placeholderPlugin: PlaceholderPlugin;
@@ -7,11 +7,13 @@ export interface PlaceholderPluginOptions {
7
7
  placeholder?: string;
8
8
  placeholderBracketHint?: string;
9
9
  emptyLinePlaceholder?: string;
10
+ placeholderPrompts?: string[];
10
11
  }
11
12
  export type PlaceholderPlugin = NextEditorPlugin<'placeholder', {
12
13
  pluginConfiguration: PlaceholderPluginOptions | undefined;
13
14
  commands: {
14
15
  setPlaceholder: (placeholder: string) => EditorCommand;
16
+ setAnimatingPlaceholderPrompts: (placeholderPrompts: string[]) => EditorCommand;
15
17
  };
16
18
  dependencies: [
17
19
  FocusPlugin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-placeholder",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Placeholder plugin for @atlaskit/editor-core.",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -36,11 +36,11 @@
36
36
  "@atlaskit/editor-plugin-type-ahead": "^3.1.0",
37
37
  "@atlaskit/editor-prosemirror": "7.0.0",
38
38
  "@atlaskit/platform-feature-flags": "^1.1.0",
39
- "@atlaskit/tmp-editor-statsig": "^9.26.0",
39
+ "@atlaskit/tmp-editor-statsig": "^10.1.0",
40
40
  "@babel/runtime": "^7.0.0"
41
41
  },
42
42
  "peerDependencies": {
43
- "@atlaskit/editor-common": "^107.20.0",
43
+ "@atlaskit/editor-common": "^107.25.0",
44
44
  "react": "^18.2.0",
45
45
  "react-dom": "^18.2.0",
46
46
  "react-intl-next": "npm:react-intl@^5.18.1"