kempo-ui 0.0.76 → 0.0.77

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.
Files changed (167) hide show
  1. package/.github/skills/get-icon/SKILL.md +144 -0
  2. package/.github/skills/highlight-code/SKILL.md +57 -0
  3. package/README.md +83 -1
  4. package/bin/get_icon.js +106 -0
  5. package/bin/highlight_code.js +39 -0
  6. package/bin/icon.js +131 -0
  7. package/bin/list_icons.js +70 -0
  8. package/dist/components/Resize.js +1 -0
  9. package/dist/components/Tree.js +1 -1
  10. package/docs/components/icon.html +28 -6
  11. package/docs/components/photo-viewer.html +7 -7
  12. package/docs/components/tree.html +26 -6
  13. package/docs/icons/account-circle.svg +1 -1
  14. package/docs/icons/add.svg +1 -1
  15. package/docs/icons/arrow-circle.svg +1 -1
  16. package/docs/icons/arrow-line.svg +1 -1
  17. package/docs/icons/arrow.svg +1 -1
  18. package/docs/icons/arrow_drop_down.svg +1 -1
  19. package/docs/icons/cards.svg +1 -1
  20. package/docs/icons/check.svg +1 -1
  21. package/docs/icons/chevron-double.svg +1 -1
  22. package/docs/icons/chevron-line.svg +1 -1
  23. package/docs/icons/chevron.svg +1 -1
  24. package/docs/icons/close.svg +1 -1
  25. package/docs/icons/code.svg +1 -1
  26. package/docs/icons/code_blocks.svg +1 -1
  27. package/docs/icons/copy.svg +1 -1
  28. package/docs/icons/delete.svg +1 -1
  29. package/docs/icons/drag-handle.svg +1 -1
  30. package/docs/icons/edit.svg +1 -1
  31. package/docs/icons/error.svg +1 -1
  32. package/docs/icons/export-file.svg +1 -1
  33. package/docs/icons/file-text.svg +1 -1
  34. package/docs/icons/file.svg +1 -1
  35. package/docs/icons/filter.svg +1 -1
  36. package/docs/icons/folder-open.svg +1 -1
  37. package/docs/icons/folder.svg +1 -1
  38. package/docs/icons/format_align_center.svg +1 -1
  39. package/docs/icons/format_align_justify.svg +1 -1
  40. package/docs/icons/format_align_left.svg +1 -1
  41. package/docs/icons/format_align_right.svg +1 -1
  42. package/docs/icons/format_bold.svg +1 -1
  43. package/docs/icons/format_clear.svg +1 -1
  44. package/docs/icons/format_color_fill.svg +1 -1
  45. package/docs/icons/format_color_text.svg +1 -1
  46. package/docs/icons/format_h1.svg +1 -1
  47. package/docs/icons/format_h2.svg +1 -1
  48. package/docs/icons/format_h3.svg +1 -1
  49. package/docs/icons/format_h4.svg +1 -1
  50. package/docs/icons/format_h5.svg +1 -1
  51. package/docs/icons/format_h6.svg +1 -1
  52. package/docs/icons/format_italic.svg +1 -1
  53. package/docs/icons/format_list_bulleted.svg +1 -1
  54. package/docs/icons/format_list_numbered.svg +1 -1
  55. package/docs/icons/format_paragraph.svg +1 -1
  56. package/docs/icons/format_underlined.svg +1 -1
  57. package/docs/icons/github-mark.svg +1 -1
  58. package/docs/icons/help.svg +1 -1
  59. package/docs/icons/hide.svg +1 -1
  60. package/docs/icons/image.svg +1 -1
  61. package/docs/icons/label.svg +1 -1
  62. package/docs/icons/license.svg +1 -1
  63. package/docs/icons/link.svg +1 -1
  64. package/docs/icons/menu.svg +1 -1
  65. package/docs/icons/mode-auto.svg +1 -1
  66. package/docs/icons/mode-dark.svg +1 -1
  67. package/docs/icons/mode-light.svg +1 -1
  68. package/docs/icons/more_vert.svg +1 -1
  69. package/docs/icons/settings.svg +1 -1
  70. package/docs/icons/show.svg +1 -1
  71. package/docs/icons/strikethrough_s.svg +1 -1
  72. package/docs/icons/table-visibility.svg +1 -1
  73. package/docs/icons/table.svg +1 -1
  74. package/docs/icons/warning.svg +1 -1
  75. package/docs/icons/wysiwyg.svg +1 -1
  76. package/docs/src/components/Resize.js +1 -0
  77. package/docs/src/components/Tree.js +1 -1
  78. package/icons/account-circle.svg +1 -1
  79. package/icons/add.svg +1 -1
  80. package/icons/arrow-circle.svg +1 -1
  81. package/icons/arrow-line.svg +1 -1
  82. package/icons/arrow.svg +1 -1
  83. package/icons/arrow_drop_down.svg +1 -1
  84. package/icons/cards.svg +1 -1
  85. package/icons/check.svg +1 -1
  86. package/icons/chevron-double.svg +1 -1
  87. package/icons/chevron-line.svg +1 -1
  88. package/icons/chevron.svg +1 -1
  89. package/icons/close.svg +1 -1
  90. package/icons/code.svg +1 -1
  91. package/icons/code_blocks.svg +1 -1
  92. package/icons/copy.svg +1 -1
  93. package/icons/delete.svg +1 -1
  94. package/icons/drag-handle.svg +1 -1
  95. package/icons/edit.svg +1 -1
  96. package/icons/error.svg +1 -1
  97. package/icons/export-file.svg +1 -1
  98. package/icons/file-text.svg +1 -1
  99. package/icons/file.svg +1 -1
  100. package/icons/filter.svg +1 -1
  101. package/icons/folder-open.svg +1 -1
  102. package/icons/folder.svg +1 -1
  103. package/icons/format_align_center.svg +1 -1
  104. package/icons/format_align_justify.svg +1 -1
  105. package/icons/format_align_left.svg +1 -1
  106. package/icons/format_align_right.svg +1 -1
  107. package/icons/format_bold.svg +1 -1
  108. package/icons/format_clear.svg +1 -1
  109. package/icons/format_color_fill.svg +1 -1
  110. package/icons/format_color_text.svg +1 -1
  111. package/icons/format_h1.svg +1 -1
  112. package/icons/format_h2.svg +1 -1
  113. package/icons/format_h3.svg +1 -1
  114. package/icons/format_h4.svg +1 -1
  115. package/icons/format_h5.svg +1 -1
  116. package/icons/format_h6.svg +1 -1
  117. package/icons/format_italic.svg +1 -1
  118. package/icons/format_list_bulleted.svg +1 -1
  119. package/icons/format_list_numbered.svg +1 -1
  120. package/icons/format_paragraph.svg +1 -1
  121. package/icons/format_underlined.svg +1 -1
  122. package/icons/github-mark.svg +1 -1
  123. package/icons/help.svg +1 -1
  124. package/icons/hide.svg +1 -1
  125. package/icons/image.svg +1 -1
  126. package/icons/label.svg +1 -1
  127. package/icons/license.svg +1 -1
  128. package/icons/link.svg +1 -1
  129. package/icons/menu.svg +1 -1
  130. package/icons/mode-auto.svg +1 -1
  131. package/icons/mode-dark.svg +1 -1
  132. package/icons/mode-light.svg +1 -1
  133. package/icons/more_vert.svg +1 -1
  134. package/icons/settings.svg +1 -1
  135. package/icons/show.svg +1 -1
  136. package/icons/strikethrough_s.svg +1 -1
  137. package/icons/table-visibility.svg +1 -1
  138. package/icons/table.svg +1 -1
  139. package/icons/warning.svg +1 -1
  140. package/icons/wysiwyg.svg +1 -1
  141. package/package.json +13 -4
  142. package/src/components/Resize.js +1 -0
  143. package/src/components/Tree.js +4 -5
  144. package/icons/circle.svg +0 -1
  145. package/icons/compare.svg +0 -1
  146. package/icons/drawing2.svg +0 -124
  147. package/icons/filter-off.svg +0 -1
  148. package/icons/first.svg +0 -1
  149. package/icons/folder-clear.svg +0 -1
  150. package/icons/folder-create.svg +0 -1
  151. package/icons/key.svg +0 -1
  152. package/icons/label-add.svg +0 -1
  153. package/icons/last.svg +0 -1
  154. package/icons/login.svg +0 -1
  155. package/icons/logout.svg +0 -1
  156. package/icons/open-in-browser.svg +0 -1
  157. package/icons/pause.svg +0 -1
  158. package/icons/play.svg +0 -1
  159. package/icons/refresh.svg +0 -1
  160. package/icons/remove.svg +0 -1
  161. package/icons/save.svg +0 -1
  162. package/icons/search.svg +0 -1
  163. package/icons/storage.svg +0 -1
  164. package/icons/tag.svg +0 -1
  165. package/icons/tools.svg +0 -1
  166. package/icons/trash-x.svg +0 -1
  167. package/scripts/getIcon.js +0 -52
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: get-icon
3
+ description: Fetches icons from the Google Material Design Icon set, saves it to this project, and then formats it.
4
+ ---
5
+
6
+ # Get Icon
7
+
8
+ ## When to Use
9
+
10
+ Use this skill any time an icon is needed — whether the user explicitly asks for one or you determine that a UI element (button, control, action, etc.) would benefit from one. For example, if a user asks you to build a "bold" button for a WYSIWYG toolbar, you should proactively source an appropriate icon without being explicitly told to.
11
+
12
+ ## Overview
13
+
14
+ Icons live in the `icons/` directory as SVG files. The project uses the **Material Symbols Outlined** style from Google's CDN. The workflow has three stages:
15
+
16
+ 1. **Check local icons first** — the icon may already exist
17
+ 2. **Find** the correct Material Symbols name if it doesn't
18
+ 3. **Fetch, format, and save** using the script (directional handling is automatic)
19
+
20
+ ---
21
+
22
+ ## Stage 1: Check Local Icons First
23
+
24
+ Before going to the internet, list the contents of `icons/` and look for an existing icon that fits the need. Use `file_search` or `list_dir` on the `icons/` directory.
25
+
26
+ - If a **clear match** exists (e.g. `icons/format_bold.svg` for a bold button), use it — no download needed.
27
+ - If a **reasonable match** exists for a directional icon (e.g. `icons/arrow.svg` for a left-arrow), use it with the appropriate `direction` attribute — no download needed.
28
+ - If **nothing fits**, proceed to Stage 2.
29
+
30
+ ---
31
+
32
+ ## Stage 2: Find the Icon Name
33
+
34
+ If the user describes an icon but doesn't supply an exact name, search Google Material Symbols to find the right one.
35
+
36
+ Run the search script, which fetches icon names and tags from GitHub and caches them locally in `node_modules/.cache/kempo-ui/`. On subsequent runs it only re-downloads if the icon list has changed (SHA check — ~1KB request vs ~500KB full list). Search matches both icon names **and tags**, so searching `chevron` will also surface `keyboard_arrow_right` because it has a `chevron` tag:
37
+
38
+ ```bash
39
+ npm run listicons -- <search_term>
40
+ ```
41
+
42
+ Example:
43
+ ```bash
44
+ npm run listicons -- bold
45
+ # → format_bold
46
+ ```
47
+
48
+ ```bash
49
+ npm run listicons -- chevron
50
+ # → chevron_right
51
+ # chevron_left
52
+ # keyboard_arrow_right
53
+ # keyboard_arrow_left
54
+ # ...
55
+ ```
56
+
57
+ The results show **full icon names including direction suffixes** so you can see what variants are available. Pick the best match for the user's intent. If no results are returned, try different search terms (e.g. `type` instead of `font`, `text` instead of `word`).
58
+
59
+ ---
60
+
61
+ ## Stage 3: Fetch, Format, and Save
62
+
63
+ Run the fetch script with the chosen icon name. The script automatically handles directional icons — if the name you pass is directional (has a `_left`, `_up`, `_down`, `_backward` suffix, or has no exact match but a `_right`/`_forward` variant exists), it will prompt:
64
+
65
+ ```
66
+ "chevron_left" is a directional icon. kempo-ui uses right direction only and applies CSS rotation.
67
+ Save right-facing variant as "chevron.svg"? (Y/n)
68
+ ```
69
+
70
+ ```bash
71
+ npm run geticon -- <icon_name> [custom_name] [-y]
72
+ ```
73
+
74
+ - `icon_name` — the Material Symbols name as returned by the search script
75
+ - `custom_name` — optional rename (e.g. `keyboard_double_arrow_right double_chevron` saves as `double_chevron.svg`)
76
+ - `-y` — auto-accept the directional prompt without user interaction
77
+
78
+ Examples:
79
+ ```bash
80
+ # Non-directional — downloads silently
81
+ npm run geticon -- format_bold
82
+
83
+ # Directional — prompts to confirm saving right-facing variant as "chevron"
84
+ npm run geticon -- chevron_left
85
+
86
+ # Directional with rename and auto-accept
87
+ npm run geticon -- keyboard_double_arrow_up double_chevron -y
88
+ ```
89
+
90
+ If the script exits with a non-zero code mentioning a 404, the icon name is wrong — return to Stage 2 and search for the correct name.
91
+
92
+ The script produces minified output with only `xmlns` and `viewBox` on `<svg>`, and only `fill="currentColor"` and `d` on each `<path>`:
93
+ ```svg
94
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="..."/></svg>
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Directional Icons and `<k-icon>`
100
+
101
+ When the script saves a right-facing icon (after a directional prompt), the `<k-icon>` component handles rotation via its `direction` attribute:
102
+
103
+ | `direction` value | Rotation applied |
104
+ |---|---|
105
+ | *(omitted)* | 0° — points right |
106
+ | `down` | 90° |
107
+ | `left` | 180° |
108
+ | `up` | 270° |
109
+
110
+ **Example:** A user asking for a "left arrow" after downloading `arrow.svg`:
111
+ ```html
112
+ <k-icon name="arrow" direction="left"></k-icon>
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Complete Example
118
+
119
+ **User asks to "add a bold button" (implicit icon need)**
120
+
121
+ 1. **Local check:** `icons/format_bold.svg` exists → use it directly, no download needed
122
+ 2. **Use:** `<k-icon name="format_bold"></k-icon>`
123
+
124
+ **User asks to "add a thumbs up icon" (explicit, not in local icons)**
125
+
126
+ 1. **Local check:** No match in `icons/`
127
+ 2. **Find:** Search Material Symbols for `thumbs+up` → `thumb_up`
128
+ 3. **Arrow check:** Not directional, proceed normally
129
+ 4. **Run:** `node .github/skills/get-icon/get_icon.js thumb_up`
130
+ 5. **Result:** `icons/thumb_up.svg` saved and build triggered
131
+
132
+ **User asks for a "left arrow icon"**
133
+
134
+ 1. **Local check:** `icons/arrow.svg` exists and is right-facing
135
+ 2. **Arrow check:** Directional — right-facing variant already present
136
+ 3. **No download needed:** use `<k-icon name="arrow" direction="left"></k-icon>`
137
+
138
+ **User asks for a "left arrow icon" (no arrow in local icons)**
139
+
140
+ 1. **Local check:** No arrow icon found
141
+ 2. **Find:** Search for `arrow_forward` or `arrow_right`
142
+ 3. **Arrow check:** Directional — download the right-facing variant with a generic name
143
+ 4. **Run:** `node .github/skills/get-icon/get_icon.js arrow_forward arrow`
144
+ 5. **Use:** `<k-icon name="arrow" direction="left"></k-icon>`
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: highlight-code
3
+ description: Generates syntax-highlighted HTML for use in documentation code examples. USE THIS instead of creating temporary scripts.
4
+ ---
5
+
6
+ # Highlight Code
7
+
8
+ ## When to Use
9
+
10
+ Use this skill whenever you need syntax-highlighted HTML to embed in a documentation page — for example, when updating a `<pre><code class="hljs ...">` block in a `docs/components/*.html` file. **Do not create temporary `.mjs` scripts to do this.**
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ npm run highlightcode -- <lang> "<code>"
16
+ ```
17
+
18
+ Or via npx:
19
+ ```bash
20
+ npx kempo-highlightcode <lang> "<code>"
21
+ ```
22
+
23
+ **Supported languages:** `html`, `css`, `js` / `javascript`, `ts` / `typescript`, `json`, `md` / `markdown`, `sh` / `bash`, `xml`
24
+
25
+ ## Output
26
+
27
+ The script returns the **inner highlighted content only** — the hljs span markup without any `<pre><code>` wrapper. Paste it directly inside an existing `<pre><code class="hljs <lang>">...</code></pre>` block.
28
+
29
+ ## Example
30
+
31
+ ```bash
32
+ npm run highlightcode -- html "<p>Hello World</p>"
33
+ ```
34
+
35
+ Returns:
36
+ ```
37
+ <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
38
+ ```
39
+
40
+ ## Multi-line Code
41
+
42
+ For multi-line code, use a here-string in PowerShell:
43
+
44
+ ```powershell
45
+ $code = @"
46
+ class Foo extends Bar {
47
+ render(){ return html`<p>hello</p>`; }
48
+ }
49
+ "@
50
+ node bin/highlight_code.js js $code
51
+ ```
52
+
53
+ ## Workflow for Updating Doc Code Samples
54
+
55
+ 1. Run `npm run highlightcode -- <lang> "<new code>"` to get the highlighted HTML.
56
+ 2. Replace the inner content of the matching `<pre><code class="hljs ...">` block in the doc file with the output.
57
+ 3. Also update the live demo `<script type="module">` block with the raw (unhighlighted) code.
package/README.md CHANGED
@@ -22,7 +22,7 @@ npm install kempo-ui
22
22
  <script>
23
23
  window.kempo = {
24
24
  pathToStylesheet: '/kempo.min.css',
25
- pathToIcons: '/icons'
25
+ pathsToIcons: ['/icons']
26
26
  };
27
27
  </script>
28
28
  </head>
@@ -63,6 +63,88 @@ Import and use components in your HTML:
63
63
  ```
64
64
 
65
65
 
66
+ ## Icons
67
+
68
+ kempo-ui ships a small set of icons used internally by its components. The `<k-icon>` component can also serve icons from your own directory, so you can add project-specific icons alongside the built-in ones.
69
+
70
+ ### Interactive picker (for humans)
71
+
72
+ Run a single command, type to filter, arrow keys to navigate, Enter to select:
73
+
74
+ ```bash
75
+ npx kempo-icon
76
+ ```
77
+
78
+ ### Finding an icon (for scripts/LLMs)
79
+
80
+ Search the full Material Symbols library by name or tag:
81
+
82
+ ```bash
83
+ npx kempo-listicons <search_term>
84
+ ```
85
+
86
+ ```bash
87
+ npx kempo-listicons chevron
88
+ # → chevron_right
89
+ # chevron_left
90
+ # keyboard_arrow_right
91
+ # ...
92
+ ```
93
+
94
+ ### Downloading an icon
95
+
96
+ ```bash
97
+ npx kempo-geticon <icon_name> [custom_name] [--dir <path>] [-y]
98
+ ```
99
+
100
+ By default icons are saved to `icons/` in your project root. Use `--dir` to point to your own assets directory:
101
+
102
+ ```bash
103
+ # Save to icons/ (default)
104
+ npx kempo-geticon format_bold
105
+
106
+ # Save to a custom directory
107
+ npx kempo-geticon format_bold --dir src/assets/icons
108
+
109
+ # Rename on download
110
+ npx kempo-geticon content_copy copy
111
+
112
+ # Auto-accept directional icon prompt
113
+ npx kempo-geticon chevron_left -y
114
+ ```
115
+
116
+ **Directional icons:** Material Symbols has separate icons for each direction (`chevron_left`, `chevron_right`, etc.). kempo-ui only stores the right-facing version and uses the `direction` attribute to rotate it, so you only need one file per icon. If you pass a non-right directional name, the script will prompt you to download the right-facing variant instead and save it under the base name.
117
+
118
+ ### Configuring icon directories
119
+
120
+ Tell kempo-ui where to look for icons in your page config. It checks directories in order and uses the first match, so your icons override the built-in ones:
121
+
122
+ ```html
123
+ <script>
124
+ window.kempo = {
125
+ pathToStylesheet: '/kempo.min.css',
126
+ pathsToIcons: ['/my-icons', '/node_modules/kempo-ui/icons']
127
+ };
128
+ </script>
129
+ ```
130
+
131
+ ### Using icons in markup
132
+
133
+ ```html
134
+ <k-icon name="format_bold"></k-icon>
135
+ <k-icon name="chevron" direction="left"></k-icon>
136
+ <k-icon name="chevron" direction="down"></k-icon>
137
+ ```
138
+
139
+ | `direction` | Rotation |
140
+ |---|---|
141
+ | *(omitted)* | 0° — right |
142
+ | `down` | 90° |
143
+ | `left` | 180° |
144
+ | `up` | 270° |
145
+
146
+ ---
147
+
66
148
  ## Documentation
67
149
 
68
150
  **Full Documentation**: [https://dustinpoissant.github.io/kempo-ui/](https://dustinpoissant.github.io/kempo-ui/)
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { spawn } from 'child_process';
5
+ import readline from 'readline';
6
+
7
+ const CWD = process.cwd();
8
+ const CACHE_DIR = path.join(CWD, 'node_modules', '.cache', 'kempo-ui');
9
+
10
+ const yFlag = process.argv.includes('-y') || process.argv.includes('--yes') || process.env.npm_config_yes === 'true';
11
+ const rawArgs = process.argv.slice(2).filter(a => a !== '-y' && a !== '--yes');
12
+
13
+ let iconDir = 'icons';
14
+ const dirIdx = rawArgs.indexOf('--dir');
15
+ if(dirIdx !== -1){
16
+ iconDir = rawArgs[dirIdx + 1];
17
+ rawArgs.splice(dirIdx, 2);
18
+ }
19
+
20
+ const iconName = rawArgs[0];
21
+ const customName = rawArgs[1];
22
+
23
+ if(!iconName){
24
+ console.error('Usage: kempo-geticon <icon_name> [custom_name] [--dir <path>] [-y]');
25
+ console.error('Example: kempo-geticon format_bold');
26
+ console.error('Example: kempo-geticon content_copy copy');
27
+ console.error('Example: kempo-geticon chevron_left --dir src/assets/icons');
28
+ process.exit(1);
29
+ }
30
+
31
+ const RIGHT_SUFFIXES = ['_right', '_forward'];
32
+ const ALL_SUFFIXES = ['_right', '_forward', '_left', '_backward', '_up', '_down'];
33
+
34
+ const fetchSvg = async (name) => {
35
+ const res = await fetch(`https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/${name}/default/24px.svg`);
36
+ return res.ok ? res.text() : null;
37
+ };
38
+
39
+ const confirm = async (question) => {
40
+ if(yFlag) return true;
41
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
42
+ return new Promise(resolve => {
43
+ rl.question(question, answer => {
44
+ rl.close();
45
+ resolve(answer.trim().toLowerCase() !== 'n');
46
+ });
47
+ });
48
+ };
49
+
50
+ const dirSuffix = ALL_SUFFIXES.find(s => iconName.endsWith(s));
51
+ const baseName = dirSuffix ? iconName.slice(0, -dirSuffix.length) : iconName;
52
+ const fileName = customName || baseName;
53
+
54
+ let raw;
55
+
56
+ if(dirSuffix && !RIGHT_SUFFIXES.includes(dirSuffix)){
57
+ // Passed a non-right directional (e.g. chevron_left) — find right-facing equivalent
58
+ for(const rs of RIGHT_SUFFIXES){
59
+ raw = await fetchSvg(`${baseName}${rs}`);
60
+ if(raw) break;
61
+ }
62
+ if(!raw){
63
+ console.error(`Error: No right-facing variant found for "${baseName}"`);
64
+ process.exit(1);
65
+ }
66
+ const ok = await confirm(`"${iconName}" is a directional icon. kempo-ui uses right direction only and applies CSS rotation.\nSave right-facing variant as "${fileName}.svg"? (Y/n) `);
67
+ if(!ok) process.exit(0);
68
+ } else {
69
+ raw = await fetchSvg(iconName);
70
+ if(raw && dirSuffix){
71
+ // Passed chevron_right or chevron_forward explicitly
72
+ const ok = await confirm(`"${iconName}" is a directional icon. kempo-ui uses right direction only and applies CSS rotation.\nSave as "${fileName}.svg"? (Y/n) `);
73
+ if(!ok) process.exit(0);
74
+ } else if(!raw){
75
+ // No exact match — check if right-facing variants exist
76
+ let rightName;
77
+ for(const rs of RIGHT_SUFFIXES){
78
+ const candidate = `${iconName}${rs}`;
79
+ raw = await fetchSvg(candidate);
80
+ if(raw){ rightName = candidate; break; }
81
+ }
82
+ if(!raw){
83
+ console.error(`Error: "${iconName}" not found`);
84
+ process.exit(1);
85
+ }
86
+ const ok = await confirm(`"${iconName}" appears to be a directional icon (found "${rightName}"). kempo-ui uses right direction only and applies CSS rotation.\nSave as "${fileName}.svg"? (Y/n) `);
87
+ if(!ok) process.exit(0);
88
+ }
89
+ }
90
+
91
+ const viewBox = raw.match(/viewBox="([^"]+)"/)?.[1];
92
+ const paths = [...raw.matchAll(/<path[^>]*\sd="([^"]+)"/g)].map(m => m[1]);
93
+
94
+ if(!viewBox || !paths.length){
95
+ console.error('Error: Unexpected SVG structure');
96
+ process.exit(1);
97
+ }
98
+
99
+ const formatted = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">${paths.map(d => `<path fill="currentColor" d="${d}"/>`).join('')}</svg>\n`;
100
+ const outputDir = path.resolve(CWD, iconDir);
101
+
102
+ await fs.mkdir(outputDir, { recursive: true });
103
+ await fs.writeFile(path.join(outputDir, `${fileName}.svg`), formatted, 'utf8');
104
+ console.log(`✓ Saved ${iconDir}/${fileName}.svg`);
105
+
106
+ spawn('npm', ['run', 'build', '--if-present'], { stdio: 'inherit', shell: true, cwd: CWD });
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { highlight } from '../tools/highlight.js';
3
+
4
+ const LANG_ALIASES = {
5
+ js: 'javascript',
6
+ ts: 'typescript',
7
+ html: 'html',
8
+ css: 'css',
9
+ json: 'json',
10
+ md: 'markdown',
11
+ sh: 'bash',
12
+ bash: 'bash',
13
+ xml: 'xml',
14
+ javascript: 'javascript',
15
+ typescript: 'typescript',
16
+ markdown: 'markdown'
17
+ };
18
+
19
+ const args = process.argv.slice(2);
20
+
21
+ if(args.length < 2){
22
+ console.error('Usage: kempo-highlightcode <lang> <code>');
23
+ console.error('Example: kempo-highlightcode html "<p>Hello World</p>"');
24
+ process.exit(1);
25
+ }
26
+
27
+ const [langArg, ...rest] = args;
28
+ const lang = LANG_ALIASES[langArg.toLowerCase()];
29
+
30
+ if(!lang){
31
+ console.error(`Unknown language: "${langArg}"`);
32
+ console.error(`Supported: ${Object.keys(LANG_ALIASES).join(', ')}`);
33
+ process.exit(1);
34
+ }
35
+
36
+ const code = rest.join(' ');
37
+ const full = highlight(code, lang);
38
+ // Strip the <pre><code> wrapper — return just the inner highlighted content
39
+ process.stdout.write(full.replace(/^<pre><code class="hljs [^"]*">/, '').replace(/<\/code><\/pre>\n?$/, '') + '\n');
package/bin/icon.js ADDED
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { spawn } from 'child_process';
5
+ import { search, input, confirm } from '@inquirer/prompts';
6
+
7
+ const CWD = process.cwd();
8
+ const CACHE_DIR = path.join(CWD, 'node_modules', '.cache', 'kempo-ui');
9
+ const CACHE_FILE = path.join(CACHE_DIR, 'icons-cache.json');
10
+ const SYMBOLS_URL = 'https://api.github.com/repos/google/material-design-icons/contents/symbols';
11
+
12
+ const RIGHT_SUFFIXES = ['_right', '_forward'];
13
+ const ALL_SUFFIXES = ['_right', '_forward', '_left', '_backward', '_up', '_down'];
14
+
15
+ const fetchJson = async (url) => {
16
+ const response = await fetch(url, { headers: { 'Accept': 'application/vnd.github+json' } });
17
+ if(!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
18
+ return response.json();
19
+ };
20
+
21
+ const fetchSvg = async (name) => {
22
+ const res = await fetch(`https://fonts.gstatic.com/s/i/short-term/release/materialsymbolsoutlined/${name}/default/24px.svg`);
23
+ return res.ok ? res.text() : null;
24
+ };
25
+
26
+ let cache = null;
27
+
28
+ try {
29
+ cache = JSON.parse(await fs.readFile(CACHE_FILE, 'utf8'));
30
+ } catch {
31
+ // no cache yet
32
+ }
33
+
34
+ const currentSha = await (async () => {
35
+ const entries = await fetchJson(SYMBOLS_URL);
36
+ return entries.find(e => e.name === 'web')?.sha;
37
+ })();
38
+
39
+ if(cache?.sha !== currentSha){
40
+ process.stderr.write('Updating icon list cache...\n');
41
+ const [tree, metaRes] = await Promise.all([
42
+ fetchJson(`https://api.github.com/repos/google/material-design-icons/git/trees/${currentSha}`),
43
+ fetch('https://fonts.google.com/metadata/icons').then(r => r.text())
44
+ ]);
45
+ const icons = tree.tree.map(e => e.path);
46
+ const meta = JSON.parse(metaRes.replace(/^\)\]\}'\n/, ''));
47
+ const tagMap = {};
48
+ for(const icon of meta.icons) tagMap[icon.name] = icon.tags || [];
49
+ cache = { sha: currentSha, icons, tagMap };
50
+ await fs.mkdir(CACHE_DIR, { recursive: true });
51
+ await fs.writeFile(CACHE_FILE, JSON.stringify(cache), 'utf8');
52
+ }
53
+
54
+ const tagMap = cache.tagMap || {};
55
+
56
+ const matchesQuery = (name, query) => {
57
+ if(name.includes(query)) return true;
58
+ return (tagMap[name] || []).some(t => t.includes(query));
59
+ };
60
+
61
+ const iconName = await search({
62
+ message: 'Search for an icon',
63
+ source: (query) => {
64
+ if(!query) return [];
65
+ return cache.icons
66
+ .filter(name => matchesQuery(name, query))
67
+ .slice(0, 20)
68
+ .map(name => ({ name, value: name }));
69
+ }
70
+ });
71
+
72
+ const dirSuffix = ALL_SUFFIXES.find(s => iconName.endsWith(s));
73
+ const baseName = dirSuffix ? iconName.slice(0, -dirSuffix.length) : iconName;
74
+
75
+ const customName = await input({
76
+ message: 'Save as',
77
+ default: baseName
78
+ });
79
+
80
+ const fileName = customName || baseName;
81
+ let raw;
82
+
83
+ if(dirSuffix && !RIGHT_SUFFIXES.includes(dirSuffix)){
84
+ for(const rs of RIGHT_SUFFIXES){
85
+ raw = await fetchSvg(`${baseName}${rs}`);
86
+ if(raw) break;
87
+ }
88
+ if(!raw){
89
+ console.error(`Error: No right-facing variant found for "${baseName}"`);
90
+ process.exit(1);
91
+ }
92
+ const ok = await confirm({ message: `"${iconName}" is directional. kempo-ui stores right-facing only and uses CSS rotation. Download right-facing variant as "${fileName}.svg"?`, default: true });
93
+ if(!ok) process.exit(0);
94
+ } else {
95
+ raw = await fetchSvg(iconName);
96
+ if(raw && dirSuffix){
97
+ const ok = await confirm({ message: `"${iconName}" is directional. kempo-ui stores right-facing only and uses CSS rotation. Save as "${fileName}.svg"?`, default: true });
98
+ if(!ok) process.exit(0);
99
+ } else if(!raw){
100
+ let rightName;
101
+ for(const rs of RIGHT_SUFFIXES){
102
+ const candidate = `${iconName}${rs}`;
103
+ raw = await fetchSvg(candidate);
104
+ if(raw){ rightName = candidate; break; }
105
+ }
106
+ if(!raw){
107
+ console.error(`Error: "${iconName}" not found`);
108
+ process.exit(1);
109
+ }
110
+ const ok = await confirm({ message: `"${iconName}" appears directional (found "${rightName}"). kempo-ui stores right-facing only and uses CSS rotation. Save as "${fileName}.svg"?`, default: true });
111
+ if(!ok) process.exit(0);
112
+ }
113
+ }
114
+
115
+ const viewBox = raw.match(/viewBox="([^"]+)"/)?.[1];
116
+ const paths = [...raw.matchAll(/<path[^>]*\sd="([^"]+)"/g)].map(m => m[1]);
117
+
118
+ if(!viewBox || !paths.length){
119
+ console.error('Error: Unexpected SVG structure');
120
+ process.exit(1);
121
+ }
122
+
123
+ const iconDir = 'icons';
124
+ const formatted = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox}">${paths.map(d => `<path fill="currentColor" d="${d}"/>`).join('')}</svg>\n`;
125
+ const outputDir = path.resolve(CWD, iconDir);
126
+
127
+ await fs.mkdir(outputDir, { recursive: true });
128
+ await fs.writeFile(path.join(outputDir, `${fileName}.svg`), formatted, 'utf8');
129
+ console.log(`✓ Saved ${iconDir}/${fileName}.svg`);
130
+
131
+ spawn('npm', ['run', 'build', '--if-present'], { stdio: 'inherit', shell: true, cwd: CWD });
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ const CACHE_DIR = path.join(process.cwd(), 'node_modules', '.cache', 'kempo-ui');
6
+ const CACHE_FILE = path.join(CACHE_DIR, 'icons-cache.json');
7
+ const SYMBOLS_URL = 'https://api.github.com/repos/google/material-design-icons/contents/symbols';
8
+
9
+ const query = process.argv[2];
10
+
11
+ if(!query){
12
+ console.error('Usage: kempo-listicons <search_term>');
13
+ process.exit(1);
14
+ }
15
+
16
+ const fetchJson = async (url) => {
17
+ const response = await fetch(url, { headers: { 'Accept': 'application/vnd.github+json' } });
18
+ if(!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
19
+ return response.json();
20
+ };
21
+
22
+ const getCurrentSha = async () => {
23
+ const entries = await fetchJson(SYMBOLS_URL);
24
+ return entries.find(e => e.name === 'web')?.sha;
25
+ };
26
+
27
+ const fetchAllIcons = async (treeSha) => {
28
+ const [tree, metaRes] = await Promise.all([
29
+ fetchJson(`https://api.github.com/repos/google/material-design-icons/git/trees/${treeSha}`),
30
+ fetch('https://fonts.google.com/metadata/icons').then(r => r.text())
31
+ ]);
32
+ const icons = tree.tree.map(e => e.path);
33
+ const meta = JSON.parse(metaRes.replace(/^\)\]\}'\n/, ''));
34
+ const tagMap = {};
35
+ for(const icon of meta.icons) tagMap[icon.name] = icon.tags || [];
36
+ return { icons, tagMap };
37
+ };
38
+
39
+ let cache = null;
40
+
41
+ try {
42
+ cache = JSON.parse(await fs.readFile(CACHE_FILE, 'utf8'));
43
+ } catch {
44
+ // no cache yet
45
+ }
46
+
47
+ const currentSha = await getCurrentSha();
48
+
49
+ if(cache?.sha !== currentSha){
50
+ process.stderr.write('Cache outdated or missing, fetching icon list...\n');
51
+ const { icons, tagMap } = await fetchAllIcons(currentSha);
52
+ cache = { sha: currentSha, icons, tagMap };
53
+ await fs.mkdir(CACHE_DIR, { recursive: true });
54
+ await fs.writeFile(CACHE_FILE, JSON.stringify(cache), 'utf8');
55
+ }
56
+
57
+ const tagMap = cache.tagMap || {};
58
+
59
+ const matchesQuery = (name) => {
60
+ if(name.includes(query)) return true;
61
+ return (tagMap[name] || []).some(t => t.includes(query));
62
+ };
63
+
64
+ const results = cache.icons.filter(matchesQuery);
65
+
66
+ if(results.length === 0){
67
+ console.log(`No icons found matching "${query}"`);
68
+ } else {
69
+ console.log(results.join('\n'));
70
+ }
@@ -60,6 +60,7 @@ import{LitElement,html,css}from"../lit-all.min.js";import ShadowComponent from".
60
60
  }
61
61
  :host(:not([resizing=""])) #main {
62
62
  pointer-events: none;
63
+ user-select: none;
63
64
  }
64
65
  :host([resizing="side"]) #side,
65
66
  :host([resizing="corner"]) #side,
@@ -6,7 +6,7 @@ import ShadowComponent from"./ShadowComponent.js";import{html,css}from"../lit-al
6
6
  <div class="tree-root">
7
7
  ${e?e.map(([e,t])=>Tree.renderValue(t,e,1,this.depth)):Tree.renderValue(this.data,null,0,this.depth)}
8
8
  </div>
9
- `}static nodes=[];static addNode=(...e)=>{[...e].reverse().forEach(e=>{Object.prototype.hasOwnProperty.call(e,"nodeTag")||(e.nodeTag="k-tree-node-"+nodeTagCounter++,window.customElements.define(e.nodeTag,e)),Tree.nodes.unshift(e)})};static renderValue(e,t=null,s=0,n=0){const r=Tree.nodes.find(t=>t.detect(e));if(r){const s=new r(e),n=null!==t?html`<span class="${"number"==typeof t?"tc-muted":""}">${t}: </span>`:"";return html`<span class="d-b">${n}${s.renderLabel()}</span>`}const a=document.createElement(TreeNode.nodeTag);return a.value=e,a.key=t,a.depth=s,a.maxDepth=n,a}}window.customElements.define("k-tree",Tree);export class TreeNode extends ShadowComponent{static nodeTag="k-tree-node";static properties={value:{type:Object},key:{attribute:!1},depth:{type:Number},maxDepth:{type:Number},opened:{type:Boolean,converter:boolExists,reflect:!0},icon:{type:String}};constructor(e){super(),this.value=void 0!==e?e:null,this.key=null,this.depth=0,this.maxDepth=0,this.opened=!1,this.icon=null}connectedCallback(){super.connectedCallback(),this.depth<=this.maxDepth&&(this.opened=!0)}get tree(){return this.closest("k-tree")}toggle=()=>{this.opened=!this.opened};renderLabel(){return"object"==typeof this.value&&null!==this.value?html`${Array.isArray(this.value)?"Array":"Object"}`:html`${this.value}`}getChildren(){return"object"!=typeof this.value||null===this.value?null:Array.isArray(this.value)?this.value.map((e,t)=>[t,e]):Object.entries(this.value)}renderIcon(){return this.icon?html`<k-icon name="${this.icon}"></k-icon>`:html`<k-icon name="chevron" class="toggle-icon" direction="${this.opened?"down":"right"}"></k-icon>`}render(){const e=this.getChildren(),t=null!==this.key?html`<span class="${"number"==typeof this.key?"tc-muted":""}">${this.key}: </span>`:"";return e?html`
9
+ `}static nodes=[];static addNode=(...e)=>{[...e].reverse().forEach(e=>{Object.prototype.hasOwnProperty.call(e,"nodeTag")||(e.nodeTag="k-tree-node-"+nodeTagCounter++,window.customElements.define(e.nodeTag,e)),Tree.nodes.unshift(e)})};static renderValue(e,t=null,s=0,n=0){const r=Tree.nodes.find(t=>t.detect(e));if(r&&r.prototype.getChildren===TreeNode.prototype.getChildren){const s=null!==t?html`<span class="${"number"==typeof t?"tc-muted":""}">${t}: </span>`:"";return html`<span class="d-b">${s}${new r(e).renderLabel()}</span>`}const o=document.createElement((r??TreeNode).nodeTag);return o.value=e,o.key=t,o.depth=s,o.maxDepth=n,o}}window.customElements.define("k-tree",Tree);export class TreeNode extends ShadowComponent{static nodeTag="k-tree-node";static properties={value:{type:Object},key:{attribute:!1},depth:{type:Number},maxDepth:{type:Number},opened:{type:Boolean,converter:boolExists,reflect:!0},icon:{type:String}};constructor(e){super(),this.value=void 0!==e?e:null,this.key=null,this.depth=0,this.maxDepth=0,this.opened=!1,this.icon=null}connectedCallback(){super.connectedCallback(),this.depth<=this.maxDepth&&(this.opened=!0)}get tree(){return this.closest("k-tree")}toggle=()=>{this.opened=!this.opened};renderLabel(){return"object"==typeof this.value&&null!==this.value?html`${Array.isArray(this.value)?"Array":"Object"}`:html`${this.value}`}getChildren(){return"object"!=typeof this.value||null===this.value?null:Array.isArray(this.value)?this.value.map((e,t)=>[t,e]):Object.entries(this.value)}renderIcon(){return this.icon?html`<k-icon name="${this.icon}"></k-icon>`:html`<k-icon name="chevron" class="toggle-icon" direction="${this.opened?"down":"right"}"></k-icon>`}render(){const e=this.getChildren(),t=null!==this.key?html`<span class="${"number"==typeof this.key?"tc-muted":""}">${this.key}: </span>`:"";return e?html`
10
10
  <div>
11
11
  <button class="branch-label no-btn" @click=${this.toggle} aria-expanded="${this.opened}">
12
12
  ${this.renderIcon()}