@weasel-js/select 1.9.3 → 1.9.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.
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ interface Selection<K> {
18
18
  readonly ids: readonly K[];
19
19
  readonly anchor: K | null;
20
20
  }
21
+ /** What a press is resolved against besides the selection: the order a range walks, and which ids may be multi-selected. */
21
22
  interface SelectContext<K> {
22
23
  /** The sequence a range walks. A range without one replaces. */
23
24
  readonly order?: readonly K[];
package/dist/index.js CHANGED
@@ -21,10 +21,14 @@ function select(state, id, intent, context = {}) {
21
21
  const { order } = context;
22
22
  if (order.includes(state.anchor) && order.includes(id)) {
23
23
  const ids = rangeOf(order, state.anchor, id, eligible);
24
- return ids.length > 0 ? { ids, anchor: state.anchor } : state;
24
+ return ids.length > 0 ? settle(state, { ids, anchor: state.anchor }) : state;
25
25
  }
26
26
  }
27
- return { ids: [id], anchor: id };
27
+ return settle(state, { ids: [id], anchor: id });
28
+ }
29
+ function settle(state, next) {
30
+ const same = next.anchor === state.anchor && next.ids.length === state.ids.length && next.ids.every((id, i) => id === state.ids[i]);
31
+ return same ? state : next;
28
32
  }
29
33
  function always() {
30
34
  return true;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/select.ts","../src/store.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** `single` always replaces; `multi` lets a policy's keys toggle and range. */\nexport type SelectionMode = 'single' | 'multi';\n\n/** A modifier key a policy can assign to toggling or ranging. */\nexport type SelectionExtendKey = 'shift' | 'meta' | 'ctrl';\n\n/** The modifiers held during a press. */\nexport type SelectModifiers = Readonly<Record<SelectionExtendKey, boolean>>;\n\n/** What a press does to the selection. */\nexport type SelectIntent = 'replace' | 'toggle' | 'range';\n\n/** Which keys toggle and which range, so a surface states its convention: a list usually\n * toggles on Cmd/Ctrl and ranges on shift, a canvas toggles on shift. */\nexport interface SelectPolicy {\n readonly mode: SelectionMode;\n readonly toggle?: SelectionExtendKey | readonly SelectionExtendKey[];\n readonly range?: SelectionExtendKey;\n}\n\n/** What is selected, and the end a range extends from. */\nexport interface Selection<K> {\n readonly ids: readonly K[];\n readonly anchor: K | null;\n}\n\nexport interface SelectContext<K> {\n /** The sequence a range walks. A range without one replaces. */\n readonly order?: readonly K[];\n /** Whether an id may join a multi-selection. Ineligible ids are skipped by a range, dropped\n * when the selection grows, and selected alone when pressed. Default: every id. */\n readonly eligible?: (id: K) => boolean;\n}\n\n/** The intent a press carries under `policy`. A range key beats a toggle key. */\nexport function intentOf(modifiers: SelectModifiers, policy: SelectPolicy): SelectIntent {\n if (policy.mode === 'single') return 'replace';\n if (policy.range && modifiers[policy.range]) return 'range';\n const toggle = typeof policy.toggle === 'string' ? [policy.toggle] : (policy.toggle ?? []);\n return toggle.some((key) => modifiers[key]) ? 'toggle' : 'replace';\n}\n\n/** The eligible ids from `from` to `to` in `order`, ends included, in order. Empty when either\n * end is not in `order`. */\nexport function rangeOf<K>(order: readonly K[], from: K, to: K, eligible: (id: K) => boolean = always): K[] {\n const a = order.indexOf(from);\n const b = order.indexOf(to);\n if (a < 0 || b < 0) return [];\n return order.slice(Math.min(a, b), Math.max(a, b) + 1).filter(eligible);\n}\n\n/** The selection after pressing `id` with `intent`. Returns `state` itself when nothing changes\n * hands, so a caller can compare by reference. */\nexport function select<K>(state: Selection<K>, id: K, intent: SelectIntent, context: SelectContext<K> = {}): Selection<K> {\n const eligible = context.eligible ?? always;\n if (intent === 'toggle' && eligible(id)) {\n const kept = state.ids.filter(eligible);\n return { ids: kept.includes(id) ? kept.filter((x) => x !== id) : [...kept, id], anchor: id };\n }\n if (intent === 'range' && state.anchor !== null && context.order) {\n const { order } = context;\n if (order.includes(state.anchor) && order.includes(id)) {\n const ids = rangeOf(order, state.anchor, id, eligible);\n return ids.length > 0 ? { ids, anchor: state.anchor } : state;\n }\n }\n return { ids: [id], anchor: id };\n}\n\nfunction always(): boolean {\n return true;\n}\n","/** Somewhere a selection can live outside the component that edits it. core's `Scene` satisfies\n * it, and so does any store with the same three methods. */\nexport interface SelectionStore<K> {\n getSelection(): readonly K[];\n setSelection(ids: readonly K[]): void;\n subscribe(listener: () => void): () => void;\n}\n\n/** A {@link SelectionStore} holding the ids itself. A listener reads the new value. */\nexport function createSelectionStore<K>(initial: readonly K[] = []): SelectionStore<K> {\n let ids = initial;\n const listeners = new Set<() => void>();\n return {\n getSelection: () => ids,\n setSelection(next) {\n ids = next;\n for (const listener of listeners) listener();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/select.ts","../src/store.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** `single` always replaces; `multi` lets a policy's keys toggle and range. */\nexport type SelectionMode = 'single' | 'multi';\n\n/** A modifier key a policy can assign to toggling or ranging. */\nexport type SelectionExtendKey = 'shift' | 'meta' | 'ctrl';\n\n/** The modifiers held during a press. */\nexport type SelectModifiers = Readonly<Record<SelectionExtendKey, boolean>>;\n\n/** What a press does to the selection. */\nexport type SelectIntent = 'replace' | 'toggle' | 'range';\n\n/** Which keys toggle and which range, so a surface states its convention: a list usually\n * toggles on Cmd/Ctrl and ranges on shift, a canvas toggles on shift. */\nexport interface SelectPolicy {\n readonly mode: SelectionMode;\n readonly toggle?: SelectionExtendKey | readonly SelectionExtendKey[];\n readonly range?: SelectionExtendKey;\n}\n\n/** What is selected, and the end a range extends from. */\nexport interface Selection<K> {\n readonly ids: readonly K[];\n readonly anchor: K | null;\n}\n\n/** What a press is resolved against besides the selection: the order a range walks, and which ids may be multi-selected. */\nexport interface SelectContext<K> {\n /** The sequence a range walks. A range without one replaces. */\n readonly order?: readonly K[];\n /** Whether an id may join a multi-selection. Ineligible ids are skipped by a range, dropped\n * when the selection grows, and selected alone when pressed. Default: every id. */\n readonly eligible?: (id: K) => boolean;\n}\n\n/** The intent a press carries under `policy`. A range key beats a toggle key. */\nexport function intentOf(modifiers: SelectModifiers, policy: SelectPolicy): SelectIntent {\n if (policy.mode === 'single') return 'replace';\n if (policy.range && modifiers[policy.range]) return 'range';\n const toggle = typeof policy.toggle === 'string' ? [policy.toggle] : (policy.toggle ?? []);\n return toggle.some((key) => modifiers[key]) ? 'toggle' : 'replace';\n}\n\n/** The eligible ids from `from` to `to` in `order`, ends included, in order. Empty when either\n * end is not in `order`. */\nexport function rangeOf<K>(order: readonly K[], from: K, to: K, eligible: (id: K) => boolean = always): K[] {\n const a = order.indexOf(from);\n const b = order.indexOf(to);\n if (a < 0 || b < 0) return [];\n return order.slice(Math.min(a, b), Math.max(a, b) + 1).filter(eligible);\n}\n\n/** The selection after pressing `id` with `intent`. Returns `state` itself when nothing changes\n * hands, so a caller can compare by reference. */\nexport function select<K>(state: Selection<K>, id: K, intent: SelectIntent, context: SelectContext<K> = {}): Selection<K> {\n const eligible = context.eligible ?? always;\n if (intent === 'toggle' && eligible(id)) {\n const kept = state.ids.filter(eligible);\n return { ids: kept.includes(id) ? kept.filter((x) => x !== id) : [...kept, id], anchor: id };\n }\n if (intent === 'range' && state.anchor !== null && context.order) {\n const { order } = context;\n if (order.includes(state.anchor) && order.includes(id)) {\n const ids = rangeOf(order, state.anchor, id, eligible);\n return ids.length > 0 ? settle(state, { ids, anchor: state.anchor }) : state;\n }\n }\n return settle(state, { ids: [id], anchor: id });\n}\n\nfunction settle<K>(state: Selection<K>, next: Selection<K>): Selection<K> {\n const same = next.anchor === state.anchor\n && next.ids.length === state.ids.length\n && next.ids.every((id, i) => id === state.ids[i]);\n return same ? state : next;\n}\n\nfunction always(): boolean {\n return true;\n}\n","/** Somewhere a selection can live outside the component that edits it. core's `Scene` satisfies\n * it, and so does any store with the same three methods. */\nexport interface SelectionStore<K> {\n getSelection(): readonly K[];\n setSelection(ids: readonly K[]): void;\n subscribe(listener: () => void): () => void;\n}\n\n/** A {@link SelectionStore} holding the ids itself. A listener reads the new value. */\nexport function createSelectionStore<K>(initial: readonly K[] = []): SelectionStore<K> {\n let ids = initial;\n const listeners = new Set<() => void>();\n return {\n getSelection: () => ids,\n setSelection(next) {\n ids = next;\n for (const listener of listeners) listener();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weasel-js/select",
3
- "version": "1.9.3",
3
+ "version": "1.9.4",
4
4
  "description": "Selection as a value: which ids a press selects, with toggle, range and anchor rules a surface configures. No React, no DOM.",
5
5
  "license": "MIT",
6
6
  "type": "module",