command-panel 1.0.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 ADDED
@@ -0,0 +1,198 @@
1
+ # Pochade-JS Project
2
+
3
+ A vanilla JS, CSS and HTML project with Web Workers and Custom HTML Elements as first class citizens.
4
+
5
+ ## Getting Started
6
+
7
+ Install dependencies:
8
+
9
+ ```bash
10
+ npm install
11
+ ```
12
+
13
+ ## Running the Project
14
+
15
+ To run the project in development mode:
16
+
17
+ ```bash
18
+ npm start
19
+ ```
20
+
21
+ This will start a development server. By default, it runs on port 3000. You can view the project in your browser.
22
+
23
+ ## Building the Project
24
+
25
+ To build the project for production:
26
+
27
+ ```bash
28
+ npm build
29
+ ```
30
+
31
+ This will create a `dist` folder with the bundled and optimized files.
32
+
33
+ ## Customizing the Build
34
+
35
+ You can customize the build output by creating a `.env` file in the root of the project.
36
+
37
+ ### Output Filename
38
+
39
+ To change the name of the output file, set the `OUTPUT_FILE_NAME` variable in your `.env` file.
40
+
41
+ **.env**
42
+ ```
43
+ OUTPUT_FILE_NAME=my-custom-filename.js
44
+ ```
45
+
46
+ If this variable is not set, the output file will default to `dist/main.min.js`.
47
+
48
+ ### Development Server Port
49
+
50
+ You can also change the development server port by setting the `PORT` variable in your `.env` file.
51
+
52
+ **.env**
53
+ ```
54
+ PORT=8080
55
+ ```
56
+
57
+ If this variable is not set, the port will default to `3000`.
58
+
59
+ ## Project Structure
60
+
61
+ - `src/` - Your JavaScript source files
62
+ - `styles/` - CSS files
63
+ - `scripts/` - Build scripts (including Web Worker transformation)
64
+ - `index.html` - Main HTML file
65
+ - `index.js` - Main JavaScript entry point
66
+ - `index.css` - Main CSS file
67
+ - `rspack.config.js` - Rspack configuration
68
+
69
+ ## Rspack Build Configuration
70
+
71
+ ### Features
72
+
73
+ The project uses Rspack with the following features configured:
74
+
75
+ #### Module Processing
76
+
77
+ - **CSS Processing Pipeline**
78
+ - `style-loader` - Injects CSS into the DOM
79
+ - `css-loader` - Resolves CSS imports and URLs
80
+ - `postcss-loader` with cssnano - Minifies and optimizes CSS
81
+ - Source maps enabled in development mode
82
+ - Automatic comment removal in production builds
83
+
84
+ - **JavaScript Processing**
85
+ - `builtin:swc-loader` - Fast JavaScript transpilation
86
+ - Custom `transform-workers.js` loader - Transforms web worker imports
87
+ - Dynamic imports forced to eager mode for web worker compatibility
88
+ - Source maps enabled in development mode
89
+
90
+ #### Web Workers
91
+
92
+ The build system includes special handling for web workers:
93
+
94
+ - Custom loader (`scripts/transform-workers.js`) transforms worker imports
95
+ - Dynamic imports are eagerly evaluated for worker compatibility
96
+ - Workers are properly bundled and can be imported in your code
97
+
98
+ #### Assets Directory
99
+
100
+ The `assets/` folder receives special treatment:
101
+
102
+ - **Development Server**: Assets are served from the root path (`/`) if the directory exists and contains files
103
+ - **Production Build**: Assets are copied to the dist root (not in a subdirectory) via `CopyRspackPlugin`
104
+ - **Conditional Loading**: Assets are only processed if the directory exists and has files
105
+
106
+ Place any static files (images, fonts, etc.) in the `assets/` directory and they will be accessible from the root path in both dev and production.
107
+
108
+ #### Optimization
109
+
110
+ - `splitChunks: false` - Bundles everything into a single file
111
+ - `runtimeChunk: false` - No separate runtime chunk
112
+ - `clean: true` - Automatically cleans the dist directory before each build
113
+
114
+ #### Development Server
115
+
116
+ - Serves static files from project root
117
+ - Conditionally serves assets directory
118
+ - Gzip compression enabled
119
+ - Cache-Control headers set to `no-store` for development
120
+ - Configurable port via environment variable
121
+
122
+ #### Environment Configuration
123
+
124
+ - `.env` file support via dotenv
125
+ - `OUTPUT_FILE_NAME` - Customize output filename (default: `main.min.js`)
126
+ - `PORT` - Configure dev server port (default: `3000`)
127
+ - `NODE_ENV` - Set to `production` for production builds
128
+
129
+ ## Technologies
130
+
131
+ - **Rspack** - Fast bundler for development and production
132
+ - **dataroom-js** - Custom HTML elements framework
133
+ - **Web Workers** - For parallel processing
134
+ - **PostCSS** - CSS processing with cssnano optimization
135
+ - **SWC** - Fast JavaScript/TypeScript compiler
136
+
137
+ ## Publishing to npm
138
+
139
+ This project is configured for publishing to npm. Follow these steps to publish:
140
+
141
+ ### Before First Publish
142
+
143
+ 1. **Update package metadata** in `package.json`:
144
+ - Set the package `name` (must be unique on npm)
145
+ - Update `author` with your name and email
146
+ - Update `repository`, `bugs`, and `homepage` URLs with your actual repository
147
+ - Set the initial `version` (recommend starting with `0.1.0`)
148
+
149
+ 2. **Verify the package contents**:
150
+ ```bash
151
+ npm pack --dry-run
152
+ ```
153
+ This shows what files will be included in the package.
154
+
155
+ 3. **Test the build**:
156
+ ```bash
157
+ npm run build
158
+ ```
159
+ Ensure the `dist/` directory is created successfully.
160
+
161
+ ### Publishing
162
+
163
+ 1. **Login to npm** (first time only):
164
+ ```bash
165
+ npm login
166
+ ```
167
+
168
+ 2. **Publish the package**:
169
+ ```bash
170
+ npm publish
171
+ ```
172
+
173
+ The `prepublishOnly` script will automatically run the build before publishing.
174
+
175
+ ### Updating the Package
176
+
177
+ 1. **Update the version** using npm's version command:
178
+ ```bash
179
+ npm version patch # For bug fixes (1.0.0 -> 1.0.1)
180
+ npm version minor # For new features (1.0.0 -> 1.1.0)
181
+ npm version major # For breaking changes (1.0.0 -> 2.0.0)
182
+ ```
183
+
184
+ 2. **Publish the update**:
185
+ ```bash
186
+ npm publish
187
+ ```
188
+
189
+ ### What Gets Published
190
+
191
+ The package includes:
192
+ - `dist/` - Built production files
193
+ - `src/` - Source JavaScript files
194
+ - `styles/` - CSS files
195
+ - `index.js`, `index.css`, `index.html` - Entry files
196
+ - `package.json` and related metadata
197
+
198
+ Development files (rspack config, build scripts, tests, etc.) are excluded via `.npmignore`.
@@ -0,0 +1,29 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta property="og:title" content="command-panel"><meta property="og:type" content="article"><meta property="og:image" content=""><meta property="og:url" content=""><meta name="twitter:card" content="summary_large_image"><meta property="og:description" content="Command Panel Component"><meta property="og:site_name" content="command-panel"><meta name="twitter:image:alt" content=""><title>command-panel</title><script src="/main.min.js" type="module"></script><style>
2
+ :root {
3
+ --background-color: #000;
4
+ --foreground-color: #fff;
5
+ --highlight-color: #ff00ff;
6
+ --secondary-color: #ede9d7;
7
+ --trinary-color: #8aa38a;
8
+ --quaternary-color: #d4cfbd;
9
+ --confirmation-color: #0a5c0a;
10
+ --notification-color: #377c43;
11
+ --inactive-color: #cccccc;
12
+ --warning-color: #639e71;
13
+ --error-color: #ff6b6b;
14
+ --neutral-color: #1c3320;
15
+ --h1-font-size: 200%;
16
+ --h2-font-size: 180%;
17
+ --h3-font-size: 150%;
18
+
19
+ }
20
+ </style><script defer src="main.min.js"></script><link href="main.css" rel="stylesheet"></head><body><h1>Command Panel Demo</h1>
21
+ <p>Press <kbd>Ctrl+Shift+P</kbd> to open the command panel, or <kbd>Alt+K</kbd> for the custom shortcut example.</p>
22
+
23
+
24
+ <command-panel id="command_panel"></command-panel>
25
+
26
+
27
+ <command-panel id="custom_command_panel" open-keys="alt+k"></command-panel>
28
+
29
+ <script>setTimeout(()=>{let e=document.getElementById("command_panel");e.addCommand("Create New File","\uD83D\uDCC4",()=>{console.log("Creating new file..."),alert("New file created!")}),e.addCommand("Open Settings","⚙️",()=>{console.log("Opening settings..."),alert("Settings opened!")}),e.addCommand("Search Project","\uD83D\uDD0D",()=>{console.log("Searching project..."),alert("Search initiated!")}),e.addCommand("Run Build","\uD83D\uDD28",()=>{console.log("Running build..."),alert("Build started!")}),e.addCommand("Deploy Application","\uD83D\uDE80",()=>{console.log("Deploying application..."),alert("Deployment started!")}),e.addCommand("View Documentation","\uD83D\uDCDA",()=>{console.log("Opening documentation..."),alert("Documentation opened!")}),e.addCommand("Toggle Dark Mode",null,()=>{console.log("Toggling dark mode..."),alert("Dark mode toggled!")}),e.on("COMMAND-EXECUTED",e=>{console.log("Command executed:",e)});let o=document.getElementById("custom_command_panel");o.addCommand("Quick Action 1","⚡",()=>{console.log("Quick action 1 executed"),alert("Quick Action 1!")}),o.addCommand("Quick Action 2","✨",()=>{console.log("Quick action 2 executed"),alert("Quick Action 2!")}),o.addCommand("Quick Action 3","\uD83C\uDFAF",()=>{console.log("Quick action 3 executed"),alert("Quick Action 3!")}),o.on("COMMAND-EXECUTED",e=>{console.log("Custom command executed:",e)})},1e3)</script></body></html>
package/dist/main.css ADDED
@@ -0,0 +1,2 @@
1
+ command-panel dialog{z-index:100;background-color:var(--background-color);border:none;border-radius:8px;width:600px;max-width:90vw;margin:0;padding:0;position:fixed;top:20%;left:50%;transform:translate(-50%)}command-panel dialog::-ms-backdrop{background-color:rgba(0,0,0,.5)}command-panel dialog::backdrop{background-color:rgba(0,0,0,.5)}.command-panel-container{flex-direction:column;max-height:400px;display:flex;overflow:hidden}.command-search{border:none;border-bottom:2px solid var(--trinary-color);background-color:var(--background-color);color:var(--foreground-color);outline:none;padding:12px 16px;font-size:16px}.command-search:focus{border-bottom-color:var(--foreground-color);background-color:var(--secondary-color)}.command-search::-webkit-input-placeholder{color:var(--trinary-color)}.command-search::-ms-input-placeholder{color:var(--trinary-color)}.command-search::placeholder{color:var(--trinary-color)}.command-list{max-height:340px;margin:0;padding:0;list-style:none;overflow-y:auto}.command-item{cursor:pointer;color:var(--foreground-color);align-items:center;gap:12px;padding:10px 16px;transition:background-color .15s;display:flex}.command-item:hover{background-color:var(--secondary-color)}.command-item.selected{background-color:var(--highlight-color)}.command-icon{text-align:center;flex-shrink:0;width:24px;font-size:1.2em}.command-name{flex:1;font-size:14px}.no-results{text-align:center;color:var(--trinary-color);padding:20px 16px;font-style:italic;list-style:none}
2
+ /*# sourceMappingURL=main.css.map*/
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.css","sources":["webpack://command-panel/./src/command-panel.css"],"sourcesContent":["\n/* Dialog element styling */\ncommand-panel dialog {\n position: fixed;\n top: 20%;\n left: 50%;\n transform: translateX(-50%);\n margin: 0;\n z-index: 100;\n width: 600px;\n max-width: 90vw;\n border: none;\n border-radius: 8px;\n padding: 0;\n background-color: var(--background-color);\n}\n\ncommand-panel dialog::backdrop {\n background-color: rgba(0, 0, 0, 0.5);\n}\n\n/* Container styling */\n.command-panel-container {\n display: flex;\n flex-direction: column;\n max-height: 400px;\n overflow: hidden;\n}\n\n/* Search input styling */\n.command-search {\n padding: 12px 16px;\n border: none;\n border-bottom: 2px solid var(--trinary-color);\n background-color: var(--background-color);\n color: var(--foreground-color);\n font-size: 16px;\n outline: none;\n}\n\n.command-search:focus {\n border-bottom-color: var(--foreground-color);\n background-color: var(--secondary-color);\n}\n\n.command-search::placeholder {\n color: var(--trinary-color);\n}\n\n/* Command list styling */\n.command-list {\n overflow-y: auto;\n list-style: none;\n margin: 0;\n padding: 0;\n max-height: 340px;\n}\n\n/* Command item styling */\n.command-item {\n display: flex;\n align-items: center;\n padding: 10px 16px;\n cursor: pointer;\n gap: 12px;\n color: var(--foreground-color);\n transition: background-color 0.15s ease;\n}\n\n.command-item:hover {\n background-color: var(--secondary-color);\n}\n\n.command-item.selected {\n background-color: var(--highlight-color);\n}\n\n/* Command icon styling */\n.command-icon {\n font-size: 1.2em;\n width: 24px;\n text-align: center;\n flex-shrink: 0;\n}\n\n/* Command name styling */\n.command-name {\n flex: 1;\n font-size: 14px;\n}\n\n/* No results message styling */\n.no-results {\n padding: 20px 16px;\n text-align: center;\n color: var(--trinary-color);\n font-style: italic;\n list-style: none;\n}\n"],"names":[],"mappings":"AAEA,gNAeA,kIAKA,6FAQA,8LAUA,0GAKA,gMAKA,kFASA,uJAUA,4DAIA,+DAKA,yEAQA,oCAMA"}
@@ -0,0 +1,21 @@
1
+ (()=>{"use strict";var e={},t={};function s(n){var a=t[n];if(void 0!==a)return a.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,s),i.exports}s.rv=()=>"1.6.0",s.ruid="bundler=rspack@1.6.0";class n extends HTMLElement{create(e,t={},s=null){this.log(`Creating a new Element of ${e}`);let n=document.createElement(e);return Object.keys(t).forEach(e=>{"content"===e?n.innerHTML=t[e]:n.setAttribute(e,t[e])}),null===s?this.appendChild(n):s.appendChild(n),n}event(e,t={}){let s=new CustomEvent(e,{detail:t});this.dispatchEvent(s)}on(e,t){return console.log("creating event listener...",e),this.addEventListener(e,e=>{t(e.detail)})}async call(e,t={}){let s={"Content-Type":"application/json"};if("localstorage"===this.getAttribute("security-scheme")){let e=localStorage.getItem("bearer-token");s.Authorization=`Bearer ${e}`}let n=new AbortController,a=n.signal,i=this.getAttribute("call-timeout");i&&setTimeout(()=>n.abort(),i);try{let n=await fetch(e,{method:"post",headers:s,body:JSON.stringify(t),signal:a});if(n.ok)return await n.json();throw Error(`HTTP error! status: ${n.status}`)}catch(e){if("AbortError"===e.name)throw Error("Request timed out");throw e}}log(e){this.verbose&&console.log(this.id,"says:",e),this.event("status-update",e)}connectedCallback(){"loading"!==document.readyState?this.preInit():document.addEventListener("DOMContentLoaded",()=>this.preInit())}async preInit(){this.content=this.innerText,this.attrs=this.getAttributeNames().reduce((e,t)=>({...e,[t]:this.getAttribute(t)}),{}),this.classList.add("dataroom-element"),this.observeAttributeChanges(),this.initialize()}async setAttrs(e){for(let[t,s]of(this.log("setting attrs:",e),Object.entries(e)))await this.setAttribute(t,s);this.render()}observeAttributeChanges(){this.log("observing attribute changes"),this.attributeObserver=new MutationObserver(e=>{e.forEach(e=>{"attributes"===e.type&&(this.attrs[e.attributeName]=this.getAttribute(e.attributeName),this.event("NODE-CHANGED",{attribute:e.attributeName,oldValue:e.oldValue,newValue:this.getAttribute(e.attributeName)}))})}),this.attributeObserver.observe(this,{attributes:!0,attributeOldValue:!0})}async initialize(){}disconnectedCallback(){this.log("disconnecting..."),this.disconnect()}async disconnect(){}}function a(e,t,s,n,a,i,r){try{var o=e[i](r),l=o.value}catch(e){s(e);return}o.done?t(l):Promise.resolve(l).then(n,a)}function i(e,t,s,n,a,i,r){try{var o=e[i](r),l=o.value}catch(e){s(e);return}o.done?t(l):Promise.resolve(l).then(n,a)}customElements.get("example-component")||customElements.define("example-component",class extends n{initialize(){var e;return(e=function*(){let e,t,s;this.create("h1",{content:"Example Code"}),this.create("p",{content:"This element uses the dataroom.js. It provides a few features that make using custom HTML Elements easier!"}),this.create("a",{content:"Check it out here!",href:"https://dataroom-network.github.io/dataroom.js/"});let n=(e=new Blob([`/**
2
+ * Example Web Worker
3
+ *
4
+ * Simple web worker that receives messages from the main thread and responds.
5
+ * Demonstrates basic worker communication pattern.
6
+ */
7
+
8
+ /**
9
+ * Message handler for incoming messages from main thread
10
+ *
11
+ * Logs the received message and sends a response back to the main thread.
12
+ *
13
+ * @param {MessageEvent} event - The message event from the main thread
14
+ * @param {*} event.data - Data sent from the main thread
15
+ */
16
+ self.onmessage = (event) => {
17
+ console.log("Message received in worker:", event.data);
18
+ self.postMessage({ message: "Hello from Web Worker!" });
19
+ };
20
+ `],{type:"application/javascript"}),s=new Worker(t=URL.createObjectURL(e)),URL.revokeObjectURL(t),s);n.onmessage=e=>{console.log("Message received from worker:",e.data),this.create("p",{content:JSON.stringify(e.data)}),this.event("WEB-WORKER-RESPONSE",e.data)},n.postMessage({message:"Hello from the main thread!"})},function(){var t=this,s=arguments;return new Promise(function(n,i){var r=e.apply(t,s);function o(e){a(r,n,i,o,l,"next",e)}function l(e){a(r,n,i,o,l,"throw",e)}o(void 0)})}).call(this)}}),customElements.define("command-panel",class extends n{static get observedAttributes(){return["open-keys"]}attributeChangedCallback(e,t,s){"open-keys"===e&&t!==s&&this.keyboardListener&&this.setupKeyboardShortcut()}initialize(){var e;return(e=function*(){this.commands=[],this.filteredCommands=[],this.selectedIndex=0,this.previouslyFocusedElement=null,this.dialog=this.create("dialog",{});let e=this.create("div",{class:"command-panel-container"});this.searchInput=this.create("input",{type:"text",class:"command-search",placeholder:"Search commands..."}),this.commandList=this.create("ul",{class:"command-list"}),e.appendChild(this.searchInput),e.appendChild(this.commandList),this.dialog.appendChild(e),this.appendChild(this.dialog),this.setupKeyboardShortcut(),this.setupDialogEventListeners()},function(){var t=this,s=arguments;return new Promise(function(n,a){var r=e.apply(t,s);function o(e){i(r,n,a,o,l,"next",e)}function l(e){i(r,n,a,o,l,"throw",e)}o(void 0)})}).call(this)}setupDialogEventListeners(){this.dialog.addEventListener("keydown",e=>{if("Escape"===e.key)this.closePanel();else if("ArrowDown"===e.key)e.preventDefault(),this.handleArrowNavigation("down");else if("ArrowUp"===e.key)e.preventDefault(),this.handleArrowNavigation("up");else if("Enter"===e.key){e.preventDefault();let t=this.filteredCommands[this.selectedIndex];t&&this.executeCommand(t)}}),this.dialog.addEventListener("click",e=>{e.target===this.dialog&&this.closePanel()}),this.searchInput.addEventListener("input",e=>{this.handleSearch(e)})}fuzzyMatch(e,t){e=e.toLowerCase(),t=t.toLowerCase();let s=0,n=0;for(;s<e.length&&n<t.length;)e[s]===t[n]&&s++,n++;return s===e.length}handleSearch(e){let t=e.target.value;this.filteredCommands=this.commands.filter(e=>this.fuzzyMatch(t,e.name)),this.selectedIndex=0,this.renderCommands()}handleArrowNavigation(e){0!==this.filteredCommands.length&&("down"===e?(this.selectedIndex++,this.selectedIndex>=this.filteredCommands.length&&(this.selectedIndex=0)):"up"===e&&(this.selectedIndex--,this.selectedIndex<0&&(this.selectedIndex=this.filteredCommands.length-1)),this.renderCommands())}addCommand(e,t,s){e&&"string"==typeof e?s&&"function"==typeof s?this.commands.push({name:e,icon:t,callback:s}):console.error("Command callback must be a function"):console.error("Command name must be a non-empty string")}parseKeyboardShortcut(e){let t=e.toLowerCase().split("+"),s={ctrl:!1,shift:!1,alt:!1,meta:!1,key:""};return t.forEach(e=>{let t=e.trim();"ctrl"===t?s.ctrl=!0:"shift"===t?s.shift=!0:"alt"===t?s.alt=!0:"cmd"===t||"meta"===t?s.meta=!0:s.key=t}),s}setupKeyboardShortcut(){this.keyboardListener&&document.removeEventListener("keydown",this.keyboardListener);let e=this.getAttribute("open-keys")||"ctrl+shift+p",t=this.parseKeyboardShortcut(e);this.keyboardListener=e=>{let s=t.ctrl===(e.ctrlKey||e.metaKey),n=t.shift===e.shiftKey,a=t.alt===e.altKey,i=t.meta===e.metaKey,r=e.key.toLowerCase()===t.key;s&&n&&a&&i&&r&&(e.preventDefault(),this.openPanel())},document.addEventListener("keydown",this.keyboardListener)}openPanel(){this.previouslyFocusedElement=document.activeElement,this.dialog.showModal(),this.searchInput.focus(),this.filteredCommands=[...this.commands],this.selectedIndex=0,this.renderCommands()}closePanel(){this.dialog.close(),this.searchInput.value="",this.filteredCommands=[],this.previouslyFocusedElement&&this.previouslyFocusedElement.focus()}renderCommands(){if(this.commandList.innerHTML="",console.log("Rendering commands:",this.filteredCommands.length),0===this.filteredCommands.length){let e=this.create("li",{class:"no-results",content:"No commands found"});this.commandList.appendChild(e);return}this.filteredCommands.forEach((e,t)=>{let s=this.create("li",{class:"command-item"});if(t===this.selectedIndex&&s.classList.add("selected"),e.icon){let t=this.create("span",{class:"command-icon",content:e.icon});s.appendChild(t)}let n=this.create("span",{class:"command-name",content:e.name});s.appendChild(n),s.addEventListener("click",()=>{this.executeCommand(e)}),this.commandList.appendChild(s)})}executeCommand(e){this.event("COMMAND-EXECUTED",{name:e.name,icon:e.icon}),e.callback(),this.closePanel()}})})();
21
+ //# sourceMappingURL=main.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.min.js","sources":["webpack://command-panel/webpack/runtime/rspack_version","webpack://command-panel/webpack/runtime/rspack_unique_id","webpack://command-panel/./node_modules/dataroom-js/src/index.js","webpack://command-panel/./src/example-component.js","webpack://command-panel/./src/command-panel.js"],"sourcesContent":["__webpack_require__.rv = () => (\"1.6.0\")","__webpack_require__.ruid = \"bundler=rspack@1.6.0\";\n","/**\n * DataRoom Custom Element\n *\n * The main component that serves as the container for all data visualization\n * and interaction elements in the DataRoom application.\n *\n * @class DataRoom\n * @extends HTMLElement\n * \n * @example\n * // Use this function by importing it:\n * import Dataroom from '/dataroom.js'\n * \n * class ElementName extends Dataroom {\n * async init(){\n * // override the init() function\n * }\n * }\n */\n\nexport default class DataroomElement extends HTMLElement {\n /**\n * Creates a new HTML element of the specified type and appends it to the current element or a specified target element.\n * @param {string} type - The type of element to create.\n * @param {Object} attributes - An object of key-value pairs representing attribute names and values.\n * @param {HTMLElement|null} [target_el=null] - The target element to append the new element to. Defaults to the current element.\n * @returns {HTMLElement} - The newly created element.\n */\n create(type, attributes = {}, target_el = null) {\n this.log(`Creating a new Element of ${type}`);\n const el = document.createElement(type);\n Object.keys(attributes).forEach((attribute) => {\n if (attribute === \"content\") {\n el.innerHTML = attributes[attribute];\n } else {\n el.setAttribute(attribute, attributes[attribute]);\n }\n });\n if (target_el === null) {\n this.appendChild(el);\n } else {\n target_el.appendChild(el);\n }\n return el;\n }\n\n /**\n * Emits a custom event from the element.\n * @param {string} name - The name of the event to emit.\n * @param {Object} [detail={}] - Additional data to include with the event.\n * @returns {void}\n */\n event(name, detail = {}) {\n const dtrmEvent = new CustomEvent(name, {\n detail,\n });\n this.dispatchEvent(dtrmEvent);\n }\n\n /**\n * Attaches an event listener to the element.\n * @param {string} name - The name of the event to listen for.\n * @param {Function} cb - The callback function to execute when the event is fired.\n * @returns {void}\n */\n on(name, cb){\n console.log('creating event listener...', name);\n return this.addEventListener(name, (e)=>{\n cb(e.detail)\n });\n }\n\n /**\n * A fetch helper that handles most of the complexity of \n * talking to the server. \n * @param {string} endpoint the endpoint we want to talk to\n * @param {object} body the content of the server call\n * @returns {object} the response from the server as an object\n */\n async call(endpoint, body = {}){\n const headers = {\n 'Content-Type': 'application/json',\n }\n\n const securityScheme = this.getAttribute('security-scheme');\n\n if (securityScheme === 'localstorage') {\n const bearer_token = localStorage.getItem('bearer-token');\n headers['Authorization'] = `Bearer ${bearer_token}`;\n } else if (securityScheme === 'cookie') {\n // Cookies are sent automatically by the browser, so no special handling is needed here.\n }\n\n const controller = new AbortController();\n const signal = controller.signal;\n const timeout = this.getAttribute('call-timeout');\n\n if (timeout) {\n setTimeout(() => controller.abort(), timeout);\n }\n\n try {\n const response = await fetch(endpoint, {\n method: 'post',\n headers,\n body: JSON.stringify(body),\n signal\n });\n \n if(response.ok){\n const response_value = await response.json();\n return response_value;\n } else {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n } catch (error) {\n if (error.name === 'AbortError') {\n throw new Error('Request timed out');\n } else {\n throw error;\n }\n }\n }\n\n /**\n * Logs a message if the verbose flag is set.\n * @param {string} message - The message to log.\n * @returns {void}\n */\n log(message) {\n if (this.verbose) {\n console.log(this.id, \"says:\", message);\n this.event(\"status-update\", message);\n\n } else {\n this.event(\"status-update\", message);\n return;\n }\n }\n\n /**\n * Called when the element is added to the DOM.\n * Sets the element's ID and attributes, and initializes the element.\n * @private\n * @returns {void}\n */\n connectedCallback() {\n if (document.readyState !== 'loading') {\n this.preInit();\n return;\n }\n document.addEventListener('DOMContentLoaded', () => this.preInit());\n }\n\n /**\n * Runs before the Initializeation \n * @returns {void}\n */\n async preInit(){\n\n this.content = this.innerText; \n this.attrs = this.getAttributeNames().reduce((acc, name) => {\n return { ...acc, [name]: this.getAttribute(name) };\n }, {});\n this.classList.add('dataroom-element');\n this.observeAttributeChanges();\n\n this.initialize();\n }\n\n\n /**\n * Sets multiple attributes on the element.\n * @param {Object} data - An object of key-value pairs representing attribute names and values.\n * @returns {Promise<void>}\n */\n async setAttrs(data) {\n this.log(\"setting attrs:\", data);\n for (const [key, value] of Object.entries(data)) {\n await this.setAttribute(key, value);\n }\n this.render();\n }\n\n\n /**\n * Observes changes to element attributes and emits events when they change.\n * @private\n * @returns {void}\n */\n observeAttributeChanges() {\n this.log(\"observing attribute changes\");\n this.attributeObserver = new MutationObserver((mutations) => {\n mutations.forEach((mutation) => {\n if (mutation.type === \"attributes\") {\n this.attrs[mutation.attributeName] = this.getAttribute(\n mutation.attributeName,\n );\n this.event(\"NODE-CHANGED\", {\n attribute: mutation.attributeName,\n oldValue: mutation.oldValue,\n newValue: this.getAttribute(mutation.attributeName),\n });\n }\n });\n });\n const config = { attributes: true, attributeOldValue: true };\n this.attributeObserver.observe(this, config);\n }\n\n /**\n * Initializes the element. This function should be overridden in the child class.\n * @returns {void}\n */\n async initialize() {\n // override this class to run initialization code here\n }\n\n /**\n * Called when the element is removed from the DOM.\n * Disconnects the element.\n * @private\n * @returns {void}\n */\n disconnectedCallback() {\n this.log(\"disconnecting...\");\n this.disconnect();\n }\n\n /**\n * Handles disconnection logic. This function should be overridden in the child class.\n * @returns {void}\n */\n async disconnect() {\n // override this function to run disconnect code\n }\n\n\n\n}","/**\n * Example Component\n * \n * A custom HTML element demonstrating dataroom-js features and web worker integration.\n * Creates a simple UI with heading, description, link, and displays web worker responses.\n */ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {\n try {\n var info = gen[key](arg);\n var value = info.value;\n } catch (error) {\n reject(error);\n return;\n }\n if (info.done) {\n resolve(value);\n } else {\n Promise.resolve(value).then(_next, _throw);\n }\n}\nfunction _async_to_generator(fn) {\n return function() {\n var self = this, args = arguments;\n return new Promise(function(resolve, reject) {\n var gen = fn.apply(self, args);\n function _next(value) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value);\n }\n function _throw(err) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err);\n }\n _next(undefined);\n });\n };\n}\nimport DataroomElement from 'dataroom-js';\n/**\n * ExampleComponent class\n * \n * Custom element that extends DataroomElement to demonstrate:\n * - Creating HTML elements with the create() method\n * - Web worker instantiation and communication\n * - Dynamic content rendering from worker responses\n * \n * @extends DataroomElement\n */ class ExampleComponent extends DataroomElement {\n /**\n * Initialize the component\n * \n * Creates the component's UI elements and sets up web worker communication.\n * Renders a heading, description paragraph, link to dataroom.js documentation,\n * and initiates communication with a web worker.\n * \n * @async\n * @returns {Promise<void>}\n */ initialize() {\n return _async_to_generator(function*() {\n this.create(\"h1\", {\n content: \"Example Code\"\n });\n const p = this.create(\"p\", {\n content: \"This element uses the dataroom.js. It provides a few features that make using custom HTML Elements easier!\"\n });\n this.create(\"a\", {\n content: \"Check it out here!\",\n href: \"https://dataroom-network.github.io/dataroom.js/\"\n });\n // Initialize web worker\n const worker = (function() {\n const __workerCode = `/**\n * Example Web Worker\n * \n * Simple web worker that receives messages from the main thread and responds.\n * Demonstrates basic worker communication pattern.\n */\n\n/**\n * Message handler for incoming messages from main thread\n * \n * Logs the received message and sends a response back to the main thread.\n * \n * @param {MessageEvent} event - The message event from the main thread\n * @param {*} event.data - Data sent from the main thread\n */\nself.onmessage = (event) => {\n console.log(\"Message received in worker:\", event.data);\n self.postMessage({ message: \"Hello from Web Worker!\" });\n};\n`;\n const blob = new Blob([__workerCode], { type: 'application/javascript' });\n const url = URL.createObjectURL(blob);\n const worker = new Worker(url);\n URL.revokeObjectURL(url);\n return worker;\n})();\n /**\n * Handle messages from the web worker\n * \n * @param {MessageEvent} event - Message event from worker\n * @param {Object} event.data - Data received from worker\n */ worker.onmessage = (event)=>{\n console.log(\"Message received from worker:\", event.data);\n this.create(\"p\", {\n content: JSON.stringify(event.data)\n });\n // Emits a dataroom event\n this.event(\"WEB-WORKER-RESPONSE\", event.data);\n };\n // Send initial message to worker\n worker.postMessage({\n message: \"Hello from the main thread!\"\n });\n }).call(this);\n }\n}\n// Register the custom element\nif (!customElements.get('example-component')) {\n customElements.define('example-component', ExampleComponent);\n}\n","function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {\n try {\n var info = gen[key](arg);\n var value = info.value;\n } catch (error) {\n reject(error);\n return;\n }\n if (info.done) {\n resolve(value);\n } else {\n Promise.resolve(value).then(_next, _throw);\n }\n}\nfunction _async_to_generator(fn) {\n return function() {\n var self = this, args = arguments;\n return new Promise(function(resolve, reject) {\n var gen = fn.apply(self, args);\n function _next(value) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value);\n }\n function _throw(err) {\n asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err);\n }\n _next(undefined);\n });\n };\n}\nimport DataroomElement from 'dataroom-js';\nimport './command-panel.css';\nclass CommandPanel extends DataroomElement {\n // Define static observedAttributes getter returning ['open-keys']\n static get observedAttributes() {\n return [\n 'open-keys'\n ];\n }\n // Implement attributeChangedCallback method\n attributeChangedCallback(name, oldValue, newValue) {\n // Re-setup keyboard shortcut when open-keys attribute changes\n if (name === 'open-keys' && oldValue !== newValue && this.keyboardListener) {\n this.setupKeyboardShortcut();\n }\n }\n initialize() {\n return _async_to_generator(function*() {\n // Initialize component state properties\n this.commands = [];\n this.filteredCommands = [];\n this.selectedIndex = 0;\n this.previouslyFocusedElement = null;\n // Create dialog element\n this.dialog = this.create('dialog', {});\n // Create container div\n const container = this.create('div', {\n class: 'command-panel-container'\n });\n // Create search input\n this.searchInput = this.create('input', {\n type: 'text',\n class: 'command-search',\n placeholder: 'Search commands...'\n });\n // Create command list\n this.commandList = this.create('ul', {\n class: 'command-list'\n });\n // Append elements to container\n container.appendChild(this.searchInput);\n container.appendChild(this.commandList);\n // Append container to dialog\n this.dialog.appendChild(container);\n // Append dialog to component\n this.appendChild(this.dialog);\n // Setup keyboard shortcut listener\n this.setupKeyboardShortcut();\n // Setup dialog event listeners\n this.setupDialogEventListeners();\n }).call(this);\n }\n setupDialogEventListeners() {\n // Add keydown listener to dialog for Escape, ArrowUp, ArrowDown, and Enter\n this.dialog.addEventListener('keydown', (event)=>{\n if (event.key === 'Escape') {\n this.closePanel();\n } else if (event.key === 'ArrowDown') {\n event.preventDefault();\n this.handleArrowNavigation('down');\n } else if (event.key === 'ArrowUp') {\n event.preventDefault();\n this.handleArrowNavigation('up');\n } else if (event.key === 'Enter') {\n event.preventDefault();\n // Get command at selectedIndex from filteredCommands\n const selectedCommand = this.filteredCommands[this.selectedIndex];\n if (selectedCommand) {\n // Call executeCommand with selected command\n this.executeCommand(selectedCommand);\n }\n }\n });\n // Add backdrop click listener (check if event.target === dialog)\n this.dialog.addEventListener('click', (event)=>{\n if (event.target === this.dialog) {\n this.closePanel();\n }\n });\n // Add input event listener to searchInput\n this.searchInput.addEventListener('input', (event)=>{\n this.handleSearch(event);\n });\n }\n fuzzyMatch(query, target) {\n // Convert both to lowercase for case-insensitive matching\n query = query.toLowerCase();\n target = target.toLowerCase();\n let queryIndex = 0;\n let targetIndex = 0;\n // Iterate through query characters and find each in target sequentially\n while(queryIndex < query.length && targetIndex < target.length){\n if (query[queryIndex] === target[targetIndex]) {\n queryIndex++;\n }\n targetIndex++;\n }\n // Return true if all query characters found in order, false otherwise\n return queryIndex === query.length;\n }\n handleSearch(event) {\n // Get current search query value\n const searchQuery = event.target.value;\n // Filter this.commands using fuzzyMatch against command names\n this.filteredCommands = this.commands.filter((command)=>this.fuzzyMatch(searchQuery, command.name));\n // Reset selectedIndex to 0\n this.selectedIndex = 0;\n // Call renderCommands to update display\n this.renderCommands();\n }\n handleArrowNavigation(direction) {\n // Return early if no commands to navigate\n if (this.filteredCommands.length === 0) {\n return;\n }\n if (direction === 'down') {\n // For 'down': increment selectedIndex, wrap to 0 if at end\n this.selectedIndex++;\n if (this.selectedIndex >= this.filteredCommands.length) {\n this.selectedIndex = 0;\n }\n } else if (direction === 'up') {\n // For 'up': decrement selectedIndex, wrap to last if at start\n this.selectedIndex--;\n if (this.selectedIndex < 0) {\n this.selectedIndex = this.filteredCommands.length - 1;\n }\n }\n // Call renderCommands to update visual selection\n this.renderCommands();\n }\n addCommand(name, icon, callback) {\n // Validate that name is a non-empty string\n if (!name || typeof name !== 'string') {\n console.error('Command name must be a non-empty string');\n return;\n }\n // Validate that callback is a function\n if (!callback || typeof callback !== 'function') {\n console.error('Command callback must be a function');\n return;\n }\n // Add command object to this.commands array\n this.commands.push({\n name,\n icon,\n callback\n });\n }\n parseKeyboardShortcut(keys) {\n // Split keys by '+' delimiter and parse modifiers\n const parts = keys.toLowerCase().split('+');\n // Initialize KeyConfig object with boolean flags\n const config = {\n ctrl: false,\n shift: false,\n alt: false,\n meta: false,\n key: ''\n };\n // Parse each part and set appropriate flags\n parts.forEach((part)=>{\n const trimmedPart = part.trim();\n if (trimmedPart === 'ctrl') {\n config.ctrl = true;\n } else if (trimmedPart === 'shift') {\n config.shift = true;\n } else if (trimmedPart === 'alt') {\n config.alt = true;\n } else if (trimmedPart === 'cmd' || trimmedPart === 'meta') {\n config.meta = true;\n } else {\n config.key = trimmedPart;\n }\n });\n return config;\n }\n setupKeyboardShortcut() {\n // Remove existing listener if present\n if (this.keyboardListener) {\n document.removeEventListener('keydown', this.keyboardListener);\n }\n // Read open-keys attribute or default to \"ctrl+shift+p\"\n const openKeys = this.getAttribute('open-keys') || 'ctrl+shift+p';\n // Parse keyboard shortcut using parseKeyboardShortcut\n const keyConfig = this.parseKeyboardShortcut(openKeys);\n // Create and store the keyboard listener\n this.keyboardListener = (event)=>{\n // Check if pressed keys match configured shortcut\n const ctrlMatch = keyConfig.ctrl === (event.ctrlKey || event.metaKey);\n const shiftMatch = keyConfig.shift === event.shiftKey;\n const altMatch = keyConfig.alt === event.altKey;\n const metaMatch = keyConfig.meta === event.metaKey;\n const keyMatch = event.key.toLowerCase() === keyConfig.key;\n // Call openPanel when shortcut matches\n if (ctrlMatch && shiftMatch && altMatch && metaMatch && keyMatch) {\n event.preventDefault();\n this.openPanel();\n }\n };\n // Add document-level keydown event listener\n document.addEventListener('keydown', this.keyboardListener);\n }\n openPanel() {\n // Store reference to previously focused element\n this.previouslyFocusedElement = document.activeElement;\n // Call dialog.showModal() to display dialog\n this.dialog.showModal();\n // Focus on searchInput element\n this.searchInput.focus();\n // Reset filteredCommands to show all commands\n this.filteredCommands = [\n ...this.commands\n ];\n // Reset selectedIndex to 0\n this.selectedIndex = 0;\n // Call renderCommands to display initial list\n this.renderCommands();\n }\n closePanel() {\n // Call dialog.close() to hide dialog\n this.dialog.close();\n // Clear search input value\n this.searchInput.value = '';\n // Reset filteredCommands array\n this.filteredCommands = [];\n // Restore focus to previously focused element\n if (this.previouslyFocusedElement) {\n this.previouslyFocusedElement.focus();\n }\n }\n renderCommands() {\n // Clear commandList innerHTML\n this.commandList.innerHTML = '';\n console.log('Rendering commands:', this.filteredCommands.length);\n // Check if filteredCommands is empty and show \"No commands found\" message\n if (this.filteredCommands.length === 0) {\n const noResultsItem = this.create('li', {\n class: 'no-results',\n content: 'No commands found'\n });\n this.commandList.appendChild(noResultsItem);\n return;\n }\n // Loop through filteredCommands and create li elements for each\n this.filteredCommands.forEach((command, index)=>{\n // Create li element with command-item class\n const li = this.create('li', {\n class: 'command-item'\n });\n // Add selected class to item at selectedIndex\n if (index === this.selectedIndex) {\n li.classList.add('selected');\n }\n // Create span for icon (if present) with command-icon class\n if (command.icon) {\n const iconSpan = this.create('span', {\n class: 'command-icon',\n content: command.icon\n });\n li.appendChild(iconSpan);\n }\n // Create span for name with command-name class\n const nameSpan = this.create('span', {\n class: 'command-name',\n content: command.name\n });\n li.appendChild(nameSpan);\n // Add click listener to each li to execute that command\n li.addEventListener('click', ()=>{\n this.executeCommand(command);\n });\n // Append li element to commandList\n this.commandList.appendChild(li);\n });\n }\n executeCommand(command) {\n // Emit 'COMMAND-EXECUTED' event using this.event() with command name and icon\n this.event('COMMAND-EXECUTED', {\n name: command.name,\n icon: command.icon\n });\n // Invoke command.callback function\n command.callback();\n // Call closePanel to hide dialog\n this.closePanel();\n }\n}\ncustomElements.define('command-panel', CommandPanel);\nexport default CommandPanel;\n"],"names":["HTMLElement","document","Object","CustomEvent","console","e","localStorage","AbortController","setTimeout","fetch","JSON","Error","MutationObserver","a","Promise","customElements","Blob","Worker","URL","arguments","t","n"],"mappings":"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"}
package/index.css ADDED
@@ -0,0 +1,53 @@
1
+ /*
2
+
3
+ *** begin ascii art ***
4
+
5
+ CCCCCCCCCCCCC SSSSSSSSSSSSSSS SSSSSSSSSSSSSSS
6
+ CCC::::::::::::C SS:::::::::::::::S SS:::::::::::::::S
7
+ CC:::::::::::::::CS:::::SSSSSS::::::SS:::::SSSSSS::::::S
8
+ C:::::CCCCCCCC::::CS:::::S SSSSSSSS:::::S SSSSSSS
9
+ C:::::C CCCCCCS:::::S S:::::S
10
+ C:::::C S:::::S S:::::S
11
+ C:::::C S::::SSSS S::::SSSS
12
+ C:::::C SS::::::SSSSS SS::::::SSSSS
13
+ C:::::C SSS::::::::SS SSS::::::::SS
14
+ C:::::C SSSSSS::::S SSSSSS::::S
15
+ C:::::C S:::::S S:::::S
16
+ C:::::C CCCCCC S:::::S S:::::S
17
+ C:::::CCCCCCCC::::CSSSSSSS S:::::SSSSSSSS S:::::S
18
+ CC:::::::::::::::CS::::::SSSSSS:::::SS::::::SSSSSS:::::S
19
+ CCC::::::::::::CS:::::::::::::::SS S:::::::::::::::SS
20
+ CCCCCCCCCCCCC SSSSSSSSSSSSSSS SSSSSSSSSSSSSSS
21
+ The CSS file
22
+
23
+ *** end ascii art ***
24
+
25
+ A lot of people get into months long javascript framework
26
+ boondoggles for reasons they could have solved in 5 minutes
27
+ fiddling with the humble CSS file.
28
+
29
+ The penultimate best CSS framework is the one where every design
30
+ meeting you, the HTML/CSS Engineer (paid more than any of the
31
+ Javascript Jocks) are rolling your eyes at the cravat wearing
32
+ designer and sarcastically holding up a sticky note that says:
33
+ "What about the standards, bro?"
34
+
35
+ But the absolute best HTML/CSS Framework is the one where the
36
+ designers are writing the CSS and HTML. Drop photoshop, drop
37
+ whatever flavor of the week closed-source Mac only garbage you've
38
+ been sending me that is just bad SVG and write some HTML and CSS.
39
+
40
+ Design is about application of material facts (and yes,
41
+ HTML/CSS is a material fact on screen in browser), so start
42
+ applying the materials.
43
+
44
+ A lot of people think CSS folk aren't Engineers, or that
45
+ Designers can't become good engineers, but I know better. A lot
46
+ of you designers and CSS pushers are already better engineers
47
+ than the Javascript Jocks around you.
48
+
49
+ I know. I see you. Here's where you wow them.
50
+
51
+ LNSY
52
+
53
+ */
package/index.html ADDED
@@ -0,0 +1,131 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <!-- Essential META Tags -->
7
+ <meta property="og:title" content="command-panel">
8
+ <meta property="og:type" content="article" />
9
+ <meta property="og:image" content="">
10
+ <meta property="og:url" content="">
11
+ <meta name="twitter:card" content="summary_large_image">
12
+
13
+ <!-- Non-Essential, But Recommended -->
14
+ <meta property="og:description" content="Command Panel Component">
15
+ <meta property="og:site_name" content="command-panel">
16
+ <meta name="twitter:image:alt" content="">
17
+
18
+ <title>command-panel</title>
19
+ <script src="/main.min.js" type="module"></script>
20
+
21
+ <style>
22
+ :root {
23
+ --background-color: #000;
24
+ --foreground-color: #fff;
25
+ --highlight-color: #ff00ff;
26
+ --secondary-color: #ede9d7;
27
+ --trinary-color: #8aa38a;
28
+ --quaternary-color: #d4cfbd;
29
+ --confirmation-color: #0a5c0a;
30
+ --notification-color: #377c43;
31
+ --inactive-color: #cccccc;
32
+ --warning-color: #639e71;
33
+ --error-color: #ff6b6b;
34
+ --neutral-color: #1c3320;
35
+ --h1-font-size: 200%;
36
+ --h2-font-size: 180%;
37
+ --h3-font-size: 150%;
38
+
39
+ }
40
+ </style>
41
+ </head>
42
+
43
+ <body>
44
+ <h1>Command Panel Demo</h1>
45
+ <p>Press <kbd>Ctrl+Shift+P</kbd> to open the command panel, or <kbd>Alt+K</kbd> for the custom shortcut example.</p>
46
+
47
+ <!-- Default command panel with standard keyboard shortcut -->
48
+ <command-panel id="command_panel"></command-panel>
49
+
50
+ <!-- Command panel with custom keyboard shortcut -->
51
+ <command-panel id="custom_command_panel" open-keys="alt+k"></command-panel>
52
+
53
+ <script>
54
+ //This should probably be an onload command, but for simplicity's sake
55
+ // we're just going to use a timeout. This ensures this code doesn't
56
+ // run until the custom Elements are rendered
57
+ setTimeout(() => {
58
+ // Example component event listener
59
+
60
+ // Command Panel Demo - Default keyboard shortcut (Ctrl+Shift+P)
61
+ const commandPanel = document.getElementById('command_panel');
62
+
63
+ // Add sample commands with various icons
64
+ commandPanel.addCommand('Create New File', '📄', () => {
65
+ console.log('Creating new file...');
66
+ alert('New file created!');
67
+ });
68
+
69
+ commandPanel.addCommand('Open Settings', '⚙️', () => {
70
+ console.log('Opening settings...');
71
+ alert('Settings opened!');
72
+ });
73
+
74
+ commandPanel.addCommand('Search Project', '🔍', () => {
75
+ console.log('Searching project...');
76
+ alert('Search initiated!');
77
+ });
78
+
79
+ commandPanel.addCommand('Run Build', '🔨', () => {
80
+ console.log('Running build...');
81
+ alert('Build started!');
82
+ });
83
+
84
+ commandPanel.addCommand('Deploy Application', '🚀', () => {
85
+ console.log('Deploying application...');
86
+ alert('Deployment started!');
87
+ });
88
+
89
+ commandPanel.addCommand('View Documentation', '📚', () => {
90
+ console.log('Opening documentation...');
91
+ alert('Documentation opened!');
92
+ });
93
+
94
+ // Command without icon
95
+ commandPanel.addCommand('Toggle Dark Mode', null, () => {
96
+ console.log('Toggling dark mode...');
97
+ alert('Dark mode toggled!');
98
+ });
99
+
100
+ // Listen for command execution events
101
+ commandPanel.on('COMMAND-EXECUTED', (data) => {
102
+ console.log('Command executed:', data);
103
+ });
104
+
105
+ // Custom Command Panel Demo - Custom keyboard shortcut (Alt+K)
106
+ const customCommandPanel = document.getElementById('custom_command_panel');
107
+
108
+ customCommandPanel.addCommand('Quick Action 1', '⚡', () => {
109
+ console.log('Quick action 1 executed');
110
+ alert('Quick Action 1!');
111
+ });
112
+
113
+ customCommandPanel.addCommand('Quick Action 2', '✨', () => {
114
+ console.log('Quick action 2 executed');
115
+ alert('Quick Action 2!');
116
+ });
117
+
118
+ customCommandPanel.addCommand('Quick Action 3', '🎯', () => {
119
+ console.log('Quick action 3 executed');
120
+ alert('Quick Action 3!');
121
+ });
122
+
123
+ customCommandPanel.on('COMMAND-EXECUTED', (data) => {
124
+ console.log('Custom command executed:', data);
125
+ });
126
+
127
+ }, 1000)
128
+ </script>
129
+ </body>
130
+
131
+ </html>
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import "./index.css";
2
+ import "./src/example-component.js";
3
+ import "./src/command-panel.js";
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "command-panel",
3
+ "version": "1.0.0",
4
+ "description": "Command Panel Component",
5
+ "main": "dist/main.min.js",
6
+ "type": "module",
7
+ "author": "LNSY <lindsey.mysse@gmail.com>",
8
+ "license": "Unlicense",
9
+ "keywords": [
10
+ "web-workers",
11
+ "custom-elements",
12
+ "vanilla-js",
13
+ "dataroom-js",
14
+ "rspack",
15
+ "boilerplate"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/lnsy-dev/command-panel.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/lnsy-dev/command-panel/issues"
23
+ },
24
+ "homepage": "https://github.com/lnsy-dev/command-panel#readme",
25
+ "files": [
26
+ "dist/",
27
+ "src/",
28
+ "styles/",
29
+ "index.js",
30
+ "index.css",
31
+ "index.html"
32
+ ],
33
+ "scripts": {
34
+ "test": "echo \"Error: no test specified\" && exit 1",
35
+ "start": "rspack serve",
36
+ "build": "NODE_ENV=production rspack build",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+ "dependencies": {
40
+ "dataroom-js": "^0.6.0"
41
+ },
42
+ "devDependencies": {
43
+ "@rspack/cli": "^1.5.6",
44
+ "@rspack/core": "^1.5.6",
45
+ "@rspack/dev-server": "^1.1.4",
46
+ "css-loader": "^7.1.2",
47
+ "cssnano": "^7.1.2",
48
+ "dotenv": "^17.2.2",
49
+ "postcss": "^8.5.6",
50
+ "postcss-loader": "^8.2.0",
51
+ "style-loader": "^4.0.0"
52
+ }
53
+ }
@@ -0,0 +1,99 @@
1
+
2
+ /* Dialog element styling */
3
+ command-panel dialog {
4
+ position: fixed;
5
+ top: 20%;
6
+ left: 50%;
7
+ transform: translateX(-50%);
8
+ margin: 0;
9
+ z-index: 100;
10
+ width: 600px;
11
+ max-width: 90vw;
12
+ border: none;
13
+ border-radius: 8px;
14
+ padding: 0;
15
+ background-color: var(--background-color);
16
+ }
17
+
18
+ command-panel dialog::backdrop {
19
+ background-color: rgba(0, 0, 0, 0.5);
20
+ }
21
+
22
+ /* Container styling */
23
+ .command-panel-container {
24
+ display: flex;
25
+ flex-direction: column;
26
+ max-height: 400px;
27
+ overflow: hidden;
28
+ }
29
+
30
+ /* Search input styling */
31
+ .command-search {
32
+ padding: 12px 16px;
33
+ border: none;
34
+ border-bottom: 2px solid var(--trinary-color);
35
+ background-color: var(--background-color);
36
+ color: var(--foreground-color);
37
+ font-size: 16px;
38
+ outline: none;
39
+ }
40
+
41
+ .command-search:focus {
42
+ border-bottom-color: var(--foreground-color);
43
+ background-color: var(--secondary-color);
44
+ }
45
+
46
+ .command-search::placeholder {
47
+ color: var(--trinary-color);
48
+ }
49
+
50
+ /* Command list styling */
51
+ .command-list {
52
+ overflow-y: auto;
53
+ list-style: none;
54
+ margin: 0;
55
+ padding: 0;
56
+ max-height: 340px;
57
+ }
58
+
59
+ /* Command item styling */
60
+ .command-item {
61
+ display: flex;
62
+ align-items: center;
63
+ padding: 10px 16px;
64
+ cursor: pointer;
65
+ gap: 12px;
66
+ color: var(--foreground-color);
67
+ transition: background-color 0.15s ease;
68
+ }
69
+
70
+ .command-item:hover {
71
+ background-color: var(--secondary-color);
72
+ }
73
+
74
+ .command-item.selected {
75
+ background-color: var(--highlight-color);
76
+ }
77
+
78
+ /* Command icon styling */
79
+ .command-icon {
80
+ font-size: 1.2em;
81
+ width: 24px;
82
+ text-align: center;
83
+ flex-shrink: 0;
84
+ }
85
+
86
+ /* Command name styling */
87
+ .command-name {
88
+ flex: 1;
89
+ font-size: 14px;
90
+ }
91
+
92
+ /* No results message styling */
93
+ .no-results {
94
+ padding: 20px 16px;
95
+ text-align: center;
96
+ color: var(--trinary-color);
97
+ font-style: italic;
98
+ list-style: none;
99
+ }
@@ -0,0 +1,345 @@
1
+ import DataroomElement from 'dataroom-js';
2
+ import './command-panel.css';
3
+
4
+ class CommandPanel extends DataroomElement {
5
+ // Define static observedAttributes getter returning ['open-keys']
6
+ static get observedAttributes() {
7
+ return ['open-keys'];
8
+ }
9
+
10
+ // Implement attributeChangedCallback method
11
+ attributeChangedCallback(name, oldValue, newValue) {
12
+ // Re-setup keyboard shortcut when open-keys attribute changes
13
+ if (name === 'open-keys' && oldValue !== newValue && this.keyboardListener) {
14
+ this.setupKeyboardShortcut();
15
+ }
16
+ }
17
+
18
+ async initialize() {
19
+ // Initialize component state properties
20
+ this.commands = [];
21
+ this.filteredCommands = [];
22
+ this.selectedIndex = 0;
23
+ this.previouslyFocusedElement = null;
24
+
25
+ // Create dialog element
26
+ this.dialog = this.create('dialog', {});
27
+
28
+ // Create container div
29
+ const container = this.create('div', {
30
+ class: 'command-panel-container'
31
+ });
32
+
33
+ // Create search input
34
+ this.searchInput = this.create('input', {
35
+ type: 'text',
36
+ class: 'command-search',
37
+ placeholder: 'Search commands...'
38
+ });
39
+
40
+ // Create command list
41
+ this.commandList = this.create('ul', {
42
+ class: 'command-list'
43
+ });
44
+
45
+ // Append elements to container
46
+ container.appendChild(this.searchInput);
47
+ container.appendChild(this.commandList);
48
+
49
+ // Append container to dialog
50
+ this.dialog.appendChild(container);
51
+
52
+ // Append dialog to component
53
+ this.appendChild(this.dialog);
54
+
55
+ // Setup keyboard shortcut listener
56
+ this.setupKeyboardShortcut();
57
+
58
+ // Setup dialog event listeners
59
+ this.setupDialogEventListeners();
60
+ }
61
+
62
+ setupDialogEventListeners() {
63
+ // Add keydown listener to dialog for Escape, ArrowUp, ArrowDown, and Enter
64
+ this.dialog.addEventListener('keydown', (event) => {
65
+ if (event.key === 'Escape') {
66
+ this.closePanel();
67
+ } else if (event.key === 'ArrowDown') {
68
+ event.preventDefault();
69
+ this.handleArrowNavigation('down');
70
+ } else if (event.key === 'ArrowUp') {
71
+ event.preventDefault();
72
+ this.handleArrowNavigation('up');
73
+ } else if (event.key === 'Enter') {
74
+ event.preventDefault();
75
+ // Get command at selectedIndex from filteredCommands
76
+ const selectedCommand = this.filteredCommands[this.selectedIndex];
77
+ if (selectedCommand) {
78
+ // Call executeCommand with selected command
79
+ this.executeCommand(selectedCommand);
80
+ }
81
+ }
82
+ });
83
+
84
+ // Add backdrop click listener (check if event.target === dialog)
85
+ this.dialog.addEventListener('click', (event) => {
86
+ if (event.target === this.dialog) {
87
+ this.closePanel();
88
+ }
89
+ });
90
+
91
+ // Add input event listener to searchInput
92
+ this.searchInput.addEventListener('input', (event) => {
93
+ this.handleSearch(event);
94
+ });
95
+ }
96
+
97
+ fuzzyMatch(query, target) {
98
+ // Convert both to lowercase for case-insensitive matching
99
+ query = query.toLowerCase();
100
+ target = target.toLowerCase();
101
+
102
+ let queryIndex = 0;
103
+ let targetIndex = 0;
104
+
105
+ // Iterate through query characters and find each in target sequentially
106
+ while (queryIndex < query.length && targetIndex < target.length) {
107
+ if (query[queryIndex] === target[targetIndex]) {
108
+ queryIndex++;
109
+ }
110
+ targetIndex++;
111
+ }
112
+
113
+ // Return true if all query characters found in order, false otherwise
114
+ return queryIndex === query.length;
115
+ }
116
+
117
+ handleSearch(event) {
118
+ // Get current search query value
119
+ const searchQuery = event.target.value;
120
+
121
+ // Filter this.commands using fuzzyMatch against command names
122
+ this.filteredCommands = this.commands.filter(command =>
123
+ this.fuzzyMatch(searchQuery, command.name)
124
+ );
125
+
126
+ // Reset selectedIndex to 0
127
+ this.selectedIndex = 0;
128
+
129
+ // Call renderCommands to update display
130
+ this.renderCommands();
131
+ }
132
+
133
+ handleArrowNavigation(direction) {
134
+ // Return early if no commands to navigate
135
+ if (this.filteredCommands.length === 0) {
136
+ return;
137
+ }
138
+
139
+ if (direction === 'down') {
140
+ // For 'down': increment selectedIndex, wrap to 0 if at end
141
+ this.selectedIndex++;
142
+ if (this.selectedIndex >= this.filteredCommands.length) {
143
+ this.selectedIndex = 0;
144
+ }
145
+ } else if (direction === 'up') {
146
+ // For 'up': decrement selectedIndex, wrap to last if at start
147
+ this.selectedIndex--;
148
+ if (this.selectedIndex < 0) {
149
+ this.selectedIndex = this.filteredCommands.length - 1;
150
+ }
151
+ }
152
+
153
+ // Call renderCommands to update visual selection
154
+ this.renderCommands();
155
+ }
156
+
157
+ addCommand(name, icon, callback) {
158
+ // Validate that name is a non-empty string
159
+ if (!name || typeof name !== 'string') {
160
+ console.error('Command name must be a non-empty string');
161
+ return;
162
+ }
163
+
164
+ // Validate that callback is a function
165
+ if (!callback || typeof callback !== 'function') {
166
+ console.error('Command callback must be a function');
167
+ return;
168
+ }
169
+
170
+ // Add command object to this.commands array
171
+ this.commands.push({ name, icon, callback });
172
+ }
173
+
174
+ parseKeyboardShortcut(keys) {
175
+ // Split keys by '+' delimiter and parse modifiers
176
+ const parts = keys.toLowerCase().split('+');
177
+
178
+ // Initialize KeyConfig object with boolean flags
179
+ const config = {
180
+ ctrl: false,
181
+ shift: false,
182
+ alt: false,
183
+ meta: false,
184
+ key: ''
185
+ };
186
+
187
+ // Parse each part and set appropriate flags
188
+ parts.forEach(part => {
189
+ const trimmedPart = part.trim();
190
+ if (trimmedPart === 'ctrl') {
191
+ config.ctrl = true;
192
+ } else if (trimmedPart === 'shift') {
193
+ config.shift = true;
194
+ } else if (trimmedPart === 'alt') {
195
+ config.alt = true;
196
+ } else if (trimmedPart === 'cmd' || trimmedPart === 'meta') {
197
+ config.meta = true;
198
+ } else {
199
+ config.key = trimmedPart;
200
+ }
201
+ });
202
+
203
+ return config;
204
+ }
205
+
206
+ setupKeyboardShortcut() {
207
+ // Remove existing listener if present
208
+ if (this.keyboardListener) {
209
+ document.removeEventListener('keydown', this.keyboardListener);
210
+ }
211
+
212
+ // Read open-keys attribute or default to "ctrl+shift+p"
213
+ const openKeys = this.getAttribute('open-keys') || 'ctrl+shift+p';
214
+
215
+ // Parse keyboard shortcut using parseKeyboardShortcut
216
+ const keyConfig = this.parseKeyboardShortcut(openKeys);
217
+
218
+ // Create and store the keyboard listener
219
+ this.keyboardListener = (event) => {
220
+ // Check if pressed keys match configured shortcut
221
+ const ctrlMatch = keyConfig.ctrl === (event.ctrlKey || event.metaKey);
222
+ const shiftMatch = keyConfig.shift === event.shiftKey;
223
+ const altMatch = keyConfig.alt === event.altKey;
224
+ const metaMatch = keyConfig.meta === event.metaKey;
225
+ const keyMatch = event.key.toLowerCase() === keyConfig.key;
226
+
227
+ // Call openPanel when shortcut matches
228
+ if (ctrlMatch && shiftMatch && altMatch && metaMatch && keyMatch) {
229
+ event.preventDefault();
230
+ this.openPanel();
231
+ }
232
+ };
233
+
234
+ // Add document-level keydown event listener
235
+ document.addEventListener('keydown', this.keyboardListener);
236
+ }
237
+
238
+ openPanel() {
239
+ // Store reference to previously focused element
240
+ this.previouslyFocusedElement = document.activeElement;
241
+
242
+ // Call dialog.showModal() to display dialog
243
+ this.dialog.showModal();
244
+
245
+ // Focus on searchInput element
246
+ this.searchInput.focus();
247
+
248
+ // Reset filteredCommands to show all commands
249
+ this.filteredCommands = [...this.commands];
250
+
251
+ // Reset selectedIndex to 0
252
+ this.selectedIndex = 0;
253
+
254
+ // Call renderCommands to display initial list
255
+ this.renderCommands();
256
+ }
257
+
258
+ closePanel() {
259
+ // Call dialog.close() to hide dialog
260
+ this.dialog.close();
261
+
262
+ // Clear search input value
263
+ this.searchInput.value = '';
264
+
265
+ // Reset filteredCommands array
266
+ this.filteredCommands = [];
267
+
268
+ // Restore focus to previously focused element
269
+ if (this.previouslyFocusedElement) {
270
+ this.previouslyFocusedElement.focus();
271
+ }
272
+ }
273
+
274
+ renderCommands() {
275
+ // Clear commandList innerHTML
276
+ this.commandList.innerHTML = '';
277
+
278
+ console.log('Rendering commands:', this.filteredCommands.length);
279
+
280
+ // Check if filteredCommands is empty and show "No commands found" message
281
+ if (this.filteredCommands.length === 0) {
282
+ const noResultsItem = this.create('li', {
283
+ class: 'no-results',
284
+ content: 'No commands found'
285
+ });
286
+ this.commandList.appendChild(noResultsItem);
287
+ return;
288
+ }
289
+
290
+ // Loop through filteredCommands and create li elements for each
291
+ this.filteredCommands.forEach((command, index) => {
292
+ // Create li element with command-item class
293
+ const li = this.create('li', {
294
+ class: 'command-item'
295
+ });
296
+
297
+ // Add selected class to item at selectedIndex
298
+ if (index === this.selectedIndex) {
299
+ li.classList.add('selected');
300
+ }
301
+
302
+ // Create span for icon (if present) with command-icon class
303
+ if (command.icon) {
304
+ const iconSpan = this.create('span', {
305
+ class: 'command-icon',
306
+ content: command.icon
307
+ });
308
+ li.appendChild(iconSpan);
309
+ }
310
+
311
+ // Create span for name with command-name class
312
+ const nameSpan = this.create('span', {
313
+ class: 'command-name',
314
+ content: command.name
315
+ });
316
+ li.appendChild(nameSpan);
317
+
318
+ // Add click listener to each li to execute that command
319
+ li.addEventListener('click', () => {
320
+ this.executeCommand(command);
321
+ });
322
+
323
+ // Append li element to commandList
324
+ this.commandList.appendChild(li);
325
+ });
326
+ }
327
+
328
+ executeCommand(command) {
329
+ // Emit 'COMMAND-EXECUTED' event using this.event() with command name and icon
330
+ this.event('COMMAND-EXECUTED', {
331
+ name: command.name,
332
+ icon: command.icon
333
+ });
334
+
335
+ // Invoke command.callback function
336
+ command.callback();
337
+
338
+ // Call closePanel to hide dialog
339
+ this.closePanel();
340
+ }
341
+ }
342
+
343
+ customElements.define('command-panel', CommandPanel);
344
+
345
+ export default CommandPanel;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Example Component
3
+ *
4
+ * A custom HTML element demonstrating dataroom-js features and web worker integration.
5
+ * Creates a simple UI with heading, description, link, and displays web worker responses.
6
+ */
7
+
8
+ import DataroomElement from 'dataroom-js';
9
+
10
+ /**
11
+ * ExampleComponent class
12
+ *
13
+ * Custom element that extends DataroomElement to demonstrate:
14
+ * - Creating HTML elements with the create() method
15
+ * - Web worker instantiation and communication
16
+ * - Dynamic content rendering from worker responses
17
+ *
18
+ * @extends DataroomElement
19
+ */
20
+ class ExampleComponent extends DataroomElement {
21
+ /**
22
+ * Initialize the component
23
+ *
24
+ * Creates the component's UI elements and sets up web worker communication.
25
+ * Renders a heading, description paragraph, link to dataroom.js documentation,
26
+ * and initiates communication with a web worker.
27
+ *
28
+ * @async
29
+ * @returns {Promise<void>}
30
+ */
31
+ async initialize(){
32
+ this.create("h1", {content: "Example Code"});
33
+ const p = this.create("p", {content: "This element uses the dataroom.js. It provides a few features that make using custom HTML Elements easier!"})
34
+ this.create("a", {
35
+ content: "Check it out here!",
36
+ href:"https://dataroom-network.github.io/dataroom.js/"}
37
+ );
38
+
39
+ // Initialize web worker
40
+ const worker = new Worker(new URL('./example-webworker.js', import.meta.url));
41
+
42
+ /**
43
+ * Handle messages from the web worker
44
+ *
45
+ * @param {MessageEvent} event - Message event from worker
46
+ * @param {Object} event.data - Data received from worker
47
+ */
48
+ worker.onmessage = (event) => {
49
+ console.log("Message received from worker:", event.data);
50
+ this.create("p", {content: JSON.stringify(event.data)});
51
+
52
+ // Emits a dataroom event
53
+ this.event("WEB-WORKER-RESPONSE", event.data);
54
+ };
55
+
56
+ // Send initial message to worker
57
+ worker.postMessage({ message: "Hello from the main thread!" });
58
+ }
59
+ }
60
+
61
+ // Register the custom element
62
+ if (!customElements.get('example-component')) {
63
+ customElements.define('example-component', ExampleComponent);
64
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Example Web Worker
3
+ *
4
+ * Simple web worker that receives messages from the main thread and responds.
5
+ * Demonstrates basic worker communication pattern.
6
+ */
7
+
8
+ /**
9
+ * Message handler for incoming messages from main thread
10
+ *
11
+ * Logs the received message and sends a response back to the main thread.
12
+ *
13
+ * @param {MessageEvent} event - The message event from the main thread
14
+ * @param {*} event.data - Data sent from the main thread
15
+ */
16
+ self.onmessage = (event) => {
17
+ console.log("Message received in worker:", event.data);
18
+ self.postMessage({ message: "Hello from Web Worker!" });
19
+ };
@@ -0,0 +1,33 @@
1
+ /*
2
+ *** begin ascii art ***
3
+
4
+ _ _____ ____ _______ ____ __ ___________
5
+ | | / / | / __ \/ _/ | / __ )/ / / ____/ ___/
6
+ | | / / /| | / /_/ // // /| | / __ / / / __/ \__ \
7
+ | |/ / ___ |/ _, _// // ___ |/ /_/ / /___/ /___ ___/ /
8
+ |___/_/ |_/_/ |_/___/_/ |_/_____/_____/_____//____/
9
+
10
+ *** end ascii art ***
11
+
12
+ Most aesthethic options should live here for adjustment
13
+ */
14
+
15
+
16
+ :root {
17
+ --background-color: #fcf9f0;
18
+ --foreground-color: #0a5c0a;
19
+ --highlight-color: #b3d9b6;
20
+ --secondary-color: #ede9d7;
21
+ --trinary-color: #8aa38a;
22
+ --quaternary-color: #d4cfbd;
23
+ --confirmation-color: #0a5c0a;
24
+ --notification-color: #377c43;
25
+ --inactive-color: #cccccc;
26
+ --warning-color: #639e71;
27
+ --error-color: #ff6b6b;
28
+ --neutral-color: #1c3320;
29
+ --h1-font-size: 200%;
30
+ --h2-font-size: 180%;
31
+ --h3-font-size: 150%;
32
+
33
+ }