@prosekit/web 0.7.2 → 0.7.4

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 (173) hide show
  1. package/dist/get-default-state-CIEy7xrl.js +2 -1
  2. package/dist/get-default-state-CIEy7xrl.js.map +1 -0
  3. package/dist/get-safe-editor-view-DENm4avv.js +2 -1
  4. package/dist/get-safe-editor-view-DENm4avv.js.map +1 -0
  5. package/dist/{inject-style-RwRNoINh.js → inject-style-BJQNFufI.js} +12 -13
  6. package/dist/inject-style-BJQNFufI.js.map +1 -0
  7. package/dist/prosekit-web-autocomplete.d.ts +2 -1
  8. package/dist/prosekit-web-autocomplete.d.ts.map +1 -0
  9. package/dist/prosekit-web-autocomplete.js +5 -7
  10. package/dist/prosekit-web-autocomplete.js.map +1 -0
  11. package/dist/prosekit-web-block-handle.d.ts +2 -1
  12. package/dist/prosekit-web-block-handle.d.ts.map +1 -0
  13. package/dist/prosekit-web-block-handle.js +33 -20
  14. package/dist/prosekit-web-block-handle.js.map +1 -0
  15. package/dist/prosekit-web-drop-indicator.d.ts +2 -1
  16. package/dist/prosekit-web-drop-indicator.d.ts.map +1 -0
  17. package/dist/prosekit-web-drop-indicator.js +2 -1
  18. package/dist/prosekit-web-drop-indicator.js.map +1 -0
  19. package/dist/prosekit-web-inline-popover.d.ts +2 -1
  20. package/dist/prosekit-web-inline-popover.d.ts.map +1 -0
  21. package/dist/prosekit-web-inline-popover.js +4 -5
  22. package/dist/prosekit-web-inline-popover.js.map +1 -0
  23. package/dist/prosekit-web-popover.d.ts +2 -1
  24. package/dist/prosekit-web-popover.d.ts.map +1 -0
  25. package/dist/prosekit-web-popover.js +2 -1
  26. package/dist/prosekit-web-popover.js.map +1 -0
  27. package/dist/prosekit-web-resizable.d.ts +2 -1
  28. package/dist/prosekit-web-resizable.d.ts.map +1 -0
  29. package/dist/prosekit-web-resizable.js +2 -1
  30. package/dist/prosekit-web-resizable.js.map +1 -0
  31. package/dist/prosekit-web-table-handle.d.ts +2 -1
  32. package/dist/prosekit-web-table-handle.d.ts.map +1 -0
  33. package/dist/prosekit-web-table-handle.js +38 -75
  34. package/dist/prosekit-web-table-handle.js.map +1 -0
  35. package/dist/prosekit-web-tooltip.d.ts +2 -1
  36. package/dist/prosekit-web-tooltip.d.ts.map +1 -0
  37. package/dist/prosekit-web-tooltip.js +2 -1
  38. package/dist/prosekit-web-tooltip.js.map +1 -0
  39. package/dist/prosekit-web.d.ts +1 -1
  40. package/dist/prosekit-web.js +1 -0
  41. package/dist/use-editor-extension-Cc7ZG7uj.js +2 -1
  42. package/dist/use-editor-extension-Cc7ZG7uj.js.map +1 -0
  43. package/dist/use-scrolling-BNfsQs3S.js +2 -1
  44. package/dist/use-scrolling-BNfsQs3S.js.map +1 -0
  45. package/package.json +21 -20
  46. package/src/components/autocomplete/autocomplete-empty/element.gen.ts +18 -0
  47. package/src/components/autocomplete/autocomplete-empty/setup.ts +6 -0
  48. package/src/components/autocomplete/autocomplete-empty/types.ts +16 -0
  49. package/src/components/autocomplete/autocomplete-item/element.gen.ts +18 -0
  50. package/src/components/autocomplete/autocomplete-item/setup.ts +38 -0
  51. package/src/components/autocomplete/autocomplete-item/types.ts +31 -0
  52. package/src/components/autocomplete/autocomplete-list/element.gen.ts +18 -0
  53. package/src/components/autocomplete/autocomplete-list/setup.ts +140 -0
  54. package/src/components/autocomplete/autocomplete-list/types.ts +30 -0
  55. package/src/components/autocomplete/autocomplete-popover/element.gen.ts +18 -0
  56. package/src/components/autocomplete/autocomplete-popover/helpers.spec.ts +21 -0
  57. package/src/components/autocomplete/autocomplete-popover/helpers.ts +7 -0
  58. package/src/components/autocomplete/autocomplete-popover/setup.ts +185 -0
  59. package/src/components/autocomplete/autocomplete-popover/types.ts +103 -0
  60. package/src/components/autocomplete/context.ts +19 -0
  61. package/src/components/autocomplete/index.gen.ts +17 -0
  62. package/src/components/block-handle/block-handle-add/element.gen.ts +18 -0
  63. package/src/components/block-handle/block-handle-add/setup.ts +37 -0
  64. package/src/components/block-handle/block-handle-add/types.ts +26 -0
  65. package/src/components/block-handle/block-handle-draggable/element.gen.ts +18 -0
  66. package/src/components/block-handle/block-handle-draggable/set-drag-preview.ts +88 -0
  67. package/src/components/block-handle/block-handle-draggable/setup.ts +133 -0
  68. package/src/components/block-handle/block-handle-draggable/types.ts +26 -0
  69. package/src/components/block-handle/block-handle-popover/element.gen.ts +18 -0
  70. package/src/components/block-handle/block-handle-popover/pointer-move.ts +243 -0
  71. package/src/components/block-handle/block-handle-popover/setup.ts +88 -0
  72. package/src/components/block-handle/block-handle-popover/types.ts +84 -0
  73. package/src/components/block-handle/context.ts +34 -0
  74. package/src/components/block-handle/index.gen.ts +13 -0
  75. package/src/components/drop-indicator/drop-indicator/element.gen.ts +18 -0
  76. package/src/components/drop-indicator/drop-indicator/setup.ts +87 -0
  77. package/src/components/drop-indicator/drop-indicator/types.ts +34 -0
  78. package/src/components/drop-indicator/index.gen.ts +5 -0
  79. package/src/components/inline-popover/index.gen.ts +5 -0
  80. package/src/components/inline-popover/inline-popover/element.gen.ts +18 -0
  81. package/src/components/inline-popover/inline-popover/setup.ts +97 -0
  82. package/src/components/inline-popover/inline-popover/types.ts +115 -0
  83. package/src/components/inline-popover/inline-popover/virtual-selection-element.ts +75 -0
  84. package/src/components/popover/index.gen.ts +13 -0
  85. package/src/components/popover/popover-content/element.gen.ts +18 -0
  86. package/src/components/popover/popover-content/setup.ts +1 -0
  87. package/src/components/popover/popover-content/types.ts +12 -0
  88. package/src/components/popover/popover-root/element.gen.ts +18 -0
  89. package/src/components/popover/popover-root/setup.ts +1 -0
  90. package/src/components/popover/popover-root/types.ts +12 -0
  91. package/src/components/popover/popover-trigger/element.gen.ts +18 -0
  92. package/src/components/popover/popover-trigger/setup.ts +1 -0
  93. package/src/components/popover/popover-trigger/types.ts +12 -0
  94. package/src/components/resizable/context.ts +45 -0
  95. package/src/components/resizable/index.gen.ts +9 -0
  96. package/src/components/resizable/resizable-handle/calc-resize.spec.ts +280 -0
  97. package/src/components/resizable/resizable-handle/calc-resize.ts +121 -0
  98. package/src/components/resizable/resizable-handle/element.gen.ts +18 -0
  99. package/src/components/resizable/resizable-handle/setup.ts +112 -0
  100. package/src/components/resizable/resizable-handle/types.ts +32 -0
  101. package/src/components/resizable/resizable-root/element.gen.ts +18 -0
  102. package/src/components/resizable/resizable-root/setup.ts +93 -0
  103. package/src/components/resizable/resizable-root/types.ts +28 -0
  104. package/src/components/table-handle/context.ts +49 -0
  105. package/src/components/table-handle/dnd.ts +135 -0
  106. package/src/components/table-handle/hooks/use-drop.ts +94 -0
  107. package/src/components/table-handle/hooks/use-empty-image.ts +30 -0
  108. package/src/components/table-handle/index.gen.ts +37 -0
  109. package/src/components/table-handle/table-handle-column-root/element.gen.ts +18 -0
  110. package/src/components/table-handle/table-handle-column-root/setup.ts +71 -0
  111. package/src/components/table-handle/table-handle-column-root/types.ts +76 -0
  112. package/src/components/table-handle/table-handle-column-trigger/element.gen.ts +18 -0
  113. package/src/components/table-handle/table-handle-column-trigger/setup.ts +75 -0
  114. package/src/components/table-handle/table-handle-column-trigger/types.ts +23 -0
  115. package/src/components/table-handle/table-handle-drag-preview/element.gen.ts +18 -0
  116. package/src/components/table-handle/table-handle-drag-preview/render-preview.ts +80 -0
  117. package/src/components/table-handle/table-handle-drag-preview/setup.ts +67 -0
  118. package/src/components/table-handle/table-handle-drag-preview/types.ts +17 -0
  119. package/src/components/table-handle/table-handle-drag-preview/updater.ts +101 -0
  120. package/src/components/table-handle/table-handle-drop-indicator/calc-drag-over.ts +44 -0
  121. package/src/components/table-handle/table-handle-drop-indicator/element.gen.ts +18 -0
  122. package/src/components/table-handle/table-handle-drop-indicator/setup.ts +56 -0
  123. package/src/components/table-handle/table-handle-drop-indicator/types.ts +18 -0
  124. package/src/components/table-handle/table-handle-drop-indicator/updater.ts +110 -0
  125. package/src/components/table-handle/table-handle-popover-content/element.gen.ts +18 -0
  126. package/src/components/table-handle/table-handle-popover-content/setup.ts +90 -0
  127. package/src/components/table-handle/table-handle-popover-content/types.ts +40 -0
  128. package/src/components/table-handle/table-handle-popover-item/element.gen.ts +18 -0
  129. package/src/components/table-handle/table-handle-popover-item/setup.ts +23 -0
  130. package/src/components/table-handle/table-handle-popover-item/types.ts +24 -0
  131. package/src/components/table-handle/table-handle-root/element.gen.ts +18 -0
  132. package/src/components/table-handle/table-handle-root/setup.ts +93 -0
  133. package/src/components/table-handle/table-handle-root/types.ts +26 -0
  134. package/src/components/table-handle/table-handle-row-root/element.gen.ts +18 -0
  135. package/src/components/table-handle/table-handle-row-root/setup.ts +77 -0
  136. package/src/components/table-handle/table-handle-row-root/types.ts +75 -0
  137. package/src/components/table-handle/table-handle-row-trigger/element.gen.ts +18 -0
  138. package/src/components/table-handle/table-handle-row-trigger/setup.ts +74 -0
  139. package/src/components/table-handle/table-handle-row-trigger/types.ts +26 -0
  140. package/src/components/table-handle/utils.ts +107 -0
  141. package/src/components/tooltip/index.gen.ts +13 -0
  142. package/src/components/tooltip/tooltip-content/element.gen.ts +18 -0
  143. package/src/components/tooltip/tooltip-content/setup.ts +1 -0
  144. package/src/components/tooltip/tooltip-content/types.ts +12 -0
  145. package/src/components/tooltip/tooltip-root/element.gen.ts +18 -0
  146. package/src/components/tooltip/tooltip-root/setup.ts +1 -0
  147. package/src/components/tooltip/tooltip-root/types.ts +12 -0
  148. package/src/components/tooltip/tooltip-trigger/element.gen.ts +18 -0
  149. package/src/components/tooltip/tooltip-trigger/setup.ts +1 -0
  150. package/src/components/tooltip/tooltip-trigger/types.ts +12 -0
  151. package/src/hooks/use-editor-extension.ts +19 -0
  152. package/src/hooks/use-editor-focus-event.ts +23 -0
  153. package/src/hooks/use-editor-typing.ts +36 -0
  154. package/src/hooks/use-editor-update-event.ts +23 -0
  155. package/src/hooks/use-first-rendering.ts +20 -0
  156. package/src/hooks/use-keymap.ts +20 -0
  157. package/src/hooks/use-scrolling.ts +33 -0
  158. package/src/hooks/use-selecting.ts +63 -0
  159. package/src/index.ts +1 -0
  160. package/src/utils/assign-styles.ts +14 -0
  161. package/src/utils/clone-element.ts +110 -0
  162. package/src/utils/css-feature-detection.ts +9 -0
  163. package/src/utils/fade-color.ts +15 -0
  164. package/src/utils/get-box-element.ts +20 -0
  165. package/src/utils/get-client-rect.ts +35 -0
  166. package/src/utils/get-default-state.spec.ts +50 -0
  167. package/src/utils/get-default-state.ts +22 -0
  168. package/src/utils/get-effective-background-color.ts +21 -0
  169. package/src/utils/get-safe-editor-view.ts +10 -0
  170. package/src/utils/inject-style.ts +11 -0
  171. package/src/utils/is-finite-positive-number.ts +3 -0
  172. package/src/utils/max-z-index.ts +3 -0
  173. package/src/utils/throttle.ts +17 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-scrolling-BNfsQs3S.js","names":[],"sources":["../src/utils/assign-styles.ts","../src/hooks/use-scrolling.ts"],"sourcesContent":["import type { ConditionalPick } from 'type-fest'\n\n// Only include CSS properties whose value type is `string`\ntype StringStyleDeclaration = Partial<ConditionalPick<CSSStyleDeclaration, string>>\n\n/**\n * A type-safe version of `Object.assign` for `element.style`.\n */\nexport function assignStyles(\n element: HTMLElement | SVGElement | MathMLElement,\n styles: StringStyleDeclaration,\n): void {\n Object.assign(element.style, styles)\n}\n","import {\n createSignal,\n useEffect,\n type ConnectableElement,\n type ReadonlySignal,\n} from '@aria-ui/core'\nimport { getNearestOverflowAncestor } from '@zag-js/dom-query'\n\nexport function useScrolling(host: ConnectableElement): ReadonlySignal<boolean> {\n const scrolling = createSignal(false)\n\n useEffect(host, () => {\n const scrollableParent = getNearestOverflowAncestor(host)\n\n const handleScroll = () => {\n scrolling.set(true)\n }\n\n const handleMouseMove = () => {\n scrolling.set(false)\n }\n\n scrollableParent.addEventListener('scroll', handleScroll, { passive: true })\n window.addEventListener('mousemove', handleMouseMove, { passive: true })\n\n return () => {\n scrollableParent.removeEventListener('scroll', handleScroll)\n window.removeEventListener('mousemove', handleMouseMove)\n }\n })\n\n return scrolling\n}\n"],"mappings":";;;;;;;AAQA,SAAgB,aACd,SACA,QACM;AACN,QAAO,OAAO,QAAQ,OAAO,OAAO;;;;;ACJtC,SAAgB,aAAa,MAAmD;CAC9E,MAAM,YAAY,aAAa,MAAM;AAErC,WAAU,YAAY;EACpB,MAAM,mBAAmB,2BAA2B,KAAK;EAEzD,MAAM,qBAAqB;AACzB,aAAU,IAAI,KAAK;;EAGrB,MAAM,wBAAwB;AAC5B,aAAU,IAAI,MAAM;;AAGtB,mBAAiB,iBAAiB,UAAU,cAAc,EAAE,SAAS,MAAM,CAAC;AAC5E,SAAO,iBAAiB,aAAa,iBAAiB,EAAE,SAAS,MAAM,CAAC;AAExE,eAAa;AACX,oBAAiB,oBAAoB,UAAU,aAAa;AAC5D,UAAO,oBAAoB,aAAa,gBAAgB;;GAE1D;AAEF,QAAO"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prosekit/web",
3
3
  "type": "module",
4
- "version": "0.7.2",
4
+ "version": "0.7.4",
5
5
  "private": false,
6
6
  "description": "A collection of web components for ProseKit",
7
7
  "author": {
@@ -64,31 +64,32 @@
64
64
  }
65
65
  },
66
66
  "files": [
67
- "dist"
67
+ "dist",
68
+ "src"
68
69
  ],
69
70
  "dependencies": {
70
- "@aria-ui/collection": "^0.0.5",
71
- "@aria-ui/core": "^0.0.21",
72
- "@aria-ui/listbox": "^0.0.24",
73
- "@aria-ui/menu": "^0.0.20",
74
- "@aria-ui/overlay": "^0.0.24",
75
- "@aria-ui/popover": "^0.0.27",
76
- "@aria-ui/presence": "^0.0.19",
77
- "@aria-ui/tooltip": "^0.0.29",
78
- "@floating-ui/dom": "^1.7.3",
71
+ "@aria-ui/core": "^0.0.22",
72
+ "@aria-ui/listbox": "^0.0.25",
73
+ "@aria-ui/menu": "^0.0.21",
74
+ "@aria-ui/overlay": "^0.0.25",
75
+ "@aria-ui/popover": "^0.0.28",
76
+ "@aria-ui/presence": "^0.0.20",
77
+ "@aria-ui/tooltip": "^0.0.30",
78
+ "@floating-ui/dom": "^1.7.4",
79
79
  "@ocavue/utils": "^0.7.1",
80
- "@zag-js/dom-query": "^1.21.1",
81
- "prosemirror-tables": "^1.7.1",
82
- "@prosekit/core": "^0.8.3",
83
- "@prosekit/extensions": "^0.11.2",
84
- "@prosekit/pm": "^0.1.11"
80
+ "@zag-js/dom-query": "^1.22.1",
81
+ "prosemirror-tables": "^1.8.1",
82
+ "@prosekit/core": "^0.8.4",
83
+ "@prosekit/extensions": "^0.11.5",
84
+ "@prosekit/pm": "^0.1.12"
85
85
  },
86
86
  "devDependencies": {
87
- "tsdown": "^0.13.2",
88
- "typescript": "~5.8.3",
87
+ "tsdown": "^0.14.2",
88
+ "type-fest": "^4.41.0",
89
+ "typescript": "~5.9.2",
89
90
  "vitest": "^3.2.4",
90
- "@prosekit/config-vitest": "0.0.0",
91
- "@prosekit/config-tsdown": "0.0.0"
91
+ "@prosekit/config-tsdown": "0.0.0",
92
+ "@prosekit/config-vitest": "0.0.0"
92
93
  },
93
94
  "publishConfig": {
94
95
  "dev": {}
@@ -0,0 +1,18 @@
1
+ import { defineCustomElement, registerCustomElement, type BaseElementConstructor } from "@aria-ui/core"
2
+
3
+ import { useAutocompleteEmpty } from "./setup"
4
+ import { autocompleteEmptyEvents, autocompleteEmptyProps, type AutocompleteEmptyEvents, type AutocompleteEmptyProps } from "./types"
5
+
6
+ const AutocompleteEmptyElementBase: BaseElementConstructor<AutocompleteEmptyProps> = defineCustomElement<
7
+ AutocompleteEmptyProps,
8
+ AutocompleteEmptyEvents
9
+ >({
10
+ props: autocompleteEmptyProps,
11
+ events: autocompleteEmptyEvents,
12
+ setup: useAutocompleteEmpty,
13
+ })
14
+ class AutocompleteEmptyElement extends AutocompleteEmptyElementBase {}
15
+
16
+ registerCustomElement('prosekit-autocomplete-empty', AutocompleteEmptyElement)
17
+
18
+ export { AutocompleteEmptyElement }
@@ -0,0 +1,6 @@
1
+ import { useListboxEmpty } from '@aria-ui/listbox/elements'
2
+
3
+ /**
4
+ * @internal
5
+ */
6
+ export const useAutocompleteEmpty: typeof useListboxEmpty = useListboxEmpty
@@ -0,0 +1,16 @@
1
+ import type {
2
+ EventDeclarations,
3
+ PropDeclarations,
4
+ } from '@aria-ui/core'
5
+
6
+ /** @internal */
7
+ export interface AutocompleteEmptyProps {}
8
+
9
+ /** @internal */
10
+ export const autocompleteEmptyProps: PropDeclarations<AutocompleteEmptyProps> = {}
11
+
12
+ /** @internal */
13
+ export interface AutocompleteEmptyEvents {}
14
+
15
+ /** @internal */
16
+ export const autocompleteEmptyEvents: EventDeclarations<AutocompleteEmptyEvents> = {}
@@ -0,0 +1,18 @@
1
+ import { defineCustomElement, registerCustomElement, type BaseElementConstructor } from "@aria-ui/core"
2
+
3
+ import { useAutocompleteItem } from "./setup"
4
+ import { autocompleteItemEvents, autocompleteItemProps, type AutocompleteItemEvents, type AutocompleteItemProps } from "./types"
5
+
6
+ const AutocompleteItemElementBase: BaseElementConstructor<AutocompleteItemProps> = defineCustomElement<
7
+ AutocompleteItemProps,
8
+ AutocompleteItemEvents
9
+ >({
10
+ props: autocompleteItemProps,
11
+ events: autocompleteItemEvents,
12
+ setup: useAutocompleteItem,
13
+ })
14
+ class AutocompleteItemElement extends AutocompleteItemElementBase {}
15
+
16
+ registerCustomElement('prosekit-autocomplete-item', AutocompleteItemElement)
17
+
18
+ export { AutocompleteItemElement }
@@ -0,0 +1,38 @@
1
+ import {
2
+ useEffect,
3
+ useEventListener,
4
+ type ConnectableElement,
5
+ type SetupOptions,
6
+ } from '@aria-ui/core'
7
+ import { useListboxItem } from '@aria-ui/listbox/elements'
8
+
9
+ import { openContext } from '../context'
10
+
11
+ import type {
12
+ AutocompleteItemEvents,
13
+ AutocompleteItemProps,
14
+ } from './types'
15
+
16
+ /**
17
+ * @internal
18
+ */
19
+ export function useAutocompleteItem(
20
+ element: ConnectableElement,
21
+ { state, emit }: SetupOptions<AutocompleteItemProps, AutocompleteItemEvents>,
22
+ ): void {
23
+ useListboxItem(element, { state, emit })
24
+
25
+ const open = openContext.consume(element)
26
+
27
+ useEffect(element, () => {
28
+ // Check the text content again when the open state changes
29
+ if (!state.value.peek() && open.get()) {
30
+ state.value.set(element.textContent ?? '')
31
+ }
32
+ })
33
+
34
+ useEventListener(element, 'pointerdown', (event) => {
35
+ // Prevent the editor from losing focus
36
+ event.preventDefault()
37
+ })
38
+ }
@@ -0,0 +1,31 @@
1
+ import type {
2
+ EventDeclarations,
3
+ PropDeclarations,
4
+ } from '@aria-ui/core'
5
+ import {
6
+ listboxItemEvents,
7
+ type ListboxItemEvents,
8
+ } from '@aria-ui/listbox'
9
+
10
+ export interface AutocompleteItemProps {
11
+ /**
12
+ * The value of the item, which will be matched against the query.
13
+ *
14
+ * If not provided, the value is the item's text content.
15
+ *
16
+ * @default ""
17
+ */
18
+ value: string
19
+ }
20
+
21
+ /** @internal */
22
+ export const autocompleteItemProps: PropDeclarations<AutocompleteItemProps> = {
23
+ value: {
24
+ default: '',
25
+ },
26
+ }
27
+
28
+ export interface AutocompleteItemEvents extends ListboxItemEvents {}
29
+
30
+ /** @internal */
31
+ export const autocompleteItemEvents: EventDeclarations<AutocompleteItemEvents> = listboxItemEvents
@@ -0,0 +1,18 @@
1
+ import { defineCustomElement, registerCustomElement, type BaseElementConstructor } from "@aria-ui/core"
2
+
3
+ import { useAutocompleteList } from "./setup"
4
+ import { autocompleteListEvents, autocompleteListProps, type AutocompleteListEvents, type AutocompleteListProps } from "./types"
5
+
6
+ const AutocompleteListElementBase: BaseElementConstructor<AutocompleteListProps> = defineCustomElement<
7
+ AutocompleteListProps,
8
+ AutocompleteListEvents
9
+ >({
10
+ props: autocompleteListProps,
11
+ events: autocompleteListEvents,
12
+ setup: useAutocompleteList,
13
+ })
14
+ class AutocompleteListElement extends AutocompleteListElementBase {}
15
+
16
+ registerCustomElement('prosekit-autocomplete-list', AutocompleteListElement)
17
+
18
+ export { AutocompleteListElement }
@@ -0,0 +1,140 @@
1
+ import {
2
+ createSignal,
3
+ useEffect,
4
+ type ConnectableElement,
5
+ type ReadonlySignal,
6
+ type SetupOptions,
7
+ type TypedEventTarget,
8
+ } from '@aria-ui/core'
9
+ import {
10
+ listboxProps,
11
+ useListbox,
12
+ type ListboxProps,
13
+ } from '@aria-ui/listbox/elements'
14
+ import {
15
+ defineDOMEventHandler,
16
+ Priority,
17
+ withPriority,
18
+ type Editor,
19
+ } from '@prosekit/core'
20
+
21
+ import { getStateWithDefaults } from '../../../utils/get-default-state'
22
+ import {
23
+ onSubmitContext,
24
+ openContext,
25
+ queryContext,
26
+ } from '../context'
27
+
28
+ import type {
29
+ AutocompleteListEvents,
30
+ AutocompleteListProps,
31
+ } from './types'
32
+
33
+ /**
34
+ * @internal
35
+ */
36
+ export function useAutocompleteList(
37
+ element: ConnectableElement,
38
+ { state, emit }: SetupOptions<AutocompleteListProps, AutocompleteListEvents>,
39
+ ): void {
40
+ const open = openContext.consume(element)
41
+ const query = queryContext.consume(element)
42
+ const onSubmit = onSubmitContext.consume(element)
43
+
44
+ const keydownTarget = useKeyDownTarget(element, open, state.editor)
45
+
46
+ const listboxState = getStateWithDefaults<ListboxProps>(
47
+ { filter: state.filter, eventTarget: createSignal(keydownTarget) },
48
+ listboxProps,
49
+ )
50
+
51
+ useEffect(element, () => {
52
+ element.addEventListener('valueChange', () => {
53
+ if (onSubmit) {
54
+ onSubmit.get()?.()
55
+ }
56
+ })
57
+ })
58
+
59
+ useListbox(element, { state: listboxState, emit })
60
+
61
+ useEffect(element, () => {
62
+ listboxState.query.set(query.get())
63
+ })
64
+
65
+ useEffect(element, () => {
66
+ if (!open.get()) {
67
+ listboxState.value.set('')
68
+ query.set('')
69
+ }
70
+ })
71
+
72
+ // Reset the focused item when the popover is open
73
+ useEffect(element, () => {
74
+ if (!open.get()) {
75
+ listboxState.autoFocus.set(false)
76
+ } else {
77
+ let canceled = false
78
+
79
+ requestAnimationFrame(() => {
80
+ if (canceled) return
81
+ listboxState.autoFocus.set(true)
82
+ })
83
+
84
+ return () => {
85
+ canceled = true
86
+ }
87
+ }
88
+ })
89
+
90
+ // The autocomplete list should not be focusable because the editor will get
91
+ // the focus during typing.
92
+ useEffect(element, () => {
93
+ element.tabIndex = -1
94
+ })
95
+ }
96
+
97
+ function useKeyDownTarget(
98
+ element: ConnectableElement,
99
+ open: ReadonlySignal<boolean>,
100
+ editor: ReadonlySignal<Editor | null>,
101
+ ): TypedEventTarget<'keydown'> {
102
+ const keydownHandlers: ((event: KeyboardEvent) => void)[] = []
103
+
104
+ useEffect(element, () => {
105
+ const editorValue = editor.get()
106
+
107
+ if (!editorValue) {
108
+ return
109
+ }
110
+
111
+ const extension = defineDOMEventHandler(
112
+ 'keydown',
113
+ (view, event): boolean => {
114
+ if (view.composing || event.defaultPrevented || !open.get()) {
115
+ return false
116
+ }
117
+ keydownHandlers.forEach((handler) => handler(event))
118
+ return event.defaultPrevented
119
+ },
120
+ )
121
+
122
+ return editorValue.use(withPriority(extension, Priority.highest))
123
+ })
124
+
125
+ return {
126
+ addEventListener: (type, listener) => {
127
+ if (type === 'keydown') {
128
+ keydownHandlers.push(listener)
129
+ }
130
+ },
131
+ removeEventListener: (type, listener) => {
132
+ if (type === 'keydown') {
133
+ const index = keydownHandlers.indexOf(listener)
134
+ if (index !== -1) {
135
+ keydownHandlers.splice(index, 1)
136
+ }
137
+ }
138
+ },
139
+ }
140
+ }
@@ -0,0 +1,30 @@
1
+ import type {
2
+ EventDeclarations,
3
+ PropDeclarations,
4
+ } from '@aria-ui/core'
5
+ import {
6
+ listboxEvents,
7
+ listboxProps,
8
+ type ListboxEvents,
9
+ type ListboxProps,
10
+ } from '@aria-ui/listbox'
11
+ import type { Editor } from '@prosekit/core'
12
+
13
+ export interface AutocompleteListProps extends Pick<ListboxProps, 'filter'> {
14
+ /**
15
+ * The ProseKit editor instance.
16
+ *
17
+ * @default null
18
+ * @hidden
19
+ */
20
+ editor: Editor | null
21
+ }
22
+
23
+ export const autocompleteListProps: PropDeclarations<AutocompleteListProps> = {
24
+ filter: listboxProps.filter,
25
+ editor: { default: null },
26
+ }
27
+
28
+ export interface AutocompleteListEvents extends ListboxEvents {}
29
+
30
+ export const autocompleteListEvents: EventDeclarations<AutocompleteListEvents> = { ...listboxEvents }
@@ -0,0 +1,18 @@
1
+ import { defineCustomElement, registerCustomElement, type BaseElementConstructor } from "@aria-ui/core"
2
+
3
+ import { useAutocompletePopover } from "./setup"
4
+ import { autocompletePopoverEvents, autocompletePopoverProps, type AutocompletePopoverEvents, type AutocompletePopoverProps } from "./types"
5
+
6
+ const AutocompletePopoverElementBase: BaseElementConstructor<AutocompletePopoverProps> = defineCustomElement<
7
+ AutocompletePopoverProps,
8
+ AutocompletePopoverEvents
9
+ >({
10
+ props: autocompletePopoverProps,
11
+ events: autocompletePopoverEvents,
12
+ setup: useAutocompletePopover,
13
+ })
14
+ class AutocompletePopoverElement extends AutocompletePopoverElementBase {}
15
+
16
+ registerCustomElement('prosekit-autocomplete-popover', AutocompletePopoverElement)
17
+
18
+ export { AutocompletePopoverElement }
@@ -0,0 +1,21 @@
1
+ import {
2
+ describe,
3
+ expect,
4
+ it,
5
+ } from 'vitest'
6
+
7
+ import { defaultQueryBuilder } from './helpers'
8
+
9
+ describe('defaultQueryBuilder', () => {
10
+ it('can remove extra spaces', () => {
11
+ const str = ' a '
12
+ const match = /.*/.exec(str)!
13
+ expect(defaultQueryBuilder(match)).toMatchInlineSnapshot('"a"')
14
+ })
15
+
16
+ it('can remove punctuations', () => {
17
+ const str = ' a ~!@#$%^&*()_+`-={}|[]\\:;"\'<>?,./ b '
18
+ const match = /.*/.exec(str)!
19
+ expect(defaultQueryBuilder(match)).toMatchInlineSnapshot('"a b"')
20
+ })
21
+ })
@@ -0,0 +1,7 @@
1
+ export function defaultQueryBuilder(match: RegExpExecArray): string {
2
+ return match[0]
3
+ .toLowerCase()
4
+ .replace(/[!"#$%&'()*+,-./:;<=>?@[\\\]^_`{|}~]/g, '')
5
+ .replace(/\s\s+/g, ' ')
6
+ .trim()
7
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ createComputed,
3
+ createSignal,
4
+ useAnimationFrame,
5
+ useAttribute,
6
+ useEffect,
7
+ type ConnectableElement,
8
+ type ReadonlySignal,
9
+ type SetupOptions,
10
+ type Signal,
11
+ } from '@aria-ui/core'
12
+ import { useOverlayPositionerState } from '@aria-ui/overlay/elements'
13
+ import { usePresence } from '@aria-ui/presence'
14
+ import {
15
+ defineKeymap,
16
+ Priority,
17
+ withPriority,
18
+ type Editor,
19
+ } from '@prosekit/core'
20
+ import {
21
+ AutocompleteRule,
22
+ defineAutocomplete,
23
+ type MatchHandler,
24
+ } from '@prosekit/extensions/autocomplete'
25
+
26
+ import { useEditorExtension } from '../../../hooks/use-editor-extension'
27
+ import { useFirstRendering } from '../../../hooks/use-first-rendering'
28
+ import { getSafeEditorView } from '../../../utils/get-safe-editor-view'
29
+ import {
30
+ onSubmitContext,
31
+ openContext,
32
+ queryContext,
33
+ } from '../context'
34
+
35
+ import { defaultQueryBuilder } from './helpers'
36
+ import type {
37
+ AutocompletePopoverEvents,
38
+ AutocompletePopoverProps,
39
+ } from './types'
40
+
41
+ /**
42
+ * @internal
43
+ */
44
+ export function useAutocompletePopover(
45
+ host: ConnectableElement,
46
+ {
47
+ state,
48
+ emit,
49
+ }: SetupOptions<AutocompletePopoverProps, AutocompletePopoverEvents>,
50
+ ): void {
51
+ const { editor, regex, ...overlayState } = state
52
+
53
+ const reference = createSignal<Element | null>(null)
54
+ const query = createSignal<string>('')
55
+ const onDismiss = createSignal<VoidFunction | null>(null)
56
+ const onSubmit = createSignal<VoidFunction | null>(null)
57
+ const presence = createComputed(() => !!reference.get())
58
+
59
+ queryContext.provide(host, query)
60
+ onSubmitContext.provide(host, onSubmit)
61
+ openContext.provide(host, presence)
62
+
63
+ useEscapeKeydown(host, editor, createKeymapHandler(onDismiss, presence))
64
+
65
+ useAutocompleteExtension(
66
+ host,
67
+ editor,
68
+ regex,
69
+ reference,
70
+ query,
71
+ onDismiss,
72
+ onSubmit,
73
+ )
74
+
75
+ useOverlayPositionerState(host, overlayState, { reference })
76
+
77
+ useAttribute(host, 'data-state', () => (presence.get() ? 'open' : 'closed'))
78
+ usePresence(host, presence)
79
+
80
+ const firstRendering = useFirstRendering(host)
81
+
82
+ useEffect(host, () => {
83
+ const queryValue = query.get()
84
+
85
+ if (!firstRendering.peek()) {
86
+ emit('queryChange', queryValue)
87
+ }
88
+ })
89
+
90
+ useAnimationFrame(host, () => {
91
+ const presenceValue = presence.get()
92
+ return () => {
93
+ emit('openChange', presenceValue)
94
+ }
95
+ })
96
+ }
97
+
98
+ function useAutocompleteExtension(
99
+ host: ConnectableElement,
100
+ editor: ReadonlySignal<Editor | null>,
101
+ regex: ReadonlySignal<RegExp | null>,
102
+ reference: Signal<Element | null>,
103
+ query: Signal<string>,
104
+ onDismiss: Signal<VoidFunction | null>,
105
+ onSubmit: Signal<VoidFunction | null>,
106
+ ) {
107
+ useEffect(host, () => {
108
+ const editorValue = editor.get()
109
+ const regexValue = regex.get()
110
+
111
+ if (!editorValue || !regexValue) {
112
+ return
113
+ }
114
+
115
+ const rule = createAutocompleteRule(
116
+ editorValue,
117
+ regexValue,
118
+ reference,
119
+ query,
120
+ onDismiss,
121
+ onSubmit,
122
+ )
123
+ const extension = defineAutocomplete(rule)
124
+ return editorValue.use(extension)
125
+ })
126
+ }
127
+
128
+ function createAutocompleteRule(
129
+ editor: Editor,
130
+ regex: RegExp,
131
+ reference: Signal<Element | null>,
132
+ query: Signal<string>,
133
+ onDismiss: Signal<VoidFunction | null>,
134
+ onSubmit: Signal<VoidFunction | null>,
135
+ ) {
136
+ const handleEnter: MatchHandler = (options) => {
137
+ const view = getSafeEditorView(editor)
138
+ const span = view?.dom.querySelector('.prosemirror-prediction-match')
139
+
140
+ if (span) {
141
+ reference.set(span)
142
+ }
143
+
144
+ query.set(defaultQueryBuilder(options.match))
145
+ onDismiss.set(options.ignoreMatch)
146
+ onSubmit.set(options.deleteMatch)
147
+ }
148
+
149
+ const handleLeave = () => {
150
+ reference.set(null)
151
+ query.set('')
152
+ }
153
+
154
+ return new AutocompleteRule({
155
+ regex,
156
+ onEnter: handleEnter,
157
+ onLeave: handleLeave,
158
+ })
159
+ }
160
+
161
+ function createKeymapHandler(
162
+ handler: ReadonlySignal<VoidFunction | null>,
163
+ enabled: ReadonlySignal<boolean>,
164
+ ) {
165
+ return (): boolean => {
166
+ if (!enabled.get()) {
167
+ return false
168
+ }
169
+
170
+ const fn = handler.peek()
171
+ if (!fn) return false
172
+ fn()
173
+ return true
174
+ }
175
+ }
176
+
177
+ function useEscapeKeydown(
178
+ host: ConnectableElement,
179
+ editor: ReadonlySignal<Editor | null>,
180
+ handler: () => boolean,
181
+ ): void {
182
+ const keymap = { Escape: handler }
183
+ const extension = withPriority(defineKeymap(keymap), Priority.highest)
184
+ useEditorExtension(host, editor, extension)
185
+ }