kempo-ui 0.4.3 → 0.4.4

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 (96) hide show
  1. package/.claude/skills/get-icon/SKILL.md +144 -0
  2. package/.claude/skills/highlight-code/SKILL.md +62 -0
  3. package/.claude/skills/new-component/SKILL.md +362 -0
  4. package/.github/skills/get-icon/SKILL.md +4 -4
  5. package/.github/skills/highlight-code/SKILL.md +51 -20
  6. package/.github/skills/new-component/SKILL.md +183 -152
  7. package/AGENTS.md +2 -2
  8. package/dist/components/SpeechToText.js +60 -0
  9. package/dist/components/TextToSpeech.js +56 -0
  10. package/docs/components/accordion.html +8 -0
  11. package/docs/components/aside.html +8 -0
  12. package/docs/components/calendar.html +8 -0
  13. package/docs/components/card.html +8 -0
  14. package/docs/components/code-editor.html +8 -0
  15. package/docs/components/color-picker.html +8 -0
  16. package/docs/components/combobox.html +8 -0
  17. package/docs/components/content-slider.html +8 -0
  18. package/docs/components/context.html +8 -0
  19. package/docs/components/dialog.html +8 -0
  20. package/docs/components/dropdown.html +8 -0
  21. package/docs/components/filter-list.html +8 -0
  22. package/docs/components/focus-capture.html +8 -0
  23. package/docs/components/html-editor.html +8 -0
  24. package/docs/components/hybrid-component.html +8 -0
  25. package/docs/components/icon.html +8 -0
  26. package/docs/components/import.html +8 -0
  27. package/docs/components/light-component.html +8 -0
  28. package/docs/components/nav-spacer.html +8 -0
  29. package/docs/components/nav.html +8 -0
  30. package/docs/components/photo-viewer.html +8 -0
  31. package/docs/components/progress.html +8 -0
  32. package/docs/components/resize.html +8 -0
  33. package/docs/components/shadow-component.html +8 -0
  34. package/docs/components/show-more.html +8 -0
  35. package/docs/components/slider.html +8 -0
  36. package/docs/components/sortable.html +8 -0
  37. package/docs/components/speech-to-text.html +717 -0
  38. package/docs/components/spinner.html +8 -0
  39. package/docs/components/split.html +8 -0
  40. package/docs/components/table.html +8 -0
  41. package/docs/components/tableControls.html +8 -0
  42. package/docs/components/tableCustomFields.html +8 -0
  43. package/docs/components/tableFetchRecords.html +8 -0
  44. package/docs/components/tableFieldSortHide.html +8 -0
  45. package/docs/components/tablePagination.html +8 -0
  46. package/docs/components/tablePlaceholder.html +8 -0
  47. package/docs/components/tableRecordEditing.html +8 -0
  48. package/docs/components/tableRecordFiltering.html +8 -0
  49. package/docs/components/tableRecordHiding.html +8 -0
  50. package/docs/components/tableRecordSearching.html +8 -0
  51. package/docs/components/tableRecordSelection.html +8 -0
  52. package/docs/components/tableRowControls.html +8 -0
  53. package/docs/components/tableServerSync.html +8 -0
  54. package/docs/components/tableSorting.html +8 -0
  55. package/docs/components/tabs.html +8 -0
  56. package/docs/components/tags.html +8 -0
  57. package/docs/components/text-to-speech.html +668 -0
  58. package/docs/components/theme-select.html +8 -0
  59. package/docs/components/theme-switcher.html +8 -0
  60. package/docs/components/time.html +8 -0
  61. package/docs/components/timestamp.html +8 -0
  62. package/docs/components/toast.html +8 -0
  63. package/docs/components/toggle.html +8 -0
  64. package/docs/components/tree.html +8 -0
  65. package/docs/icons/mic.svg +1 -0
  66. package/docs/icons/record_voice_over.svg +1 -0
  67. package/docs/icons/stop.svg +1 -0
  68. package/docs/index.html +20 -0
  69. package/docs/src/components/SpeechToText.js +60 -0
  70. package/docs/src/components/TextToSpeech.js +56 -0
  71. package/docs/utils/context.html +8 -0
  72. package/docs/utils/cookie.html +8 -0
  73. package/docs/utils/debounce.html +8 -0
  74. package/docs/utils/drag.html +8 -0
  75. package/docs/utils/elevation.html +8 -0
  76. package/docs/utils/formatTimestamp.html +8 -0
  77. package/docs/utils/object.html +8 -0
  78. package/docs/utils/propConverters.html +8 -0
  79. package/docs/utils/string.html +8 -0
  80. package/docs/utils/theme.html +8 -0
  81. package/docs/utils/toTitleCase.html +8 -0
  82. package/docs/utils/type.html +8 -0
  83. package/docs/utils/wait.html +8 -0
  84. package/docs-src/components/speech-to-text.page.html +251 -0
  85. package/docs-src/components/text-to-speech.page.html +202 -0
  86. package/docs-src/index.page.html +12 -0
  87. package/docs-src/nav.fragment.html +8 -0
  88. package/icons/mic.svg +1 -0
  89. package/icons/record_voice_over.svg +1 -0
  90. package/icons/stop.svg +1 -0
  91. package/llms.txt +2 -0
  92. package/package.json +1 -1
  93. package/src/components/SpeechToText.js +252 -0
  94. package/src/components/TextToSpeech.js +194 -0
  95. package/tests/components/SpeechToText.browser-test.js +530 -0
  96. package/tests/components/TextToSpeech.browser-test.js +432 -0
@@ -0,0 +1,194 @@
1
+ import { html, css } from '../lit-all.min.js';
2
+ import ShadowComponent from './ShadowComponent.js';
3
+ import './Icon.js';
4
+
5
+ export default class TextToSpeech extends ShadowComponent {
6
+ static properties = {
7
+ text: { type: String, reflect: true },
8
+ voice: { type: String, reflect: true },
9
+ language: { type: String, reflect: true },
10
+ rate: { type: Number, reflect: true },
11
+ pitch: { type: Number, reflect: true },
12
+ volume: { type: Number, reflect: true },
13
+ disabled: { type: Boolean, reflect: true },
14
+ speaking: { type: Boolean, reflect: true }
15
+ };
16
+
17
+ #unsupported = false;
18
+ #utterance = null;
19
+
20
+ /*
21
+ Lifecycle Callbacks
22
+ */
23
+ constructor() {
24
+ super();
25
+ this.text = '';
26
+ this.voice = '';
27
+ this.language = '';
28
+ this.rate = 1;
29
+ this.pitch = 1;
30
+ this.volume = 1;
31
+ this.disabled = false;
32
+ this.speaking = false;
33
+ }
34
+
35
+ connectedCallback() {
36
+ super.connectedCallback();
37
+ if(typeof window === 'undefined' || !window.speechSynthesis || typeof window.SpeechSynthesisUtterance !== 'function'){
38
+ this.#unsupported = true;
39
+ }
40
+ }
41
+
42
+ disconnectedCallback() {
43
+ super.disconnectedCallback();
44
+ this.stop();
45
+ }
46
+
47
+ /*
48
+ Utility
49
+ */
50
+ get effectiveText() {
51
+ if(this.text) return this.text;
52
+ return this.textContent.trim();
53
+ }
54
+
55
+ resolveVoice = () => {
56
+ if(!this.voice || this.#unsupported) return null;
57
+ const voices = window.speechSynthesis.getVoices();
58
+ return voices.find(v => v.name === this.voice) || null;
59
+ };
60
+
61
+ /*
62
+ Public Methods
63
+ */
64
+ speak = (override) => {
65
+ if(this.disabled || this.#unsupported) return;
66
+ const text = (override !== undefined ? String(override) : this.effectiveText).trim();
67
+ if(!text) return;
68
+ // Cancel any in-flight speech (ours or otherwise) before queuing this one
69
+ window.speechSynthesis.cancel();
70
+ const utterance = new window.SpeechSynthesisUtterance(text);
71
+ utterance.rate = this.rate;
72
+ utterance.pitch = this.pitch;
73
+ utterance.volume = this.volume;
74
+ if(this.language) utterance.lang = this.language;
75
+ const voice = this.resolveVoice();
76
+ if(voice) utterance.voice = voice;
77
+ utterance.onstart = this.handleStart;
78
+ utterance.onend = this.handleEnd;
79
+ utterance.onerror = this.handleError;
80
+ this.#utterance = utterance;
81
+ window.speechSynthesis.speak(utterance);
82
+ };
83
+
84
+ stop = () => {
85
+ if(this.#unsupported) return;
86
+ window.speechSynthesis.cancel();
87
+ };
88
+
89
+ toggle = () => {
90
+ if(this.speaking) this.stop();
91
+ else this.speak();
92
+ };
93
+
94
+ /*
95
+ Event Handlers
96
+ */
97
+ handleStart = () => {
98
+ this.speaking = true;
99
+ this.dispatchEvent(new CustomEvent('start', { bubbles: true }));
100
+ };
101
+
102
+ handleEnd = () => {
103
+ this.speaking = false;
104
+ this.#utterance = null;
105
+ this.dispatchEvent(new CustomEvent('end', { bubbles: true }));
106
+ };
107
+
108
+ handleError = (e) => {
109
+ this.speaking = false;
110
+ this.#utterance = null;
111
+ this.dispatchEvent(new CustomEvent('error', {
112
+ detail: { error: e.error || 'unknown' },
113
+ bubbles: true
114
+ }));
115
+ };
116
+
117
+ handleClick = () => {
118
+ if(this.disabled) return;
119
+ this.toggle();
120
+ };
121
+
122
+ /*
123
+ Rendering
124
+ */
125
+ render() {
126
+ const supported = !this.#unsupported;
127
+ const label = !supported
128
+ ? 'Text-to-speech not supported in this browser'
129
+ : this.speaking ? 'Stop speaking' : 'Speak';
130
+ return html`
131
+ <button
132
+ type="button"
133
+ class="no-btn btn"
134
+ ?disabled=${this.disabled || !supported}
135
+ @click=${this.handleClick}
136
+ aria-label=${label}
137
+ title=${label}
138
+ >
139
+ <k-icon name=${this.speaking ? 'stop' : 'record_voice_over'}></k-icon>
140
+ </button>
141
+ <slot style="display:none"></slot>
142
+ `;
143
+ }
144
+
145
+ /*
146
+ Styles
147
+ */
148
+ static styles = css`
149
+ :host {
150
+ --btn_size: 2.5rem;
151
+ --btn_bg: var(--c_bg);
152
+ --btn_bg__speaking: var(--c_primary);
153
+ --btn_tc__speaking: white;
154
+ display: inline-block;
155
+ }
156
+ :host([disabled]) {
157
+ opacity: 0.5;
158
+ pointer-events: none;
159
+ }
160
+ .btn {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ width: var(--btn_size);
165
+ height: var(--btn_size);
166
+ padding: 0;
167
+ border: 1px solid var(--c_border);
168
+ border-radius: 50%;
169
+ background: var(--btn_bg);
170
+ color: var(--tc);
171
+ cursor: pointer;
172
+ transition: background var(--animation_ms), color var(--animation_ms), border-color var(--animation_ms);
173
+ }
174
+ .btn:hover:not(:disabled) {
175
+ background: var(--c_bg__alt);
176
+ }
177
+ .btn:focus-visible {
178
+ outline: none;
179
+ box-shadow: var(--focus_shadow);
180
+ }
181
+ :host([speaking]) .btn,
182
+ :host([speaking]) .btn:hover,
183
+ :host([speaking]) .btn:focus-visible {
184
+ background: var(--btn_bg__speaking);
185
+ color: var(--btn_tc__speaking);
186
+ border-color: transparent;
187
+ }
188
+ .btn:disabled {
189
+ cursor: not-allowed;
190
+ }
191
+ `;
192
+ }
193
+
194
+ customElements.define('k-text-to-speech', TextToSpeech);