How to recognise if a widget is removed ?

Whats the best way to keep decorations aka WidgetPlugins in sync with a StateFiled ?

I got a StateField that that is updated from the “outside” (my vue application) with some values lets say its an array. For this i simply dispatch an effect like:

    effects: updateAdditionalState.of(vars),

where vars is an array of variables. After this I insert into my view a string representing that var “{{1}}” which is then styles by a match decorator and i “replace” the number value inside with the value in my state field

export const variableMatchingConfig = new MatchDecorator({
  regexp: /\{{(.*?)}}/g,
  decoration: (match, view) => {
    // a function that gets the variable via view.state.field(<myStateField>)
    const variable = getVariableFromStateByNumericLabel(view, parseInt(match[1]))
    return Decoration.replace({
      widget: new VariableWidget(variable?.name, variable?.uuid), 

All of this works just fine but now - how am I’m able to recognise that a widget(its atomic) was removed from the doc (f.e. via the delete key) and sync that with my StateField ? How is it possible to keep StateField and WidgetPlugins kinda in sync?

You could do a check where you see if the range set’s size changed after mapping, and if so, do a scan through it to figure out which widget that you still have in your outside data was removed.

Thanks for the fast answer!
And where do i get that RangeSet from i just got a extension for:

  • the state - a StateField thats in sync with the outside state
  • DecorationWidgets - that show the variable

I thought about to check the doc on every view update. And if my doc changes from

“Hey {{1}} whats up {{2}}” to “Hey {{1}} whats up” i would know that the second widget was removed and update the StateField regarding to this. But i thought there is a more convenient way to keep Decoration and State in sync…

Is there even a way to check if a widget is added between two other widgets ? Probably only by parsing the doc and some diffing right ?

Another solution could be to hack around that parsing stuff, i found out that the plugins internally are correctly listed as they are used within the doc

const plugin = view?.plugin(myPlugin)?.myPluginDeco

  // that access is some kind of a hack, but we were not able
  // to locate a proper way to access the currently used widgets and their values

  return plugin?.chunk?.[0]? => {
    return {
      title: deco?.widget?.title, // identifiers i added on Widget creation
      id: deco?.widget?.id, // identifiers i added on Widget creation

the order in that chunk value array does match the correct order as we display or added the widgets to the doc so I thought about using it to determine which widgets are available atm and in which order. But looks a bit odd to me :smiley: It would be nice to have a getter on accessing it in a more convenient way :pray:

Okay we could ignore this, i was able to do it how you suggested, the Decoration as RangeSet got everything I needed… will leave the code part here for reference if anyone else is searching for a similar solution

export const getVariableVariableWidgets = (view: EditorView) => {
  const decoSet = view?.plugin(myPlugin)?.myPluginDeco
  const variableWidgets = []

  const iterator = decoSet?.iter(0)
  while (iterator?.value != null) {
      // @ts-expect-error
      id: iterator.value?.widget?.id, // identifier added to the widget class
      // @ts-expect-error
      title: iterator.value?.widget?.title, // identifier added to the widget class