@prosekit/web 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"use-scrolling.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 { createSignal, useEffect, type HostElement } from '@aria-ui/core'\nimport { getNearestOverflowAncestor, useGlobalEventListener } from '@aria-ui/utils'\n\nexport function useScrolling(host: HostElement): () => boolean {\n const { get: getScrolling, set: setScrolling } = createSignal(false)\n const handleMouseMove = () => {\n setScrolling(false)\n }\n const handleScroll = () => {\n setScrolling(true)\n }\n\n useGlobalEventListener(host, 'mousemove', handleMouseMove)\n useGlobalEventListener(host, 'pointermove', handleMouseMove)\n\n useEffect(host, () => {\n const scrollableParent = getNearestOverflowAncestor(host)\n\n const abortController = new AbortController()\n const abortSignal = abortController.signal\n\n scrollableParent.addEventListener('scroll', handleScroll, {\n passive: true,\n signal: abortSignal,\n })\n\n return () => {\n abortController.abort()\n }\n })\n\n return getScrolling\n}\n"],"mappings":";;;;;AAQA,SAAgB,aACd,SACA,QACM;AACN,QAAO,OAAO,QAAQ,OAAO,OAAO;;ACTtC,SAAgB,aAAa,MAAkC;CAC7D,MAAM,EAAE,KAAK,cAAc,KAAK,iBAAiB,aAAa,MAAM;CACpE,MAAM,wBAAwB;AAC5B,eAAa,MAAM;;CAErB,MAAM,qBAAqB;AACzB,eAAa,KAAK;;AAGpB,wBAAuB,MAAM,aAAa,gBAAgB;AAC1D,wBAAuB,MAAM,eAAe,gBAAgB;AAE5D,WAAU,YAAY;EACpB,MAAM,mBAAmB,2BAA2B,KAAK;EAEzD,MAAM,kBAAkB,IAAI,iBAAiB;EAC7C,MAAM,cAAc,gBAAgB;AAEpC,mBAAiB,iBAAiB,UAAU,cAAc;GACxD,SAAS;GACT,QAAQ;GACT,CAAC;AAEF,eAAa;AACX,mBAAgB,OAAO;;GAEzB;AAEF,QAAO"}
1
+ {"version":3,"file":"use-scrolling.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 { createSignal, useEffect, type HostElement } from '@aria-ui/core'\nimport { getNearestOverflowAncestor, useGlobalEventListener } from '@aria-ui/utils'\n\nexport function useScrolling(host: HostElement): () => boolean {\n const { get: getScrolling, set: setScrolling } = createSignal(false)\n const handleMouseMove = () => {\n setScrolling(false)\n }\n const handleScroll = () => {\n setScrolling(true)\n }\n\n useGlobalEventListener(host, 'mousemove', handleMouseMove)\n useGlobalEventListener(host, 'pointermove', handleMouseMove)\n\n useEffect(host, () => {\n const scrollableParent = getNearestOverflowAncestor(host)\n\n const abortController = new AbortController()\n const abortSignal = abortController.signal\n\n scrollableParent.addEventListener('scroll', handleScroll, {\n passive: true,\n signal: abortSignal,\n })\n\n return () => {\n abortController.abort()\n }\n })\n\n return getScrolling\n}\n"],"mappings":";;;;;AAQA,SAAgB,aACd,SACA,QACM;CACN,OAAO,OAAO,QAAQ,OAAO,OAAO;;ACTtC,SAAgB,aAAa,MAAkC;CAC7D,MAAM,EAAE,KAAK,cAAc,KAAK,iBAAiB,aAAa,MAAM;CACpE,MAAM,wBAAwB;EAC5B,aAAa,MAAM;;CAErB,MAAM,qBAAqB;EACzB,aAAa,KAAK;;CAGpB,uBAAuB,MAAM,aAAa,gBAAgB;CAC1D,uBAAuB,MAAM,eAAe,gBAAgB;CAE5D,UAAU,YAAY;EACpB,MAAM,mBAAmB,2BAA2B,KAAK;EAEzD,MAAM,kBAAkB,IAAI,iBAAiB;EAC7C,MAAM,cAAc,gBAAgB;EAEpC,iBAAiB,iBAAiB,UAAU,cAAc;GACxD,SAAS;GACT,QAAQ;GACT,CAAC;EAEF,aAAa;GACX,gBAAgB,OAAO;;GAEzB;CAEF,OAAO"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@prosekit/web",
3
3
  "type": "module",
4
- "version": "0.8.0",
4
+ "version": "0.8.1",
5
5
  "private": false,
6
6
  "description": "A collection of web components for ProseKit",
7
7
  "author": {
@@ -83,14 +83,14 @@
83
83
  "@prosekit/pm": "^0.1.16"
84
84
  },
85
85
  "devDependencies": {
86
- "@aria-ui/cli": "^0.1.8",
87
- "tsdown": "^0.21.10",
86
+ "@aria-ui/cli": "^0.1.9",
87
+ "tsdown": "^0.22.0",
88
88
  "type-fest": "^5.6.0",
89
- "typescript": "~5.9.3",
89
+ "typescript": "~6.0.3",
90
90
  "vitest": "^4.1.5",
91
- "@prosekit/config-ts": "0.0.0",
91
+ "@prosekit/config-tsdown": "0.0.0",
92
92
  "@prosekit/config-vitest": "0.0.0",
93
- "@prosekit/config-tsdown": "0.0.0"
93
+ "@prosekit/config-ts": "0.0.0"
94
94
  },
95
95
  "scripts": {
96
96
  "build:tsc": "tsc -b tsconfig.json",
@@ -88,7 +88,7 @@ export function setupAutocompletePositioner(
88
88
  ): void {
89
89
  const getStore = autocompleteStoreContext.consume(host)
90
90
  const getOverlayStore = () => getStore()?.overlayStore
91
- setupOverlayPositioner(host, props as unknown as State<OverlayPositionerProps>, getOverlayStore)
91
+ setupOverlayPositioner(host, props satisfies State<OverlayPositionerProps>, getOverlayStore)
92
92
  }
93
93
 
94
94
  const AutocompletePositionerElementBase: HostElementConstructor<AutocompletePositionerProps> = defineCustomElement(
@@ -13,6 +13,7 @@ import {
13
13
  import { defaultItemFilter, type ItemFilter, type ListboxRootEvents } from '@aria-ui/elements/listbox'
14
14
  import { createOverlayStore, OpenChangeEvent, type OverlayStore } from '@aria-ui/elements/overlay'
15
15
  import { useEventListener } from '@aria-ui/utils'
16
+ import type { ReferenceElement, VirtualElement } from '@floating-ui/dom'
16
17
  import { defineDOMEventHandler, defineKeymap, withPriority, type Editor, type Extension, type Priority } from '@prosekit/core'
17
18
  import { AutocompleteRule, defineAutocomplete, type MatchHandler } from '@prosekit/extensions/autocomplete'
18
19
 
@@ -48,6 +49,16 @@ export interface AutocompleteRootProps {
48
49
  * @default defaultItemFilter
49
50
  */
50
51
  filter: ItemFilter | null
52
+
53
+ /**
54
+ * The reference to position the popup against. This can be a DOM element, a
55
+ * Floating UI virtual element, or a function that returns either of them.
56
+ * By default, the popup will be positioned against the text content that
57
+ * triggers the autocomplete.
58
+ *
59
+ * @default null
60
+ */
61
+ anchor: Element | VirtualElement | (() => Element | VirtualElement | null) | null
51
62
  }
52
63
 
53
64
  /** @internal */
@@ -57,6 +68,7 @@ export const AutocompleteRootPropsDeclaration: PropsDeclaration<AutocompleteRoot
57
68
  editor: { default: null, attribute: false },
58
69
  regex: { default: null, attribute: false },
59
70
  filter: { default: defaultItemFilter, attribute: false },
71
+ anchor: { default: null, attribute: false },
60
72
  })
61
73
 
62
74
  /**
@@ -95,7 +107,7 @@ interface RuleHandlers {
95
107
  }
96
108
 
97
109
  interface AutocompleteRuleDeps {
98
- reference: Signal<Element | undefined>
110
+ reference: Signal<ReferenceElement | undefined>
99
111
  handlers: RuleHandlers
100
112
  setQuery: (next: string) => void
101
113
  requestOpenChange: (open: boolean) => void
@@ -110,7 +122,7 @@ export function setupAutocompleteRoot(
110
122
  ): void {
111
123
  const getEditor = props.editor.get
112
124
 
113
- const reference = createSignal<Element | undefined>(undefined)
125
+ const reference = createSignal<ReferenceElement | undefined>(undefined)
114
126
  const open = createSignal(false)
115
127
  const query = createSignal('')
116
128
  const keyboardTarget = new KeyboardEventTarget()
@@ -159,7 +171,20 @@ export function setupAutocompleteRoot(
159
171
  host.dispatchEvent(new QueryChangeEvent(next))
160
172
  }
161
173
 
162
- useAutocompleteExtension(host, getEditor, props.regex.get, {
174
+ const getAnchor = (): ReferenceElement | null => {
175
+ const customAnchor = props.anchor.get()
176
+ if (customAnchor) {
177
+ if (typeof customAnchor === 'function') {
178
+ return customAnchor() || null
179
+ } else {
180
+ return customAnchor
181
+ }
182
+ }
183
+ const view = getSafeEditorView(getEditor())
184
+ return view?.dom.querySelector('.prosekit-autocomplete-match') || null
185
+ }
186
+
187
+ useAutocompleteExtension(host, getEditor, props.regex.get, getAnchor, {
163
188
  reference,
164
189
  handlers,
165
190
  setQuery,
@@ -202,6 +227,7 @@ function useAutocompleteExtension(
202
227
  host: HostElement,
203
228
  getEditor: () => Editor | null,
204
229
  getRegex: () => RegExp | null,
230
+ getAnchor: () => ReferenceElement | null,
205
231
  deps: AutocompleteRuleDeps,
206
232
  ) {
207
233
  useEffect(host, () => {
@@ -212,7 +238,7 @@ function useAutocompleteExtension(
212
238
  return
213
239
  }
214
240
 
215
- const rule = createAutocompleteRule(editor, regex, deps)
241
+ const rule = createAutocompleteRule(editor, regex, getAnchor, deps)
216
242
  const extension = defineAutocomplete(rule)
217
243
  return editor.use(extension)
218
244
  })
@@ -221,17 +247,14 @@ function useAutocompleteExtension(
221
247
  function createAutocompleteRule(
222
248
  editor: Editor,
223
249
  regex: RegExp,
250
+ getAnchor: () => ReferenceElement | null,
224
251
  deps: AutocompleteRuleDeps,
225
252
  ) {
226
253
  const { reference, handlers, setQuery, requestOpenChange } = deps
227
254
 
228
255
  const handleEnter: MatchHandler = (options) => {
229
- const view = getSafeEditorView(editor)
230
- const span = view?.dom.querySelector('.prosekit-autocomplete-match')
231
-
232
- if (span) {
233
- reference.set(span)
234
- }
256
+ const anchor = getAnchor()
257
+ reference.set(anchor || undefined)
235
258
 
236
259
  handlers.submit = options.deleteMatch
237
260
  handlers.dismiss = options.ignoreMatch