@moyarich/console-addon-selection 0.0.0-stage → 0.1.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.
package/README.md CHANGED
@@ -1,3 +1,33 @@
1
- # Temporary Holding Version
1
+ # @moyarich/console-addon-selection
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Select Console messages. Select message IDs individually or in visible ranges, with optional message actions and decorations.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @moyarich/console @moyarich/console-addon-selection
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```tsx
14
+ import { Console } from "@moyarich/console";
15
+ import { createConsoleSelectionAddon } from "@moyarich/console-addon-selection";
16
+
17
+ const addons = [createConsoleSelectionAddon()];
18
+
19
+ // Pass your application's structured messages to Console.
20
+ <Console messages={messages} addons={addons} />;
21
+ ```
22
+
23
+ The addon is optional and can be combined with other Console addons. The host application remains responsible for supplying and retaining messages.
24
+
25
+ ## Learn more
26
+
27
+ Explore [runnable package examples](./examples) and the [Console documentation](https://moyarich.github.io/console/) for available settings and integration patterns.
28
+
29
+ Review the [changelog](./CHANGELOG.md) for changes by version and [package metadata](./package.json) for dependency requirements.
30
+
31
+ ## License
32
+
33
+ MIT
@@ -0,0 +1,2 @@
1
+ /** Default selection indicator. No stylesheet or addon runtime required. */
2
+ export declare function SelectedMessageIndicator(): import("react").JSX.Element;
package/dist/index.cjs ADDED
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const consoleCore = require("@moyarich/console-core");
5
+ function SelectedMessageIndicator() {
6
+ return /* @__PURE__ */ jsxRuntime.jsx(
7
+ "span",
8
+ {
9
+ "aria-label": "Selected message",
10
+ title: "Selected",
11
+ style: { fontSize: 12, lineHeight: 1 },
12
+ children: "●"
13
+ }
14
+ );
15
+ }
16
+ const CONSOLE_SELECTION_ADDON_ID = "@moyarich/console-addon-selection";
17
+ const consoleSelectionService = consoleCore.createConsoleServiceToken(
18
+ `${CONSOLE_SELECTION_ADDON_ID}.controller`
19
+ );
20
+ function normalizeId(value) {
21
+ const normalized = value.trim();
22
+ return normalized || void 0;
23
+ }
24
+ function uniqueIds(ids) {
25
+ return Object.freeze(
26
+ Array.from(
27
+ new Set(ids.map((id) => normalizeId(id)).filter(Boolean))
28
+ )
29
+ );
30
+ }
31
+ function snapshotIds(data) {
32
+ const snapshot = data?.getSnapshot();
33
+ if (!snapshot) return [];
34
+ if (snapshot.mode === "console") {
35
+ return snapshot.visible.flatMap(
36
+ (message) => message.id ? [message.id] : []
37
+ );
38
+ }
39
+ return snapshot.visible.flatMap(({ entry }) => entry.id ? [entry.id] : []);
40
+ }
41
+ function createConsoleSelectionController(getData = () => void 0) {
42
+ let state = Object.freeze({
43
+ selectedIds: Object.freeze([])
44
+ });
45
+ const listeners = /* @__PURE__ */ new Set();
46
+ const update = (selectedIds, anchorId) => {
47
+ state = Object.freeze({
48
+ selectedIds: uniqueIds(selectedIds),
49
+ ...anchorId ? { anchorId } : {}
50
+ });
51
+ for (const listener of listeners) listener();
52
+ };
53
+ const controller = {
54
+ getState: () => state,
55
+ subscribe(listener) {
56
+ listeners.add(listener);
57
+ return () => listeners.delete(listener);
58
+ },
59
+ isSelected(id) {
60
+ const normalized = normalizeId(id);
61
+ return Boolean(normalized && state.selectedIds.includes(normalized));
62
+ },
63
+ select(id, additive = false) {
64
+ const normalized = normalizeId(id);
65
+ if (!normalized) return false;
66
+ const next = additive ? uniqueIds([...state.selectedIds, normalized]) : [normalized];
67
+ const changed = next.length !== state.selectedIds.length || next.some((value, index) => value !== state.selectedIds[index]);
68
+ if (changed || state.anchorId !== normalized) {
69
+ update(next, normalized);
70
+ }
71
+ return changed;
72
+ },
73
+ deselect(id) {
74
+ const normalized = normalizeId(id);
75
+ if (!normalized || !state.selectedIds.includes(normalized)) return false;
76
+ const next = state.selectedIds.filter((value) => value !== normalized);
77
+ update(next, state.anchorId === normalized ? void 0 : state.anchorId);
78
+ return true;
79
+ },
80
+ toggle(id) {
81
+ const normalized = normalizeId(id);
82
+ if (!normalized) return false;
83
+ if (controller.isSelected(normalized)) {
84
+ controller.deselect(normalized);
85
+ return false;
86
+ }
87
+ controller.select(normalized, true);
88
+ return true;
89
+ },
90
+ clear() {
91
+ if (state.selectedIds.length === 0 && !state.anchorId) return;
92
+ update([]);
93
+ },
94
+ setSelectedIds(ids) {
95
+ const next = uniqueIds(ids);
96
+ update(next, next.at(-1));
97
+ },
98
+ selectRange(fromId, toId) {
99
+ const ordered = snapshotIds(getData());
100
+ const start = ordered.indexOf(fromId);
101
+ const end = ordered.indexOf(toId);
102
+ if (start < 0 || end < 0) return state.selectedIds;
103
+ const [from, to] = start <= end ? [start, end] : [end, start];
104
+ const range = ordered.slice(from, to + 1);
105
+ update(range, toId);
106
+ return range;
107
+ },
108
+ getVisibleSelectedIds() {
109
+ const visible = new Set(snapshotIds(getData()));
110
+ return Object.freeze(state.selectedIds.filter((id) => visible.has(id)));
111
+ }
112
+ };
113
+ return controller;
114
+ }
115
+ function createDecoration(controller) {
116
+ return {
117
+ id: `${CONSOLE_SELECTION_ADDON_ID}:selected`,
118
+ placement: "gutter",
119
+ match({ message }) {
120
+ return Boolean(message.id && controller.isSelected(message.id));
121
+ },
122
+ render({ message }) {
123
+ if (!message.id || !controller.isSelected(message.id)) return void 0;
124
+ return /* @__PURE__ */ jsxRuntime.jsx(SelectedMessageIndicator, {});
125
+ }
126
+ };
127
+ }
128
+ function createMessageAction(controller) {
129
+ return {
130
+ id: `${CONSOLE_SELECTION_ADDON_ID}:toggle`,
131
+ label: "Toggle selection",
132
+ ariaLabel: "Toggle message selection",
133
+ visible({ message }) {
134
+ return Boolean(message.id);
135
+ },
136
+ onSelect({ message }) {
137
+ if (message.id) controller.toggle(message.id);
138
+ }
139
+ };
140
+ }
141
+ function createConsoleSelectionAddon(options = {}) {
142
+ let activeData;
143
+ const controller = options.controller ?? createConsoleSelectionController(() => activeData);
144
+ const withDecoration = options.decoration !== false;
145
+ const withMessageAction = options.messageAction !== false;
146
+ return {
147
+ id: CONSOLE_SELECTION_ADDON_ID,
148
+ controller,
149
+ activate(host) {
150
+ activeData = host.services.get(consoleCore.consoleServices.data);
151
+ host.services.provide(consoleSelectionService, controller);
152
+ let decorationRegistration;
153
+ let actionRegistration;
154
+ const registerContributions = () => {
155
+ decorationRegistration?.dispose();
156
+ actionRegistration?.dispose();
157
+ if (withDecoration) {
158
+ decorationRegistration = host.extensions.register(
159
+ consoleCore.consoleExtensionPoints.messageDecoration,
160
+ createDecoration(controller),
161
+ { id: `${CONSOLE_SELECTION_ADDON_ID}:selected` }
162
+ );
163
+ }
164
+ if (withMessageAction) {
165
+ actionRegistration = host.extensions.register(
166
+ consoleCore.consoleExtensionPoints.messageAction,
167
+ createMessageAction(controller),
168
+ { id: `${CONSOLE_SELECTION_ADDON_ID}:toggle` }
169
+ );
170
+ }
171
+ };
172
+ registerContributions();
173
+ host.scope.defer(controller.subscribe(registerContributions));
174
+ host.scope.defer(() => decorationRegistration?.dispose());
175
+ host.scope.defer(() => actionRegistration?.dispose());
176
+ return () => {
177
+ activeData = void 0;
178
+ };
179
+ }
180
+ };
181
+ }
182
+ exports.CONSOLE_SELECTION_ADDON_ID = CONSOLE_SELECTION_ADDON_ID;
183
+ exports.SelectedMessageIndicator = SelectedMessageIndicator;
184
+ exports.consoleSelectionService = consoleSelectionService;
185
+ exports.createConsoleSelectionAddon = createConsoleSelectionAddon;
186
+ exports.createConsoleSelectionController = createConsoleSelectionController;
@@ -0,0 +1,30 @@
1
+ export { SelectedMessageIndicator } from "./components/SelectedMessageIndicator/SelectedMessageIndicator";
2
+ import { type ConsoleAddon, type ConsoleDataService } from "@moyarich/console-core";
3
+ export declare const CONSOLE_SELECTION_ADDON_ID = "@moyarich/console-addon-selection";
4
+ export interface ConsoleSelectionState {
5
+ readonly selectedIds: readonly string[];
6
+ readonly anchorId?: string;
7
+ }
8
+ export interface ConsoleSelectionController {
9
+ getState(): ConsoleSelectionState;
10
+ subscribe(listener: () => void): () => void;
11
+ isSelected(id: string): boolean;
12
+ select(id: string, additive?: boolean): boolean;
13
+ deselect(id: string): boolean;
14
+ toggle(id: string): boolean;
15
+ clear(): void;
16
+ setSelectedIds(ids: readonly string[]): void;
17
+ selectRange(fromId: string, toId: string): readonly string[];
18
+ getVisibleSelectedIds(): readonly string[];
19
+ }
20
+ export interface ConsoleSelectionAddonOptions {
21
+ controller?: ConsoleSelectionController;
22
+ decoration?: boolean;
23
+ messageAction?: boolean;
24
+ }
25
+ export interface ConsoleSelectionAddon extends ConsoleAddon {
26
+ readonly controller: ConsoleSelectionController;
27
+ }
28
+ export declare const consoleSelectionService: import("@moyarich/console-core").ConsoleServiceToken<ConsoleSelectionController>;
29
+ export declare function createConsoleSelectionController(getData?: () => ConsoleDataService | undefined): ConsoleSelectionController;
30
+ export declare function createConsoleSelectionAddon(options?: ConsoleSelectionAddonOptions): ConsoleSelectionAddon;
package/dist/index.js ADDED
@@ -0,0 +1,186 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { createConsoleServiceToken, consoleServices, consoleExtensionPoints } from "@moyarich/console-core";
3
+ function SelectedMessageIndicator() {
4
+ return /* @__PURE__ */ jsx(
5
+ "span",
6
+ {
7
+ "aria-label": "Selected message",
8
+ title: "Selected",
9
+ style: { fontSize: 12, lineHeight: 1 },
10
+ children: "●"
11
+ }
12
+ );
13
+ }
14
+ const CONSOLE_SELECTION_ADDON_ID = "@moyarich/console-addon-selection";
15
+ const consoleSelectionService = createConsoleServiceToken(
16
+ `${CONSOLE_SELECTION_ADDON_ID}.controller`
17
+ );
18
+ function normalizeId(value) {
19
+ const normalized = value.trim();
20
+ return normalized || void 0;
21
+ }
22
+ function uniqueIds(ids) {
23
+ return Object.freeze(
24
+ Array.from(
25
+ new Set(ids.map((id) => normalizeId(id)).filter(Boolean))
26
+ )
27
+ );
28
+ }
29
+ function snapshotIds(data) {
30
+ const snapshot = data?.getSnapshot();
31
+ if (!snapshot) return [];
32
+ if (snapshot.mode === "console") {
33
+ return snapshot.visible.flatMap(
34
+ (message) => message.id ? [message.id] : []
35
+ );
36
+ }
37
+ return snapshot.visible.flatMap(({ entry }) => entry.id ? [entry.id] : []);
38
+ }
39
+ function createConsoleSelectionController(getData = () => void 0) {
40
+ let state = Object.freeze({
41
+ selectedIds: Object.freeze([])
42
+ });
43
+ const listeners = /* @__PURE__ */ new Set();
44
+ const update = (selectedIds, anchorId) => {
45
+ state = Object.freeze({
46
+ selectedIds: uniqueIds(selectedIds),
47
+ ...anchorId ? { anchorId } : {}
48
+ });
49
+ for (const listener of listeners) listener();
50
+ };
51
+ const controller = {
52
+ getState: () => state,
53
+ subscribe(listener) {
54
+ listeners.add(listener);
55
+ return () => listeners.delete(listener);
56
+ },
57
+ isSelected(id) {
58
+ const normalized = normalizeId(id);
59
+ return Boolean(normalized && state.selectedIds.includes(normalized));
60
+ },
61
+ select(id, additive = false) {
62
+ const normalized = normalizeId(id);
63
+ if (!normalized) return false;
64
+ const next = additive ? uniqueIds([...state.selectedIds, normalized]) : [normalized];
65
+ const changed = next.length !== state.selectedIds.length || next.some((value, index) => value !== state.selectedIds[index]);
66
+ if (changed || state.anchorId !== normalized) {
67
+ update(next, normalized);
68
+ }
69
+ return changed;
70
+ },
71
+ deselect(id) {
72
+ const normalized = normalizeId(id);
73
+ if (!normalized || !state.selectedIds.includes(normalized)) return false;
74
+ const next = state.selectedIds.filter((value) => value !== normalized);
75
+ update(next, state.anchorId === normalized ? void 0 : state.anchorId);
76
+ return true;
77
+ },
78
+ toggle(id) {
79
+ const normalized = normalizeId(id);
80
+ if (!normalized) return false;
81
+ if (controller.isSelected(normalized)) {
82
+ controller.deselect(normalized);
83
+ return false;
84
+ }
85
+ controller.select(normalized, true);
86
+ return true;
87
+ },
88
+ clear() {
89
+ if (state.selectedIds.length === 0 && !state.anchorId) return;
90
+ update([]);
91
+ },
92
+ setSelectedIds(ids) {
93
+ const next = uniqueIds(ids);
94
+ update(next, next.at(-1));
95
+ },
96
+ selectRange(fromId, toId) {
97
+ const ordered = snapshotIds(getData());
98
+ const start = ordered.indexOf(fromId);
99
+ const end = ordered.indexOf(toId);
100
+ if (start < 0 || end < 0) return state.selectedIds;
101
+ const [from, to] = start <= end ? [start, end] : [end, start];
102
+ const range = ordered.slice(from, to + 1);
103
+ update(range, toId);
104
+ return range;
105
+ },
106
+ getVisibleSelectedIds() {
107
+ const visible = new Set(snapshotIds(getData()));
108
+ return Object.freeze(state.selectedIds.filter((id) => visible.has(id)));
109
+ }
110
+ };
111
+ return controller;
112
+ }
113
+ function createDecoration(controller) {
114
+ return {
115
+ id: `${CONSOLE_SELECTION_ADDON_ID}:selected`,
116
+ placement: "gutter",
117
+ match({ message }) {
118
+ return Boolean(message.id && controller.isSelected(message.id));
119
+ },
120
+ render({ message }) {
121
+ if (!message.id || !controller.isSelected(message.id)) return void 0;
122
+ return /* @__PURE__ */ jsx(SelectedMessageIndicator, {});
123
+ }
124
+ };
125
+ }
126
+ function createMessageAction(controller) {
127
+ return {
128
+ id: `${CONSOLE_SELECTION_ADDON_ID}:toggle`,
129
+ label: "Toggle selection",
130
+ ariaLabel: "Toggle message selection",
131
+ visible({ message }) {
132
+ return Boolean(message.id);
133
+ },
134
+ onSelect({ message }) {
135
+ if (message.id) controller.toggle(message.id);
136
+ }
137
+ };
138
+ }
139
+ function createConsoleSelectionAddon(options = {}) {
140
+ let activeData;
141
+ const controller = options.controller ?? createConsoleSelectionController(() => activeData);
142
+ const withDecoration = options.decoration !== false;
143
+ const withMessageAction = options.messageAction !== false;
144
+ return {
145
+ id: CONSOLE_SELECTION_ADDON_ID,
146
+ controller,
147
+ activate(host) {
148
+ activeData = host.services.get(consoleServices.data);
149
+ host.services.provide(consoleSelectionService, controller);
150
+ let decorationRegistration;
151
+ let actionRegistration;
152
+ const registerContributions = () => {
153
+ decorationRegistration?.dispose();
154
+ actionRegistration?.dispose();
155
+ if (withDecoration) {
156
+ decorationRegistration = host.extensions.register(
157
+ consoleExtensionPoints.messageDecoration,
158
+ createDecoration(controller),
159
+ { id: `${CONSOLE_SELECTION_ADDON_ID}:selected` }
160
+ );
161
+ }
162
+ if (withMessageAction) {
163
+ actionRegistration = host.extensions.register(
164
+ consoleExtensionPoints.messageAction,
165
+ createMessageAction(controller),
166
+ { id: `${CONSOLE_SELECTION_ADDON_ID}:toggle` }
167
+ );
168
+ }
169
+ };
170
+ registerContributions();
171
+ host.scope.defer(controller.subscribe(registerContributions));
172
+ host.scope.defer(() => decorationRegistration?.dispose());
173
+ host.scope.defer(() => actionRegistration?.dispose());
174
+ return () => {
175
+ activeData = void 0;
176
+ };
177
+ }
178
+ };
179
+ }
180
+ export {
181
+ CONSOLE_SELECTION_ADDON_ID,
182
+ SelectedMessageIndicator,
183
+ consoleSelectionService,
184
+ createConsoleSelectionAddon,
185
+ createConsoleSelectionController
186
+ };
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "@moyarich/console-addon-selection",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "First-party semantic output selection for @moyarich/console hosts.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "development": "./src/index.tsx",
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "require": "./dist/index.cjs"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "publishConfig": {
22
+ "registry": "https://npm.pkg.github.com"
23
+ },
24
+ "scripts": {
25
+ "build": "vite build && tsc -p tsconfig.build.json",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "peerDependencies": {
29
+ "@moyarich/console-core": "^0.1.0",
30
+ "react": ">=18"
31
+ },
32
+ "devDependencies": {
33
+ "@moyarich/console-core": "0.1.0",
34
+ "@types/react": "^19.1.12",
35
+ "react": "^19.2.7",
36
+ "typescript": "^5.9.2",
37
+ "vite": "^7.1.5"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/moyarich/console.git",
42
+ "directory": "packages/addons/selection"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/moyarich/console/issues"
46
+ },
47
+ "homepage": "https://github.com/moyarich/console#readme",
48
+ "keywords": [
49
+ "console",
50
+ "addon",
51
+ "selection",
52
+ "developer-tools"
53
+ ],
54
+ "publisher": "moyarich",
55
+ "author": "Moya Richards",
56
+ "private": false
57
+ }