@ape-egg/codie 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,232 @@
1
+ # Codie
2
+
3
+ Modular code display and editing package for the web.
4
+
5
+ ## Overview
6
+
7
+ **Codie** is a lightweight, modular code highlighter and editor that bridges the gap between static code display and full in-browser IDEs. Built with vanilla JavaScript, it provides syntax highlighting, live editing, and seamless integration with reactive frameworks like Vibe.
8
+
9
+ **Vision:** Codie is a spectrum from static highlighter to full in-browser IDE:
10
+
11
+ ```
12
+ Minimal (vanilla) → Editor → Full REPL (vibe-powered)
13
+ highlight + format +editable +live output, inspect,
14
+ +numberedRows visual editing, zero-build HMR
15
+ ```
16
+
17
+ ## Features
18
+
19
+ ### Core Features
20
+
21
+ - **Syntax Highlighting**: HTML, CSS, and JavaScript with language-aware highlighting
22
+ - **Editable Mode**: Transform static code into a live editor with textarea overlay
23
+ - **Line Numbers**: Optional numbered rows for code reference
24
+ - **Template Support**: Use `<template codie>` to preserve HTML without browser parsing
25
+ - **Language Modes**: Granular control via attributes (`highlight-html`, `highlight-css`, `highlight-js`)
26
+ - **Dark/Light Themes**: Toggle with `[dark]` attribute
27
+ - **Runtime State Toggle**: Change features dynamically via `codieRef.editable`, `codieRef.numberedRows`
28
+
29
+ ### Editing Features
30
+
31
+ - **Tab Handling**: Tab key inserts 2 spaces
32
+ - **Multi-line Indent**: Select multiple lines + Tab indents all
33
+ - **Outdent**: Shift+Tab removes indentation
34
+ - **Live Preview**: `onEdit` callback fires on every change
35
+ - **Error Handling**: `onError` callback for execution failures
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ npm install @ape-egg/codie
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Basic Syntax Highlighting
46
+
47
+ ```html
48
+ <link rel="stylesheet" href="@ape-egg/codie/codie.css" />
49
+ <script type="module">
50
+ import codie from '@ape-egg/codie';
51
+
52
+ codie('[codie]');
53
+ </script>
54
+
55
+ <template codie>
56
+ <div>
57
+ <p>Hello, world!</p>
58
+ </div>
59
+ </template>
60
+ ```
61
+
62
+ ### Editable Code with Line Numbers
63
+
64
+ ```js
65
+ import codie from '@ape-egg/codie';
66
+
67
+ const editor = codie('[codie]');
68
+ editor.editable = true;
69
+ editor.numberedRows = true;
70
+ ```
71
+
72
+ ### Live Preview with onEdit
73
+
74
+ ```js
75
+ const outputRef = document.querySelector('output');
76
+
77
+ const editor = codie('[codie]');
78
+ editor.editable = true;
79
+ editor.onEdit = ({ formatted, raw }) => {
80
+ outputRef.innerHTML = formatted;
81
+ };
82
+ ```
83
+
84
+ ### Language-Specific Highlighting
85
+
86
+ ```html
87
+ <!-- Only highlight HTML -->
88
+ <template codie highlight-html>...</template>
89
+
90
+ <!-- Only highlight CSS -->
91
+ <template codie highlight-css>...</template>
92
+
93
+ <!-- Only highlight JavaScript -->
94
+ <template codie highlight-js>...</template>
95
+
96
+ <!-- Highlight all languages (default) -->
97
+ <template codie>...</template>
98
+ ```
99
+
100
+ ## API
101
+
102
+ ### `codie(selector | element | config)`
103
+
104
+ Initialize a codie instance.
105
+
106
+ **Parameters:**
107
+ - `selector` (string): Query selector for codie element
108
+ - `element` (Element): DOM element reference
109
+ - `config` (object): Configuration object with optional `editable`, `numberedRows`, `foldable` properties
110
+
111
+ **Returns:** Codie instance proxy
112
+
113
+ ### Instance Properties
114
+
115
+ ```js
116
+ const editor = codie('[codie]');
117
+
118
+ // Get/set code content
119
+ editor.code; // Get current code
120
+ editor.code = '<div>New code</div>'; // Set code (updates display + textarea)
121
+
122
+ // Toggle features
123
+ editor.editable = true; // Enable editing
124
+ editor.numberedRows = true; // Show line numbers
125
+ editor.foldable = false; // Disable code folding (experimental)
126
+
127
+ // Callbacks
128
+ editor.onEdit = ({ formatted, raw }) => {
129
+ // formatted: cleaned up for display
130
+ // raw: exact code content
131
+ };
132
+
133
+ editor.onError = (error) => {
134
+ // Handle errors during editing
135
+ };
136
+ ```
137
+
138
+ ### Exported Utilities
139
+
140
+ ```js
141
+ import {
142
+ highlightHTML,
143
+ highlightJS,
144
+ highlightCSS,
145
+ highlightJSRaw,
146
+ escapeHTML,
147
+ unescapeHTML,
148
+ formatDocument,
149
+ normalizeInlineWhitespace,
150
+ ATTR,
151
+ CLASS,
152
+ } from '@ape-egg/codie';
153
+ ```
154
+
155
+ ## Attributes
156
+
157
+ ### Container Attributes
158
+
159
+ - `codie` - Marks element as codie instance
160
+ - `dehydrate` - Skip initialization (for documentation)
161
+ - `highlight-html` - Enable HTML highlighting only
162
+ - `highlight-css` - Enable CSS highlighting only
163
+ - `highlight-js` - Enable JavaScript highlighting only
164
+ - `dark` - Apply dark theme
165
+
166
+ ### Runtime Attributes (applied by codie)
167
+
168
+ - `codie-editable` - Applied when editable mode is active
169
+ - `codie-numbered` - Applied when line numbers are shown
170
+ - `codie-foldable` - Applied when folding is enabled (experimental)
171
+
172
+ ## Architecture
173
+
174
+ Codie uses a **layered approach** with CSS Grid:
175
+
176
+ 1. **Display Layer** (`<pre>`) - Syntax-highlighted code
177
+ 2. **Textarea Layer** (optional) - Editable overlay when `editable = true`
178
+ 3. **Line Numbers** (optional) - Positioned overlay when `numberedRows = true`
179
+
180
+ All layers are synchronized via a reactive proxy that updates the DOM when properties change.
181
+
182
+ ## Vibe Integration
183
+
184
+ Codie pairs naturally with Vibe for live, reactive code editing:
185
+
186
+ ```js
187
+ import { init } from '@ape-egg/vibe';
188
+ import codie from '@ape-egg/codie';
189
+
190
+ init({ output: '' });
191
+
192
+ const editor = codie('[codie]');
193
+ editor.editable = true;
194
+ editor.onEdit = ({ formatted }) => {
195
+ $.output = formatted;
196
+ };
197
+ ```
198
+
199
+ ```html
200
+ <template codie>
201
+ <p>Edit me!</p>
202
+ </template>
203
+
204
+ <output>@[output]</output>
205
+ ```
206
+
207
+ ## Roadmap
208
+
209
+ ### Implemented
210
+
211
+ - ✓ Syntax highlighting (HTML, CSS, JS)
212
+ - ✓ Editable mode with textarea overlay
213
+ - ✓ Line numbers
214
+ - ✓ Tab handling (insert spaces, multi-line indent, outdent)
215
+ - ✓ onEdit/onError callbacks
216
+ - ✓ Template support
217
+ - ✓ Language modes
218
+ - ✓ Dark/light themes
219
+
220
+ ### Planned
221
+
222
+ - Self-documenting REPL (execute code from editor)
223
+ - Smart script execution (only re-run when changed)
224
+ - Vibe exclusion zones (prevent parent vibe from watching preview)
225
+ - Hot state preservation (preserve state across re-execution)
226
+ - Code folding (deferred due to complexity with editable mode)
227
+ - Inspect mode (click preview element to highlight code)
228
+ - Visual editing (drag-drop reorder in preview)
229
+
230
+ ## License
231
+
232
+ MIT
package/codie.css ADDED
@@ -0,0 +1,7 @@
1
+ /* Codie CSS Registry */
2
+ @import "./theme.css";
3
+ @import "./structure.css";
4
+ @import "./highlight-html.css";
5
+ @import "./highlight-css.css";
6
+ @import "./highlight-js.css";
7
+ @import "./highlight-json.css";
package/codie.js ADDED
@@ -0,0 +1,252 @@
1
+ // Codie - Modular Code Display & Editing
2
+ import { ATTR, CLASS } from './constants.js';
3
+ import {
4
+ highlightHTML,
5
+ highlightJS,
6
+ highlightCSS,
7
+ highlightJSON,
8
+ highlightJSRaw,
9
+ highlightCSSOnly,
10
+ highlightJSOnly,
11
+ } from './highlight.js';
12
+ import {
13
+ escapeHTML,
14
+ unescapeHTML,
15
+ formatDocument,
16
+ dedent,
17
+ normalizeInlineWhitespace,
18
+ cleanupBooleanAttrs,
19
+ } from './format.js';
20
+ import { initNumberRows, updateNumberRows } from './numberRows.js';
21
+ import { initEditable, destroyEditable } from './editable.js';
22
+ import { initFoldable, destroyFoldable } from './foldable.js';
23
+
24
+ // Detect which languages to highlight from element attributes
25
+ const getHighlightModes = (el) => {
26
+ const hasHTML = el.hasAttribute(ATTR.HIGHLIGHT_HTML);
27
+ const hasCSS = el.hasAttribute(ATTR.HIGHLIGHT_CSS);
28
+ const hasJS = el.hasAttribute(ATTR.HIGHLIGHT_JS);
29
+ const hasJSON = el.hasAttribute(ATTR.HIGHLIGHT_JSON);
30
+ const explicit = hasHTML || hasCSS || hasJS || hasJSON;
31
+
32
+ return {
33
+ html: !explicit || hasHTML,
34
+ css: !explicit || hasCSS,
35
+ js: !explicit || hasJS,
36
+ json: !explicit || hasJSON,
37
+ };
38
+ };
39
+
40
+ // Highlight code content based on enabled modes
41
+ const highlight = (code, modes, options = {}) => {
42
+ const escaped = escapeHTML(code);
43
+
44
+ // When all modes are enabled (no explicit attribute), detect JSON by content
45
+ const looksLikeJSON = /^\s*[{\[]/.test(code);
46
+
47
+ // JSON takes priority over HTML when content looks like JSON
48
+ if (modes.json && looksLikeJSON) {
49
+ return highlightJSON(escaped);
50
+ }
51
+
52
+ if (modes.html && !looksLikeJSON) {
53
+ return highlightHTML(escaped, {
54
+ ...options,
55
+ highlightJS: modes.js,
56
+ highlightCSS: modes.css,
57
+ });
58
+ }
59
+
60
+ // Fallback: explicit mode selected or no HTML detected
61
+ if (modes.json && !modes.html) {
62
+ return highlightJSON(escaped);
63
+ }
64
+
65
+ let result = escaped;
66
+ if (modes.js) result = highlightJSOnly(result);
67
+ if (modes.css) result = highlightCSSOnly(result);
68
+ return result;
69
+ };
70
+
71
+ // Toggle a feature on the instance
72
+ const toggleFeature = (instance, feature, enabled) => {
73
+ const { el, display, modes } = instance;
74
+
75
+ switch (feature) {
76
+ case 'numberedRows':
77
+ if (enabled && !instance.lineNumbers) {
78
+ instance.lineNumbers = initNumberRows(el, instance._code);
79
+ el.setAttribute(ATTR.NUMBERED, '');
80
+ } else if (!enabled && instance.lineNumbers) {
81
+ instance.lineNumbers.remove();
82
+ instance.lineNumbers = null;
83
+ el.removeAttribute(ATTR.NUMBERED);
84
+ }
85
+ break;
86
+
87
+ case 'editable':
88
+ if (enabled && !instance.textarea) {
89
+ instance.textarea = initEditable(el, instance, instance._config);
90
+ el.setAttribute(ATTR.EDITABLE, '');
91
+ } else if (!enabled && instance.textarea) {
92
+ destroyEditable(instance);
93
+ el.removeAttribute(ATTR.EDITABLE);
94
+ }
95
+ break;
96
+
97
+ case 'foldable':
98
+ if (enabled && !el.hasAttribute(ATTR.FOLDABLE)) {
99
+ initFoldable(el, display, modes);
100
+ el.setAttribute(ATTR.FOLDABLE, '');
101
+ } else if (!enabled && el.hasAttribute(ATTR.FOLDABLE)) {
102
+ destroyFoldable(el, display);
103
+ el.removeAttribute(ATTR.FOLDABLE);
104
+ }
105
+ break;
106
+ }
107
+ };
108
+
109
+ // Main init function - one call per codie element
110
+ const codie = (elOrConfig = {}) => {
111
+ // Determine element and config
112
+ let el, config;
113
+
114
+ if (elOrConfig instanceof Element) {
115
+ el = elOrConfig;
116
+ config = {};
117
+ } else if (typeof elOrConfig === 'string') {
118
+ el = document.querySelector(elOrConfig);
119
+ config = {};
120
+ } else {
121
+ config = elOrConfig;
122
+ el = document.querySelector(`[${ATTR.CODIE}]`);
123
+ }
124
+
125
+ if (!el) {
126
+ console.info('[codie] No element found');
127
+ return null;
128
+ }
129
+
130
+ // Skip dehydrated elements
131
+ if (el.hasAttribute(ATTR.DEHYDRATE)) {
132
+ return null;
133
+ }
134
+
135
+ const modes = getHighlightModes(el);
136
+ let raw = el.tagName === 'TEMPLATE' ? el.innerHTML : el.textContent;
137
+
138
+ // Decode HTML entities from template innerHTML
139
+ raw = unescapeHTML(raw);
140
+
141
+ // Strip empty values from boolean attributes
142
+ raw = cleanupBooleanAttrs(raw);
143
+
144
+ const code = dedent(raw);
145
+
146
+ // Create display layer
147
+ const display = document.createElement('pre');
148
+ display.className = `${CLASS.CODE_LAYER} ${CLASS.DISPLAY}`;
149
+ display.innerHTML = highlight(code, modes, {
150
+ preserveWhitespace: !config.foldable,
151
+ });
152
+
153
+ // Clear and setup element
154
+ el.textContent = '';
155
+ el.appendChild(display);
156
+
157
+ // Internal state
158
+ const internal = {
159
+ el,
160
+ display,
161
+ modes,
162
+ _code: code,
163
+ _config: { ...config },
164
+ textarea: null,
165
+ lineNumbers: null,
166
+ onEdit: null,
167
+ onError: null,
168
+ _editable: config.editable || false,
169
+ _numberedRows: config.numberedRows || false,
170
+ _foldable: config.foldable || false,
171
+ _proxy: null, // Will hold reference to proxy for editable.js
172
+ };
173
+
174
+ // Create reactive proxy
175
+ const instance = new Proxy(internal, {
176
+ get(target, prop) {
177
+ if (prop === 'code') return target._code;
178
+ if (prop === 'editable') return target._editable;
179
+ if (prop === 'numberedRows') return target._numberedRows;
180
+ if (prop === 'foldable') return target._foldable;
181
+ return target[prop];
182
+ },
183
+ set(target, prop, value) {
184
+ if (prop === 'code') {
185
+ // Decode HTML entities when setting code (same as initialization)
186
+ const decoded = unescapeHTML(value);
187
+ target._code = decoded;
188
+ target.display.innerHTML = highlight(decoded, target.modes, {
189
+ preserveWhitespace: !target._foldable,
190
+ });
191
+ if (target.lineNumbers) {
192
+ updateNumberRows(target.lineNumbers, decoded);
193
+ }
194
+ if (target.textarea) {
195
+ target.textarea.value = decoded;
196
+ }
197
+ return true;
198
+ }
199
+
200
+ if (prop === 'editable' && value !== target._editable) {
201
+ target._editable = value;
202
+ toggleFeature(target, 'editable', value);
203
+ return true;
204
+ }
205
+
206
+ if (prop === 'numberedRows' && value !== target._numberedRows) {
207
+ target._numberedRows = value;
208
+ toggleFeature(target, 'numberedRows', value);
209
+ return true;
210
+ }
211
+
212
+ if (prop === 'foldable' && value !== target._foldable) {
213
+ target._foldable = value;
214
+ toggleFeature(target, 'foldable', value);
215
+ return true;
216
+ }
217
+
218
+ // Fire onEdit immediately when callback is set
219
+ if (prop === 'onEdit' && typeof value === 'function') {
220
+ target.onEdit = value;
221
+ value({ formatted: normalizeInlineWhitespace(cleanupBooleanAttrs(target._code)), raw: target._code });
222
+ return true;
223
+ }
224
+
225
+ target[prop] = value;
226
+ return true;
227
+ },
228
+ });
229
+
230
+ // Store proxy reference for editable.js to access callbacks
231
+ internal._proxy = instance;
232
+
233
+ // Initialize features based on config
234
+ if (config.numberedRows) {
235
+ toggleFeature(internal, 'numberedRows', true);
236
+ }
237
+ if (config.editable) {
238
+ toggleFeature(internal, 'editable', true);
239
+ }
240
+ if (config.foldable) {
241
+ toggleFeature(internal, 'foldable', true);
242
+ }
243
+
244
+ return instance;
245
+ };
246
+
247
+ export default codie;
248
+
249
+ // Export utilities for direct use
250
+ export { highlightHTML, highlightJS, highlightCSS, highlightJSRaw };
251
+ export { escapeHTML, unescapeHTML, formatDocument, normalizeInlineWhitespace };
252
+ export { ATTR, CLASS };
package/constants.js ADDED
@@ -0,0 +1,228 @@
1
+ // Codie constants - DRY naming foundation
2
+
3
+ // Attribute names for codie elements
4
+ export const ATTR = {
5
+ CODIE: 'codie',
6
+ HIGHLIGHT_HTML: 'highlight-html',
7
+ HIGHLIGHT_CSS: 'highlight-css',
8
+ HIGHLIGHT_JS: 'highlight-js',
9
+ HIGHLIGHT_JSON: 'highlight-json',
10
+ NUMBERED: 'numbered',
11
+ EDITABLE: 'editable',
12
+ FOLDABLE: 'foldable',
13
+ DEHYDRATE: 'dehydrate',
14
+ }
15
+
16
+ // CSS class names
17
+ export const CLASS = {
18
+ // Layout layers
19
+ CODE_LAYER: 'codie-layer',
20
+ TEXTAREA: 'codie-textarea',
21
+ DISPLAY: 'codie-display',
22
+ LINE_NUMBERS: 'codie-line-numbers',
23
+
24
+ // States
25
+ DARK: 'codie-dark',
26
+ HAS_SELECTION: 'codie-has-selection',
27
+ FOCUSED: 'codie-focused',
28
+
29
+ // Line highlighting
30
+ LINE: 'codie-line',
31
+ LINE_HIGHLIGHT: 'codie-line-highlight',
32
+
33
+ // Folding
34
+ FOLD_WRAPPER: 'codie-fold-wrapper',
35
+ FOLD_HEADER: 'codie-fold-header',
36
+ FOLD_TOGGLE: 'codie-fold-toggle',
37
+ FOLD_LINE: 'codie-fold-line',
38
+ FOLD_CONTENT: 'codie-fold-content',
39
+ FOLD_PREVIEW: 'codie-fold-preview',
40
+ FOLD_OPEN: 'codie-fold-open',
41
+
42
+ // Syntax tokens - HTML
43
+ TAG: 'codie-tag',
44
+ ATTR_NAME: 'codie-attr-name',
45
+ ATTR_VALUE: 'codie-attr-value',
46
+ PUNCTUATION: 'codie-punct',
47
+ COMMENT: 'codie-comment',
48
+ TEXT: 'codie-text',
49
+
50
+ // Syntax tokens - CSS
51
+ CSS_SELECTOR: 'codie-css-selector',
52
+ CSS_PROPERTY: 'codie-css-property',
53
+ CSS_VALUE: 'codie-css-value',
54
+
55
+ // Syntax tokens - JS
56
+ JS_KEYWORD: 'codie-js-keyword',
57
+ JS_IMPORT: 'codie-js-import',
58
+ JS_STRING: 'codie-js-string',
59
+ JS_FUNCTION: 'codie-js-function',
60
+ JS_NUMBER: 'codie-js-number',
61
+ JS_IDENT: 'codie-js-ident',
62
+
63
+ // Syntax tokens - JSON
64
+ JSON_KEY: 'codie-json-key',
65
+ JSON_STRING: 'codie-json-string',
66
+ JSON_NUMBER: 'codie-json-number',
67
+ JSON_BOOLEAN: 'codie-json-boolean',
68
+ JSON_NULL: 'codie-json-null',
69
+
70
+ // Vibe-specific tokens
71
+ VIBE_COLOR: 'codie-vibe',
72
+ VIBE_VARIABLE: 'codie-vibe-var',
73
+ }
74
+
75
+ // CSS variable prefix
76
+ export const VAR_PREFIX = '--codie-'
77
+
78
+ // Void HTML elements (self-closing)
79
+ export const VOID_ELEMENTS = new Set([
80
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
81
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
82
+ ])
83
+
84
+ // Attributes where the string value is meaningful (should keep empty values like attr="")
85
+ // All other attributes are boolean-like (should be stripped to just attr when empty)
86
+ export const VALUE_ATTRS = new Set([
87
+ // Global attributes
88
+ 'class',
89
+ 'style',
90
+ 'id',
91
+ 'title',
92
+ 'lang',
93
+ 'dir',
94
+ 'tabindex',
95
+ 'accesskey',
96
+ 'slot',
97
+ 'part',
98
+ 'is',
99
+ 'nonce',
100
+ 'popover',
101
+ 'anchor',
102
+
103
+ // Enumerated (take specific string values, not truly boolean)
104
+ 'contenteditable',
105
+ 'draggable',
106
+ 'spellcheck',
107
+ 'translate',
108
+ 'autocapitalize',
109
+ 'inputmode',
110
+ 'enterkeyhint',
111
+ 'virtualkeyboardpolicy',
112
+
113
+ // URLs and sources
114
+ 'href',
115
+ 'src',
116
+ 'action',
117
+ 'cite',
118
+ 'data',
119
+ 'poster',
120
+ 'srcset',
121
+ 'imagesrcset',
122
+ 'formaction',
123
+ 'ping',
124
+ 'usemap',
125
+ 'manifest',
126
+ 'codebase',
127
+
128
+ // Form attributes
129
+ 'name',
130
+ 'type',
131
+ 'value',
132
+ 'placeholder',
133
+ 'pattern',
134
+ 'min',
135
+ 'max',
136
+ 'step',
137
+ 'minlength',
138
+ 'maxlength',
139
+ 'size',
140
+ 'accept',
141
+ 'autocomplete',
142
+ 'list',
143
+ 'form',
144
+ 'formmethod',
145
+ 'formtarget',
146
+ 'formenctype',
147
+ 'wrap',
148
+ 'method',
149
+ 'enctype',
150
+ 'for',
151
+ 'dirname',
152
+
153
+ // Text/accessibility
154
+ 'alt',
155
+ 'label',
156
+ 'summary',
157
+ 'abbr',
158
+
159
+ // Dimensions and layout
160
+ 'width',
161
+ 'height',
162
+ 'cols',
163
+ 'rows',
164
+ 'span',
165
+ 'rowspan',
166
+ 'colspan',
167
+ 'low',
168
+ 'high',
169
+ 'optimum',
170
+
171
+ // Link/resource hints
172
+ 'target',
173
+ 'rel',
174
+ 'hreflang',
175
+ 'download',
176
+ 'as',
177
+ 'media',
178
+ 'charset',
179
+ 'crossorigin',
180
+ 'integrity',
181
+ 'loading',
182
+ 'decoding',
183
+ 'fetchpriority',
184
+ 'referrerpolicy',
185
+ 'blocking',
186
+ 'imagesizes',
187
+ 'sizes',
188
+
189
+ // Media
190
+ 'preload',
191
+ 'kind',
192
+ 'srclang',
193
+
194
+ // Meta
195
+ 'content',
196
+ 'http-equiv',
197
+
198
+ // iframe/embed
199
+ 'sandbox',
200
+ 'allow',
201
+ 'srcdoc',
202
+ 'credentialless',
203
+
204
+ // Table
205
+ 'headers',
206
+ 'scope',
207
+
208
+ // Datetime
209
+ 'datetime',
210
+
211
+ // Object/embed legacy
212
+ 'coords',
213
+ 'shape',
214
+ ])
215
+
216
+ // JavaScript keywords for syntax highlighting
217
+ export const JS_KEYWORDS = new Set([
218
+ 'const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'do',
219
+ 'switch', 'case', 'break', 'continue', 'try', 'catch', 'finally', 'throw', 'new',
220
+ 'typeof', 'instanceof', 'class', 'extends', 'default', 'async', 'await', 'yield',
221
+ 'this', 'super', 'null', 'undefined', 'true', 'false',
222
+ ])
223
+
224
+ // JavaScript import/export keywords
225
+ export const JS_IMPORT_KEYWORDS = new Set(['import', 'export', 'from'])
226
+
227
+ // JavaScript bracket characters
228
+ export const JS_BRACKETS = new Set(['{', '}', '(', ')', '[', ']'])