@atlaskit/editor-plugin-placeholder 6.5.4 → 6.6.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 (47) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cjs/placeholderPlugin.js +19 -568
  3. package/dist/cjs/pm-plugins/adf-builders.js +30 -0
  4. package/dist/cjs/pm-plugins/animation.js +58 -0
  5. package/dist/cjs/pm-plugins/constants.js +18 -0
  6. package/dist/cjs/pm-plugins/decorations.js +96 -0
  7. package/dist/cjs/pm-plugins/main.js +167 -0
  8. package/dist/cjs/pm-plugins/placeholderPluginLegacy.js +616 -0
  9. package/dist/cjs/pm-plugins/types.js +5 -0
  10. package/dist/cjs/pm-plugins/utils.js +243 -0
  11. package/dist/es2019/placeholderPlugin.js +8 -548
  12. package/dist/es2019/pm-plugins/adf-builders.js +22 -0
  13. package/dist/es2019/pm-plugins/animation.js +49 -0
  14. package/dist/es2019/pm-plugins/constants.js +12 -0
  15. package/dist/es2019/pm-plugins/decorations.js +87 -0
  16. package/dist/es2019/pm-plugins/main.js +161 -0
  17. package/dist/es2019/pm-plugins/placeholderPluginLegacy.js +597 -0
  18. package/dist/es2019/pm-plugins/types.js +1 -0
  19. package/dist/es2019/pm-plugins/utils.js +234 -0
  20. package/dist/esm/placeholderPlugin.js +17 -563
  21. package/dist/esm/pm-plugins/adf-builders.js +24 -0
  22. package/dist/esm/pm-plugins/animation.js +52 -0
  23. package/dist/esm/pm-plugins/constants.js +12 -0
  24. package/dist/esm/pm-plugins/decorations.js +90 -0
  25. package/dist/esm/pm-plugins/main.js +161 -0
  26. package/dist/esm/pm-plugins/placeholderPluginLegacy.js +606 -0
  27. package/dist/esm/pm-plugins/types.js +1 -0
  28. package/dist/esm/pm-plugins/utils.js +232 -0
  29. package/dist/types/placeholderPlugin.d.ts +0 -8
  30. package/dist/types/pm-plugins/adf-builders.d.ts +4 -0
  31. package/dist/types/pm-plugins/animation.d.ts +1 -0
  32. package/dist/types/pm-plugins/constants.d.ts +9 -0
  33. package/dist/types/pm-plugins/decorations.d.ts +4 -0
  34. package/dist/types/pm-plugins/main.d.ts +6 -0
  35. package/dist/types/pm-plugins/placeholderPluginLegacy.d.ts +13 -0
  36. package/dist/types/pm-plugins/types.d.ts +37 -0
  37. package/dist/types/pm-plugins/utils.d.ts +27 -0
  38. package/dist/types-ts4.5/placeholderPlugin.d.ts +0 -8
  39. package/dist/types-ts4.5/pm-plugins/adf-builders.d.ts +4 -0
  40. package/dist/types-ts4.5/pm-plugins/animation.d.ts +1 -0
  41. package/dist/types-ts4.5/pm-plugins/constants.d.ts +9 -0
  42. package/dist/types-ts4.5/pm-plugins/decorations.d.ts +4 -0
  43. package/dist/types-ts4.5/pm-plugins/main.d.ts +6 -0
  44. package/dist/types-ts4.5/pm-plugins/placeholderPluginLegacy.d.ts +13 -0
  45. package/dist/types-ts4.5/pm-plugins/types.d.ts +37 -0
  46. package/dist/types-ts4.5/pm-plugins/utils.d.ts +27 -0
  47. package/package.json +7 -4
@@ -0,0 +1,87 @@
1
+ import { processRawValue } from '@atlaskit/editor-common/process-raw-value';
2
+ import { browser } from '@atlaskit/editor-common/utils';
3
+ import { DOMSerializer } from '@atlaskit/editor-prosemirror/model';
4
+ import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
5
+ import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
6
+ import { cycleThroughPlaceholderPrompts } from './animation';
7
+ import { placeholderTestId } from './constants';
8
+ export function createPlaceholderDecoration(editorState, placeholderText, placeholderPrompts, activeTypewriterTimeouts, pos = 1, initialDelayWhenUserTypedAndDeleted = 0, placeholderADF) {
9
+ const placeholderDecoration = document.createElement('span');
10
+ let placeholderNodeWithText = placeholderDecoration;
11
+ placeholderDecoration.setAttribute('data-testid', placeholderTestId);
12
+ placeholderDecoration.className = 'placeholder-decoration';
13
+ placeholderDecoration.setAttribute('aria-hidden', 'true');
14
+
15
+ // PM sets contenteditable to false on Decorations so Firefox doesn't display the flashing cursor
16
+ // So adding an extra span which will contain the placeholder text
17
+ if (browser.gecko) {
18
+ const placeholderNode = document.createElement('span');
19
+ placeholderNode.setAttribute('contenteditable', 'true'); // explicitly overriding the default Decoration behaviour
20
+ placeholderDecoration.appendChild(placeholderNode);
21
+ placeholderNodeWithText = placeholderNode;
22
+ }
23
+ if (placeholderText) {
24
+ placeholderNodeWithText.textContent = placeholderText || ' ';
25
+ } else if (placeholderADF) {
26
+ const serializer = DOMSerializer.fromSchema(editorState.schema);
27
+ // Get a PMNode from docnode
28
+ const docNode = processRawValue(editorState.schema, placeholderADF);
29
+ if (docNode) {
30
+ // Extract only the inline content from paragraphs, avoiding block-level elements
31
+ // that can interfere with cursor rendering
32
+
33
+ docNode.children.forEach(node => {
34
+ // For paragraph nodes, serialize their content (inline elements) directly
35
+ // without the wrapping <p> tag
36
+ if (node.type.name === 'paragraph') {
37
+ node.content.forEach(inlineNode => {
38
+ const inlineDOM = serializer.serializeNode(inlineNode);
39
+ placeholderNodeWithText.append(inlineDOM);
40
+ });
41
+ } else {
42
+ // For non-paragraph nodes, serialize normally
43
+ const nodeDOM = serializer.serializeNode(node);
44
+ placeholderNodeWithText.append(nodeDOM);
45
+ }
46
+ });
47
+ const markElements = placeholderNodeWithText.querySelectorAll('[data-prosemirror-content-type="mark"]');
48
+ markElements.forEach(markEl => {
49
+ if (markEl instanceof HTMLElement) {
50
+ markEl.style.setProperty('color', "var(--ds-text-subtlest, #6B6E76)");
51
+ }
52
+ });
53
+ // Ensure all child elements don't block pointer events or cursor
54
+ const allElements = placeholderNodeWithText.querySelectorAll('*');
55
+ allElements.forEach(el => {
56
+ if (el instanceof HTMLElement) {
57
+ el.style.pointerEvents = 'none';
58
+ el.style.userSelect = 'none';
59
+ }
60
+ });
61
+ }
62
+ } else if (placeholderPrompts) {
63
+ cycleThroughPlaceholderPrompts(placeholderPrompts, activeTypewriterTimeouts, placeholderNodeWithText, initialDelayWhenUserTypedAndDeleted);
64
+ }
65
+
66
+ // ME-2289 Tapping on backspace in empty editor hides and displays the keyboard
67
+ // Add a editable buff node as the cursor moving forward is inevitable
68
+ // when backspace in GBoard composition
69
+ if (browser.android && browser.chrome) {
70
+ const buffNode = document.createElement('span');
71
+ buffNode.setAttribute('class', 'placeholder-android');
72
+ buffNode.setAttribute('contenteditable', 'true');
73
+ buffNode.textContent = ' ';
74
+ placeholderDecoration.appendChild(buffNode);
75
+ }
76
+ const isTargetNested = editorState.doc.resolve(pos).depth > 1;
77
+
78
+ // only truncate text for nested nodes, otherwise applying 'overflow: hidden;' to top level nodes
79
+ // creates issues with quick insert button
80
+ if (isTargetNested && editorExperiment('platform_editor_controls', 'variant1')) {
81
+ placeholderDecoration.classList.add('placeholder-decoration-hide-overflow');
82
+ }
83
+ return DecorationSet.create(editorState.doc, [Decoration.widget(pos, placeholderDecoration, {
84
+ side: 0,
85
+ key: `placeholder ${placeholderText}`
86
+ })]);
87
+ }
@@ -0,0 +1,161 @@
1
+ import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
2
+ import { fg } from '@atlaskit/platform-feature-flags';
3
+ import { pluginKey, EMPTY_PARAGRAPH_TIMEOUT_DELAY } from '../placeholderPlugin';
4
+ import { TYPEWRITER_TYPED_AND_DELETED_DELAY } from './constants';
5
+ import { createPlaceholderDecoration } from './decorations';
6
+ import { calculateUserInteractionState, createPlaceHolderStateFrom, getPlaceholderState } from './utils';
7
+ export default function createPlugin(intl, defaultPlaceholderText, bracketPlaceholderText, emptyLinePlaceholder, placeholderPrompts, withEmptyParagraph, placeholderADF, api) {
8
+ if (!defaultPlaceholderText && !placeholderPrompts && !bracketPlaceholderText && !placeholderADF) {
9
+ return;
10
+ }
11
+ let isDestroyed = false;
12
+ let activeTypewriterTimeouts = [];
13
+ const clearAllTypewriterTimeouts = () => {
14
+ activeTypewriterTimeouts.forEach(clearFn => clearFn());
15
+ activeTypewriterTimeouts = [];
16
+ };
17
+ return new SafePlugin({
18
+ key: pluginKey,
19
+ state: {
20
+ init: (_, state) => {
21
+ var _api$focus, _api$focus$sharedStat, _api$typeAhead;
22
+ return createPlaceHolderStateFrom({
23
+ isInitial: true,
24
+ isEditorFocused: Boolean(api === null || api === void 0 ? void 0 : (_api$focus = api.focus) === null || _api$focus === void 0 ? void 0 : (_api$focus$sharedStat = _api$focus.sharedState.currentState()) === null || _api$focus$sharedStat === void 0 ? void 0 : _api$focus$sharedStat.hasFocus),
25
+ editorState: state,
26
+ isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead = api.typeAhead) === null || _api$typeAhead === void 0 ? void 0 : _api$typeAhead.actions.isOpen,
27
+ defaultPlaceholderText,
28
+ bracketPlaceholderText,
29
+ emptyLinePlaceholder,
30
+ placeholderADF,
31
+ placeholderPrompts,
32
+ typedAndDeleted: false,
33
+ userHadTyped: false,
34
+ intl
35
+ });
36
+ },
37
+ apply: (tr, placeholderState, _oldEditorState, newEditorState) => {
38
+ var _api$focus2, _api$focus2$sharedSta, _placeholderState$isP, _api$typeAhead2, _ref, _meta$placeholderText, _ref2, _meta$placeholderProm, _meta$showOnEmptyPara;
39
+ const meta = tr.getMeta(pluginKey);
40
+ 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);
41
+ const {
42
+ userHadTyped,
43
+ typedAndDeleted
44
+ } = calculateUserInteractionState({
45
+ placeholderState,
46
+ oldEditorState: _oldEditorState,
47
+ newEditorState
48
+ });
49
+ let isPlaceholderHidden = (_placeholderState$isP = placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.isPlaceholderHidden) !== null && _placeholderState$isP !== void 0 ? _placeholderState$isP : false;
50
+ if ((meta === null || meta === void 0 ? void 0 : meta.isPlaceholderHidden) !== undefined && fg('platform_editor_ai_aifc_patch_beta')) {
51
+ isPlaceholderHidden = meta.isPlaceholderHidden;
52
+ }
53
+ if ((meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== undefined && (fg('platform_editor_ai_aifc_patch_beta_2') || fg('platform_editor_ai_aifc_patch_ga'))) {
54
+ // Only update defaultPlaceholderText from meta if we're not using ADF placeholder
55
+ if (!(fg('platform_editor_ai_aifc_patch_ga') && placeholderADF)) {
56
+ defaultPlaceholderText = meta.placeholderText;
57
+ }
58
+ }
59
+ const newPlaceholderState = createPlaceHolderStateFrom({
60
+ isEditorFocused,
61
+ editorState: newEditorState,
62
+ isTypeAheadOpen: api === null || api === void 0 ? void 0 : (_api$typeAhead2 = api.typeAhead) === null || _api$typeAhead2 === void 0 ? void 0 : _api$typeAhead2.actions.isOpen,
63
+ defaultPlaceholderText: fg('platform_editor_ai_aifc_patch_beta_2') || fg('platform_editor_ai_aifc_patch_ga') ? defaultPlaceholderText : (_ref = (_meta$placeholderText = meta === null || meta === void 0 ? void 0 : meta.placeholderText) !== null && _meta$placeholderText !== void 0 ? _meta$placeholderText : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderText) !== null && _ref !== void 0 ? _ref : defaultPlaceholderText,
64
+ bracketPlaceholderText,
65
+ emptyLinePlaceholder,
66
+ placeholderADF,
67
+ placeholderPrompts: (_ref2 = (_meta$placeholderProm = meta === null || meta === void 0 ? void 0 : meta.placeholderPrompts) !== null && _meta$placeholderProm !== void 0 ? _meta$placeholderProm : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.placeholderPrompts) !== null && _ref2 !== void 0 ? _ref2 : placeholderPrompts,
68
+ typedAndDeleted,
69
+ userHadTyped,
70
+ intl,
71
+ isPlaceholderHidden,
72
+ withEmptyParagraph,
73
+ showOnEmptyParagraph: (_meta$showOnEmptyPara = meta === null || meta === void 0 ? void 0 : meta.showOnEmptyParagraph) !== null && _meta$showOnEmptyPara !== void 0 ? _meta$showOnEmptyPara : placeholderState === null || placeholderState === void 0 ? void 0 : placeholderState.showOnEmptyParagraph
74
+ });
75
+
76
+ // Clear timeouts when hasPlaceholder becomes false
77
+ if (!newPlaceholderState.hasPlaceholder) {
78
+ clearAllTypewriterTimeouts();
79
+ }
80
+ return newPlaceholderState;
81
+ }
82
+ },
83
+ props: {
84
+ decorations(editorState) {
85
+ var _api$composition, _api$showDiff, _api$showDiff$sharedS;
86
+ const {
87
+ hasPlaceholder,
88
+ placeholderText,
89
+ pos,
90
+ typedAndDeleted,
91
+ contextPlaceholderADF
92
+ } = getPlaceholderState(editorState);
93
+
94
+ // Decorations is still called after plugin is destroyed
95
+ // So we need to make sure decorations is not called if plugin has been destroyed to prevent the placeholder animations' setTimeouts called infinitely
96
+ if (isDestroyed) {
97
+ return;
98
+ }
99
+ const compositionPluginState = api === null || api === void 0 ? void 0 : (_api$composition = api.composition) === null || _api$composition === void 0 ? void 0 : _api$composition.sharedState.currentState();
100
+ 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);
101
+ if (hasPlaceholder && ((placeholderText !== null && placeholderText !== void 0 ? placeholderText : '') || placeholderPrompts || placeholderADF || contextPlaceholderADF) && pos !== undefined && !(compositionPluginState !== null && compositionPluginState !== void 0 && compositionPluginState.isComposing) && !isShowingDiff) {
102
+ const initialDelayWhenUserTypedAndDeleted = typedAndDeleted ? TYPEWRITER_TYPED_AND_DELETED_DELAY : 0;
103
+ // contextPlaceholderADF takes precedence over the global placeholderADF
104
+ const placeholderAdfToUse = contextPlaceholderADF || placeholderADF;
105
+ return createPlaceholderDecoration(editorState, placeholderText !== null && placeholderText !== void 0 ? placeholderText : '', placeholderPrompts, activeTypewriterTimeouts, pos, initialDelayWhenUserTypedAndDeleted, placeholderAdfToUse);
106
+ }
107
+ return;
108
+ }
109
+ },
110
+ view() {
111
+ let timeoutId;
112
+ function startEmptyParagraphTimeout(editorView) {
113
+ if (timeoutId) {
114
+ return;
115
+ }
116
+ timeoutId = setTimeout(() => {
117
+ timeoutId = undefined;
118
+ editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
119
+ showOnEmptyParagraph: true
120
+ }));
121
+ }, EMPTY_PARAGRAPH_TIMEOUT_DELAY);
122
+ }
123
+ function destroyEmptyParagraphTimeout() {
124
+ if (timeoutId) {
125
+ clearTimeout(timeoutId);
126
+ timeoutId = undefined;
127
+ }
128
+ }
129
+ return {
130
+ update(editorView, prevState) {
131
+ if (fg('platform_editor_ai_aifc_patch_beta_2') || fg('platform_editor_ai_aifc_patch_ga')) {
132
+ const prevPluginState = getPlaceholderState(prevState);
133
+ const newPluginState = getPlaceholderState(editorView.state);
134
+
135
+ // user start typing after move to an empty paragraph, clear timeout
136
+ if (!newPluginState.canShowOnEmptyParagraph && timeoutId) {
137
+ destroyEmptyParagraphTimeout();
138
+ }
139
+ // user move to an empty paragraph again, reset state to hide placeholder, and restart timeout
140
+ else if (prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && newPluginState.pos !== prevPluginState.pos) {
141
+ editorView.dispatch(editorView.state.tr.setMeta(pluginKey, {
142
+ showOnEmptyParagraph: false
143
+ }));
144
+ destroyEmptyParagraphTimeout();
145
+ startEmptyParagraphTimeout(editorView);
146
+ }
147
+ // user move to an empty paragraph (by click enter or move to an empty paragraph), start timeout
148
+ else if (!prevPluginState.canShowOnEmptyParagraph && newPluginState.canShowOnEmptyParagraph && !newPluginState.showOnEmptyParagraph && !timeoutId) {
149
+ startEmptyParagraphTimeout(editorView);
150
+ }
151
+ }
152
+ },
153
+ destroy() {
154
+ clearAllTypewriterTimeouts();
155
+ destroyEmptyParagraphTimeout();
156
+ isDestroyed = true;
157
+ }
158
+ };
159
+ }
160
+ });
161
+ }