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 +198 -0
- package/dist/index.html +29 -0
- package/dist/main.css +2 -0
- package/dist/main.css.map +1 -0
- package/dist/main.min.js +21 -0
- package/dist/main.min.js.map +1 -0
- package/index.css +53 -0
- package/index.html +131 -0
- package/index.js +3 -0
- package/package.json +53 -0
- package/src/command-panel.css +99 -0
- package/src/command-panel.js +345 -0
- package/src/example-component.js +64 -0
- package/src/example-webworker.js +19 -0
- package/styles/variables.css +33 -0
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`.
|
package/dist/index.html
ADDED
|
@@ -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"}
|
package/dist/main.min.js
ADDED
|
@@ -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
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
|
+
}
|