openscd / open-scd-core

Apache License 2.0
5 stars 8 forks source link

Finalize Action API #15

Closed ca-d closed 11 months ago

ca-d commented 2 years ago

NB since this proposal did not meet our needs regarding namespaced attributes, we amended the definition of our update action to accomodate them.

As with our Wizard API, we would like to move our Action API to a "pure declaration of intent" model:

Sending an EditorActionEvent will simply mean "Please make these changes", with all other implementation details of how the changes are to be made moved into the event listener. The event source can be sure that the action will be committed, no validation of the action will take place prior to committing it. This means it is up to the event source to make sure the action is sensible and will not result in loss of document validity.

This also means no more need to supply old and new properties, and no more need for the event source to differentiate between Create and Move Actions, which are both merged into the new Append action, supplying only the new place (parent and potentially a reference Node) where the Node in question is to appear. Both Append and Remove actions will work on Nodes instead of Elements, only the Update action will still require an Element to be given since only Elements have attributes that could be updated.

interface RemoveActionDetail {
  node: Node;
}

interface InsertActionDetail {
  parent: Node;
  node: Node;
  reference?: Node | null;
}

interface UpdateActionDetail {
  element: Element;
  attributes: Partial<Record<string, string | null>>;
}
ca-d commented 2 years ago

Since we need a generic way to update namespaced attributes in this API, we decided to amend this API as follows:

/** Represents the intent to `parent.insertBefore(node, reference)`. */
export type Insert = {
  parent: Node;
  node: Node;
  reference: Node | null;
};

export type NamespacedAttributeValue = {
  value: string | null;
  namespaceURI: string | null;
};
export type AttributeValue = string | null | NamespacedAttributeValue;
/** Represents the intent to set or remove (if null) attributes on element. */
export type Update = {
  element: Element;
  attributes: Partial<Record<string, AttributeValue>>;
};

/** Represents the intent to remove a node from its ownerDocument. */
export type Remove = {
  node: Node;
};

/** Represents the user's intent to change an XMLDocument. */
export type EditorAction = Insert | Update | Remove;

export function isInsert(action: EditorAction): action is Insert {
  return (action as Insert).parent !== undefined;
}

export function isNamespaced(
  value: AttributeValue
): value is NamespacedAttributeValue {
  return value !== null && typeof value !== 'string';
}
export function isUpdate(action: EditorAction): action is Update {
  return (action as Update).element !== undefined;
}

export function isRemove(action: EditorAction): action is Remove {
  return (
    (action as Insert).parent === undefined &&
    (action as Remove).node !== undefined
  );
}
JakobVogelsang commented 1 year ago

We can close this or shall we use it to have a documentation.