@estiva-app/ui 0.51.0 → 0.53.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.
@@ -44,6 +44,15 @@ export interface SuggestionPopupOptions<T> {
44
44
  sections?: (items: T[], query: string) => readonly SuggestionMenuSection<T>[]
45
45
  itemKey: (item: T) => string
46
46
  row: (item: T) => SuggestionMenuRow
47
+ /**
48
+ * With `refresh`: called with a listener while the list is open; the app
49
+ * calls it when its data has changed (a search landing), and the open list is
50
+ * drawn again for the same query. Returns the unsubscribe, run when the list
51
+ * closes however it closes.
52
+ */
53
+ subscribe?: (listener: () => void) => () => void
54
+ /** The items for a query, as the plugin's `items` answers — asked again when `subscribe` fires. */
55
+ refresh?: (query: string) => T[]
47
56
  }
48
57
 
49
58
  type Props<T> = SuggestionProps<T> & { options: SuggestionPopupOptions<T> }
@@ -88,7 +97,12 @@ export function suggestionPopup<T>(options: SuggestionPopupOptions<T>): NonNulla
88
97
  return () => {
89
98
  let component: ReactRenderer<SuggestionMenuHandle, Props<T>> | null = null
90
99
  let container: HTMLDivElement | null = null
100
+ let last: SuggestionProps<T> | null = null
101
+ let unsubscribe: (() => void) | undefined
91
102
  const exit = () => {
103
+ unsubscribe?.()
104
+ unsubscribe = undefined
105
+ last = null
92
106
  if (!component) return
93
107
  openCount = Math.max(0, openCount - 1)
94
108
  lastClose = Date.now()
@@ -104,8 +118,22 @@ export function suggestionPopup<T>(options: SuggestionPopupOptions<T>): NonNulla
104
118
  container = document.createElement('div')
105
119
  document.body.appendChild(container)
106
120
  container.appendChild(component.element)
121
+ last = props
122
+ const { subscribe, refresh } = options
123
+ if (subscribe && refresh) {
124
+ // The plugin asks for items only when the query changes; an answer
125
+ // landing later draws the open list again with the same props.
126
+ unsubscribe = subscribe(() => {
127
+ if (!last || !component) return
128
+ last = { ...last, items: refresh(last.query) }
129
+ component.updateProps({ ...last, options })
130
+ })
131
+ }
132
+ },
133
+ onUpdate: (props) => {
134
+ last = props
135
+ component?.updateProps({ ...props, options })
107
136
  },
108
- onUpdate: (props) => component?.updateProps({ ...props, options }),
109
137
  onKeyDown: ({ event }: SuggestionKeyDownProps) => {
110
138
  if (event.key === 'Escape') {
111
139
  exit()
@@ -68,6 +68,7 @@ fork freely, owe nothing back, mention it on the package's ticket.
68
68
  | One window to find something and act on it, opened by a shortcut | **CommandPalette** — rows, forms and written answers, one level at a time |
69
69
  | A list that opens as you type in an editor — `/` for commands, `@` for people | **SuggestionMenu** — the editor keeps the caret and hands it the arrow keys; `suggestionPopup` from `@estiva-app/ui/editor` wires it to a Tiptap plugin |
70
70
  | Bold, italic and the like over selected text in an editor | **SelectionToolbar**, from `@estiva-app/ui/editor` — it follows the selection by itself |
71
+ | A composer's `@`, `!@`, `[` and `/` — mention a person, reference something, format or insert | **ComposerTriggers** — `PersonMention`, `UrgentPersonMention`, `CaptionedReference`, `ReferenceTrigger` and `SlashCommands` from `@estiva-app/ui/editor`; the app passes its people, its items and its own `/` rows |
71
72
  | An editor that saves a block document and must keep its block ids | **BlockNodes** — `BlockId`, `UnknownBlock`, `ReferenceNode` and `AttachmentNode` from `@estiva-app/ui/editor`, beside StarterKit and TableKit |
72
73
  | Anything with a title, body and footer | **DialogShell** |
73
74
  | "Are you sure?" | **ConfirmDialog** |