even-toolkit 1.4.1 → 1.5.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/CHANGELOG.md +27 -0
- package/README.md +53 -5
- package/dist/glasses/action-bar.d.ts +3 -3
- package/dist/glasses/action-bar.js +7 -7
- package/dist/glasses/action-bar.js.map +1 -1
- package/dist/glasses/action-map.js +3 -3
- package/dist/glasses/action-map.js.map +1 -1
- package/dist/glasses/gestures.d.ts +4 -0
- package/dist/glasses/gestures.d.ts.map +1 -1
- package/dist/glasses/gestures.js +44 -0
- package/dist/glasses/gestures.js.map +1 -1
- package/dist/glasses/glass-chat-display.d.ts +43 -0
- package/dist/glasses/glass-chat-display.d.ts.map +1 -0
- package/dist/glasses/glass-chat-display.js +102 -0
- package/dist/glasses/glass-chat-display.js.map +1 -0
- package/dist/glasses/glass-format.d.ts +50 -0
- package/dist/glasses/glass-format.d.ts.map +1 -0
- package/dist/glasses/glass-format.js +65 -0
- package/dist/glasses/glass-format.js.map +1 -0
- package/dist/glasses/index.d.ts +2 -0
- package/dist/glasses/index.d.ts.map +1 -1
- package/dist/glasses/index.js +2 -0
- package/dist/glasses/index.js.map +1 -1
- package/dist/glasses/useGlasses.js +1 -1
- package/dist/glasses/useGlasses.js.map +1 -1
- package/dist/stt/providers/deepgram.d.ts +1 -0
- package/dist/stt/providers/deepgram.d.ts.map +1 -1
- package/dist/stt/providers/deepgram.js +25 -8
- package/dist/stt/providers/deepgram.js.map +1 -1
- package/dist/web/components/dialog.d.ts.map +1 -1
- package/dist/web/components/dialog.js +16 -1
- package/dist/web/components/dialog.js.map +1 -1
- package/dist/web/components/drawer-shell.d.ts +19 -0
- package/dist/web/components/drawer-shell.d.ts.map +1 -0
- package/dist/web/components/drawer-shell.js +59 -0
- package/dist/web/components/drawer-shell.js.map +1 -0
- package/dist/web/components/list-item.d.ts +1 -1
- package/dist/web/components/list-item.d.ts.map +1 -1
- package/dist/web/components/list-item.js +20 -5
- package/dist/web/components/list-item.js.map +1 -1
- package/dist/web/components/multi-select.d.ts +22 -0
- package/dist/web/components/multi-select.d.ts.map +1 -0
- package/dist/web/components/multi-select.js +52 -0
- package/dist/web/components/multi-select.js.map +1 -0
- package/dist/web/components/select.d.ts +13 -3
- package/dist/web/components/select.d.ts.map +1 -1
- package/dist/web/components/select.js +36 -3
- package/dist/web/components/select.js.map +1 -1
- package/dist/web/components/side-drawer.d.ts +43 -0
- package/dist/web/components/side-drawer.d.ts.map +1 -0
- package/dist/web/components/side-drawer.js +88 -0
- package/dist/web/components/side-drawer.js.map +1 -0
- package/dist/web/icons/svg-icons.js +1 -1
- package/dist/web/icons/svg-icons.js.map +1 -1
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +3 -0
- package/dist/web/index.js.map +1 -1
- package/glasses/action-bar.ts +7 -7
- package/glasses/action-map.ts +3 -3
- package/glasses/gestures.ts +50 -0
- package/glasses/glass-chat-display.ts +152 -0
- package/glasses/glass-format.ts +75 -0
- package/glasses/index.ts +2 -0
- package/glasses/useGlasses.ts +1 -1
- package/package.json +10 -1
- package/stt/providers/deepgram.ts +23 -7
- package/web/components/dialog.tsx +20 -1
- package/web/components/drawer-shell.tsx +145 -0
- package/web/components/list-item.tsx +25 -10
- package/web/components/multi-select.tsx +118 -0
- package/web/components/select.tsx +90 -20
- package/web/components/side-drawer.tsx +246 -0
- package/web/icons/svg-icons.tsx +1 -2
- package/web/index.ts +9 -0
- package/web/theme/utilities.css +11 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
Released: 2026-03-31
|
|
6
|
+
|
|
7
|
+
No breaking changes.
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- new glasses helpers `glass-format` and `glass-chat-display`
|
|
12
|
+
- new web `MultiSelect` component export
|
|
13
|
+
- README refresh to reflect the current published surface area
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- action bar hover/active triangle semantics now match the latest glasses UX
|
|
18
|
+
- glasses gesture handling is more reliable for settings editing and immediate scroll changes
|
|
19
|
+
- Deepgram STT shutdown noise is suppressed during intentional close
|
|
20
|
+
- dialog background scroll locking is stronger on web/touch devices
|
|
21
|
+
- swipe-to-delete rows now support loading feedback through the shared list item
|
|
22
|
+
- SVG navigate icon now inherits current color correctly for dark buttons
|
|
23
|
+
|
|
24
|
+
### Notes
|
|
25
|
+
|
|
26
|
+
- This release is backward compatible with existing 1.4.x imports.
|
|
27
|
+
- Consumers can upgrade without code changes unless they were depending on old visual bugs.
|
package/README.md
CHANGED
|
@@ -28,9 +28,9 @@ npx create-even-app my-app
|
|
|
28
28
|
import { Button, Card, NavBar, ListItem, Toggle, AppShell } from 'even-toolkit/web';
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
**Primitives:** Button, Card, Badge, Input, Textarea, Select, Checkbox, RadioGroup, Slider, InputGroup, Skeleton, Progress, StatusDot, Pill, Toggle, SegmentedControl, Table, Kbd, Divider
|
|
31
|
+
**Primitives:** Button, Card, Badge, Input, Textarea, Select, MultiSelect, Checkbox, RadioGroup, Slider, InputGroup, Skeleton, Progress, StatusDot, Pill, Toggle, SegmentedControl, Table, Kbd, Divider
|
|
32
32
|
|
|
33
|
-
**Layout:** AppShell, Page, NavBar, NavHeader, ScreenHeader, SectionHeader, SettingsGroup, CategoryFilter, ListItem (swipe-to-delete), SearchBar, Tag, TagCarousel, TagCard, SliderIndicator, PageIndicator, StepIndicator, Timeline, StatGrid, StatusProgress
|
|
33
|
+
**Layout:** AppShell, Page, NavBar, NavHeader, SideDrawer, DrawerShell, DrawerTrigger, ScreenHeader, SectionHeader, SettingsGroup, CategoryFilter, ListItem (swipe-to-delete), SearchBar, Tag, TagCarousel, TagCard, SliderIndicator, PageIndicator, StepIndicator, Timeline, StatGrid, StatusProgress
|
|
34
34
|
|
|
35
35
|
**Feedback:** TimerRing, Dialog, ConfirmDialog, Toast, EmptyState, Loading, BottomSheet, CTAGroup, ScrollPicker, DatePicker, TimePicker, SelectionPicker
|
|
36
36
|
|
|
@@ -206,14 +206,16 @@ import { line, separator, glassHeader } from 'even-toolkit/types';
|
|
|
206
206
|
import { buildActionBar, buildStaticActionBar } from 'even-toolkit/action-bar';
|
|
207
207
|
import { truncate, applyScrollIndicators } from 'even-toolkit/text-utils';
|
|
208
208
|
import { renderTimerLines } from 'even-toolkit/timer-display';
|
|
209
|
+
import { formatGlassHeader, formatGlassListRow } from 'even-toolkit/glass-format';
|
|
210
|
+
import { renderChatBlocks, renderChatReadMode } from 'even-toolkit/glass-chat-display';
|
|
209
211
|
import { createSplash, TILE_PRESETS } from 'even-toolkit/splash';
|
|
210
212
|
```
|
|
211
213
|
|
|
212
214
|
**Display:** 576x288px, 10 text lines, text/columns/chart/home page modes, image tiles (max 288x144)
|
|
213
215
|
|
|
214
|
-
**Input:** action-map (tap/double-tap/scroll events), gestures (debounce), keyboard bindings
|
|
216
|
+
**Input:** action-map (tap/double-tap/scroll events), gestures (debounce + post-tap scroll suppression), keyboard bindings
|
|
215
217
|
|
|
216
|
-
**Utilities:** splash screens, PNG encoding, text cleaning, pagination, keep-alive
|
|
218
|
+
**Utilities:** splash screens, PNG encoding, text cleaning, pagination, keep-alive, chat block formatters, reusable glass text formatting helpers
|
|
217
219
|
|
|
218
220
|
---
|
|
219
221
|
|
|
@@ -316,7 +318,53 @@ Light theme following Even Realities 2025 guidelines:
|
|
|
316
318
|
| Normal Subtitle | 13px | 400 | -0.13px |
|
|
317
319
|
| Normal Detail | 11px | 400 | -0.11px |
|
|
318
320
|
|
|
319
|
-
##
|
|
321
|
+
## Navigation Patterns
|
|
322
|
+
|
|
323
|
+
### DrawerShell (recommended)
|
|
324
|
+
|
|
325
|
+
Side drawer navigation with automatic hamburger/back-button detection, header context for nested screens, and `bottomItems` for pinned items like Settings.
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
import { DrawerShell, useDrawerHeader } from 'even-toolkit/web';
|
|
329
|
+
import type { SideDrawerItem } from 'even-toolkit/web';
|
|
330
|
+
|
|
331
|
+
// In your shell/layout:
|
|
332
|
+
const MENU_ITEMS: SideDrawerItem[] = [
|
|
333
|
+
{ id: '/', label: 'Home', section: 'App' },
|
|
334
|
+
];
|
|
335
|
+
const BOTTOM_ITEMS: SideDrawerItem[] = [
|
|
336
|
+
{ id: '/settings', label: 'Settings', section: 'App' },
|
|
337
|
+
];
|
|
338
|
+
|
|
339
|
+
function Shell() {
|
|
340
|
+
return (
|
|
341
|
+
<DrawerShell
|
|
342
|
+
items={MENU_ITEMS}
|
|
343
|
+
bottomItems={BOTTOM_ITEMS}
|
|
344
|
+
title="MyApp"
|
|
345
|
+
getPageTitle={(p) => p === '/' ? 'MyApp' : 'Page'}
|
|
346
|
+
deriveActiveId={(p) => p}
|
|
347
|
+
/>
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// In nested screens — customize the header:
|
|
352
|
+
function DetailScreen() {
|
|
353
|
+
useDrawerHeader({
|
|
354
|
+
title: 'Detail',
|
|
355
|
+
backTo: '/', // shows back button instead of hamburger
|
|
356
|
+
right: <Button size="sm">Save</Button>,
|
|
357
|
+
below: <Progress value={50} />, // below header (progress bars)
|
|
358
|
+
footer: <StepIndicator ... />, // fixed bottom area
|
|
359
|
+
hidden: true, // hide header entirely
|
|
360
|
+
});
|
|
361
|
+
return <div>...</div>;
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### NavBar + AppShell (tab bar)
|
|
366
|
+
|
|
367
|
+
Horizontal tab bar for simpler apps.
|
|
320
368
|
|
|
321
369
|
```tsx
|
|
322
370
|
import { AppShell, NavBar, ScreenHeader, Button, Card } from 'even-toolkit/web';
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Renders a row of named buttons with triangle indicators:
|
|
5
5
|
* ▶Timer◀ ▷Scroll◁ Steps
|
|
6
6
|
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - Selected button (current highlight): empty triangles ▷Name◁
|
|
8
|
+
* - Active button (entered mode, not highlighted): filled triangles ▶Name◀
|
|
9
9
|
* - Inactive button: plain Name
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function buildActionBar(buttons: string[], selectedIndex: number, activeLabel: string | null, _flashPhase?: boolean): string;
|
|
20
20
|
/**
|
|
21
|
-
* Build a static action bar (
|
|
21
|
+
* Build a static action bar (filled triangles on selected).
|
|
22
22
|
* Useful for screens like recipe detail or completion where there's no mode switching.
|
|
23
23
|
*/
|
|
24
24
|
export declare function buildStaticActionBar(buttons: string[], selectedIndex: number): string;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Renders a row of named buttons with triangle indicators:
|
|
5
5
|
* ▶Timer◀ ▷Scroll◁ Steps
|
|
6
6
|
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - Selected button (current highlight): empty triangles ▷Name◁
|
|
8
|
+
* - Active button (entered mode, not highlighted): filled triangles ▶Name◀
|
|
9
9
|
* - Inactive button: plain Name
|
|
10
10
|
*/
|
|
11
11
|
/**
|
|
@@ -20,24 +20,24 @@ export function buildActionBar(buttons, selectedIndex, activeLabel, _flashPhase)
|
|
|
20
20
|
const activeIdx = activeLabel ? buttons.indexOf(activeLabel) : -1;
|
|
21
21
|
return buttons.map((name, i) => {
|
|
22
22
|
if (activeIdx === i) {
|
|
23
|
-
// Active
|
|
23
|
+
// Active/confirmed button: filled triangles
|
|
24
24
|
return `\u25B6${name}\u25C0`;
|
|
25
25
|
}
|
|
26
|
-
if (
|
|
27
|
-
//
|
|
26
|
+
if (i === selectedIndex) {
|
|
27
|
+
// Scroll highlight on a non-active button: empty triangles
|
|
28
28
|
return `\u25B7${name}\u25C1`;
|
|
29
29
|
}
|
|
30
30
|
return ` ${name} `;
|
|
31
31
|
}).join(' ');
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Build a static action bar (
|
|
34
|
+
* Build a static action bar (filled triangles on selected).
|
|
35
35
|
* Useful for screens like recipe detail or completion where there's no mode switching.
|
|
36
36
|
*/
|
|
37
37
|
export function buildStaticActionBar(buttons, selectedIndex) {
|
|
38
38
|
return buttons.map((name, i) => {
|
|
39
39
|
if (i === selectedIndex) {
|
|
40
|
-
return `\
|
|
40
|
+
return `\u25B6${name}\u25C0`;
|
|
41
41
|
}
|
|
42
42
|
return ` ${name} `;
|
|
43
43
|
}).join(' ');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-bar.js","sourceRoot":"","sources":["../../glasses/action-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAiB,EACjB,aAAqB,EACrB,WAA0B,EAC1B,WAAqB;IAErB,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAElE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC7B,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,
|
|
1
|
+
{"version":3,"file":"action-bar.js","sourceRoot":"","sources":["../../glasses/action-bar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAiB,EACjB,aAAqB,EACrB,WAA0B,EAC1B,WAAqB;IAErB,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAElE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC7B,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;YACpB,4CAA4C;YAC5C,OAAO,SAAS,IAAI,QAAQ,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,KAAK,aAAa,EAAE,CAAC;YACxB,2DAA2D;YAC3D,OAAO,SAAS,IAAI,QAAQ,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,IAAI,GAAG,CAAC;IACrB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAiB,EACjB,aAAqB;IAErB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAC7B,IAAI,CAAC,KAAK,aAAa,EAAE,CAAC;YACxB,OAAO,SAAS,IAAI,QAAQ,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,IAAI,GAAG,CAAC;IACrB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OsEventTypeList } from '@evenrealities/even_hub_sdk';
|
|
2
|
-
import { tryConsumeTap,
|
|
2
|
+
import { tryConsumeTap, shouldIgnoreScroll } from './gestures';
|
|
3
3
|
export function mapGlassEvent(event) {
|
|
4
4
|
if (!event)
|
|
5
5
|
return null;
|
|
@@ -25,11 +25,11 @@ function mapEvent(event) {
|
|
|
25
25
|
return null;
|
|
26
26
|
return { type: 'GO_BACK' };
|
|
27
27
|
case OsEventTypeList.SCROLL_TOP_EVENT:
|
|
28
|
-
if (
|
|
28
|
+
if (shouldIgnoreScroll('prev'))
|
|
29
29
|
return null;
|
|
30
30
|
return { type: 'HIGHLIGHT_MOVE', direction: 'up' };
|
|
31
31
|
case OsEventTypeList.SCROLL_BOTTOM_EVENT:
|
|
32
|
-
if (
|
|
32
|
+
if (shouldIgnoreScroll('next'))
|
|
33
33
|
return null;
|
|
34
34
|
return { type: 'HIGHLIGHT_MOVE', direction: 'down' };
|
|
35
35
|
default:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-map.js","sourceRoot":"","sources":["../../glasses/action-map.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"action-map.js","sourceRoot":"","sources":["../../glasses/action-map.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAE/D,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC/C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC;QAChE,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACrB,OAAO,QAAQ,CAAC,EAAE,CAAC,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,KAA8D;IAC9E,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,EAAE,EAAE,CAAC;QACX,KAAK,eAAe,CAAC,WAAW;YAC9B,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;gBAAE,OAAO,IAAI,CAAC;YACvC,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;QACxC,KAAK,eAAe,CAAC,kBAAkB;YACrC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC1C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7B,KAAK,eAAe,CAAC,gBAAgB;YACnC,IAAI,kBAAkB,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;QACrD,KAAK,eAAe,CAAC,mBAAmB;YACtC,IAAI,kBAAkB,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC5C,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;QACvD;YACE,uDAAuD;YACvD,wEAAwE;YACxE,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,sBAAsB,IAAI,IAAI,IAAK,EAAa,KAAK,CAAC,CAAC,EAAE,CAAC;gBACjF,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;oBAAE,OAAO,IAAI,CAAC;gBACvC,OAAO,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC;YACxC,CAAC;YACD,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC"}
|
|
@@ -3,4 +3,8 @@ export declare function isScrollSuppressed(): boolean;
|
|
|
3
3
|
/** Call after every text container update to suppress spurious G2 scroll events */
|
|
4
4
|
export declare function notifyTextUpdate(): void;
|
|
5
5
|
export declare function isScrollDebounced(direction: 'prev' | 'next'): boolean;
|
|
6
|
+
/** Shared scroll gate so a single bypass applies to the full event. */
|
|
7
|
+
export declare function shouldIgnoreScroll(direction: 'prev' | 'next'): boolean;
|
|
8
|
+
/** Allow the next scroll event through immediately after a tap-driven mode change. */
|
|
9
|
+
export declare function armImmediateScroll(): void;
|
|
6
10
|
//# sourceMappingURL=gestures.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gestures.d.ts","sourceRoot":"","sources":["../../glasses/gestures.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"gestures.d.ts","sourceRoot":"","sources":["../../glasses/gestures.ts"],"names":[],"mappings":"AAqBA,wBAAgB,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,OAAO,CAiB7D;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAM5C;AAMD,mFAAmF;AACnF,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAoBrE;AAED,uEAAuE;AACvE,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAsBtE;AAED,sFAAsF;AACtF,wBAAgB,kBAAkB,IAAI,IAAI,CAIzC"}
|
package/dist/glasses/gestures.js
CHANGED
|
@@ -8,6 +8,14 @@ const DIRECTION_CHANGE_DEBOUNCE_MS = 50;
|
|
|
8
8
|
const SCROLL_SUPPRESS_AFTER_TEXT_MS = 80;
|
|
9
9
|
let lastTapTime = 0;
|
|
10
10
|
let lastTapKind = null;
|
|
11
|
+
let bypassNextScrollChecks = false;
|
|
12
|
+
function consumeExternalScrollBypass() {
|
|
13
|
+
const g = globalThis;
|
|
14
|
+
if (!g.__evenAllowImmediateScrollOnce)
|
|
15
|
+
return false;
|
|
16
|
+
g.__evenAllowImmediateScrollOnce = false;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
11
19
|
export function tryConsumeTap(kind) {
|
|
12
20
|
const now = Date.now();
|
|
13
21
|
const elapsed = now - lastTapTime;
|
|
@@ -23,6 +31,10 @@ export function tryConsumeTap(kind) {
|
|
|
23
31
|
return true;
|
|
24
32
|
}
|
|
25
33
|
export function isScrollSuppressed() {
|
|
34
|
+
if (bypassNextScrollChecks || consumeExternalScrollBypass()) {
|
|
35
|
+
bypassNextScrollChecks = false;
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
26
38
|
return Date.now() - lastTapTime < SCROLL_SUPPRESS_AFTER_TAP_MS;
|
|
27
39
|
}
|
|
28
40
|
let lastScrollTime = 0;
|
|
@@ -34,6 +46,11 @@ export function notifyTextUpdate() {
|
|
|
34
46
|
}
|
|
35
47
|
export function isScrollDebounced(direction) {
|
|
36
48
|
const now = Date.now();
|
|
49
|
+
if (bypassNextScrollChecks || consumeExternalScrollBypass()) {
|
|
50
|
+
lastScrollTime = now;
|
|
51
|
+
lastScrollDir = direction;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
37
54
|
// Suppress scrolls briefly after a text update (G2 re-layout fires spurious events)
|
|
38
55
|
if (now - textUpdateTime < SCROLL_SUPPRESS_AFTER_TEXT_MS)
|
|
39
56
|
return true;
|
|
@@ -44,4 +61,31 @@ export function isScrollDebounced(direction) {
|
|
|
44
61
|
lastScrollDir = direction;
|
|
45
62
|
return false;
|
|
46
63
|
}
|
|
64
|
+
/** Shared scroll gate so a single bypass applies to the full event. */
|
|
65
|
+
export function shouldIgnoreScroll(direction) {
|
|
66
|
+
const now = Date.now();
|
|
67
|
+
const bypassed = bypassNextScrollChecks || consumeExternalScrollBypass();
|
|
68
|
+
if (bypassed) {
|
|
69
|
+
bypassNextScrollChecks = false;
|
|
70
|
+
lastScrollTime = now;
|
|
71
|
+
lastScrollDir = direction;
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (now - lastTapTime < SCROLL_SUPPRESS_AFTER_TAP_MS)
|
|
75
|
+
return true;
|
|
76
|
+
if (now - textUpdateTime < SCROLL_SUPPRESS_AFTER_TEXT_MS)
|
|
77
|
+
return true;
|
|
78
|
+
const threshold = direction === lastScrollDir ? SAME_DIRECTION_DEBOUNCE_MS : DIRECTION_CHANGE_DEBOUNCE_MS;
|
|
79
|
+
if (now - lastScrollTime < threshold)
|
|
80
|
+
return true;
|
|
81
|
+
lastScrollTime = now;
|
|
82
|
+
lastScrollDir = direction;
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/** Allow the next scroll event through immediately after a tap-driven mode change. */
|
|
86
|
+
export function armImmediateScroll() {
|
|
87
|
+
bypassNextScrollChecks = true;
|
|
88
|
+
lastScrollTime = 0;
|
|
89
|
+
lastScrollDir = null;
|
|
90
|
+
}
|
|
47
91
|
//# sourceMappingURL=gestures.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gestures.js","sourceRoot":"","sources":["../../glasses/gestures.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,GAAG,CAAC;AAC5B,MAAM,yBAAyB,GAAG,EAAE,CAAC;AACrC,MAAM,gCAAgC,GAAG,GAAG,CAAC;AAC7C,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAEzC,uEAAuE;AACvE,MAAM,0BAA0B,GAAG,GAAG,CAAC;AACvC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AACxC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AAEzC,IAAI,WAAW,GAAG,CAAC,CAAC;AACpB,IAAI,WAAW,GAA4B,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"gestures.js","sourceRoot":"","sources":["../../glasses/gestures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat/terminal output display builder for G2 glasses.
|
|
3
|
+
* Optimized for reading streaming AI output on a 10-line text display.
|
|
4
|
+
*
|
|
5
|
+
* Line type prefixes:
|
|
6
|
+
* > user prompt
|
|
7
|
+
* >> tool call
|
|
8
|
+
* + collapsed thinking
|
|
9
|
+
* - expanded thinking header
|
|
10
|
+
* ! error
|
|
11
|
+
* (no prefix) assistant text
|
|
12
|
+
*/
|
|
13
|
+
import type { DisplayData } from './types';
|
|
14
|
+
export interface ChatLine {
|
|
15
|
+
type: 'prompt' | 'text' | 'tool' | 'thinking-collapsed' | 'thinking-expanded' | 'thinking-body' | 'error' | 'system';
|
|
16
|
+
text: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Format a single ChatLine into one or more display strings,
|
|
20
|
+
* word-wrapping at maxChars.
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatChatLine(chatLine: ChatLine, maxChars?: number): string[];
|
|
23
|
+
export interface ChatDisplayOptions {
|
|
24
|
+
/** Header title, e.g. "CLAUDE · opus · running" */
|
|
25
|
+
title: string;
|
|
26
|
+
/** Action bar string from buildStaticActionBar() */
|
|
27
|
+
actionBar: string;
|
|
28
|
+
/** Ordered chat lines to display */
|
|
29
|
+
chatLines: ChatLine[];
|
|
30
|
+
/** Scroll offset from bottom. 0 = show latest. Positive = scrolled up. */
|
|
31
|
+
scrollOffset: number;
|
|
32
|
+
/** Number of visible content lines. Default: 7 (10 total - 3 header) */
|
|
33
|
+
contentSlots?: number;
|
|
34
|
+
/** Max chars per line. Default: 44 */
|
|
35
|
+
maxChars?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build a complete chat display for G2 glasses.
|
|
39
|
+
* Auto-scrolls to bottom (latest content) unless scrollOffset > 0.
|
|
40
|
+
* Returns DisplayData with header + scrollable content.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildChatDisplay(opts: ChatDisplayOptions): DisplayData;
|
|
43
|
+
//# sourceMappingURL=glass-chat-display.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glass-chat-display.d.ts","sourceRoot":"","sources":["../../glasses/glass-chat-display.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAIxD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,oBAAoB,GAAG,mBAAmB,GAAG,eAAe,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrH,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,SAAK,GAAG,MAAM,EAAE,CA6D1E;AAED,MAAM,WAAW,kBAAkB;IACjC,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,kBAAkB,GAAG,WAAW,CA0CtE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat/terminal output display builder for G2 glasses.
|
|
3
|
+
* Optimized for reading streaming AI output on a 10-line text display.
|
|
4
|
+
*
|
|
5
|
+
* Line type prefixes:
|
|
6
|
+
* > user prompt
|
|
7
|
+
* >> tool call
|
|
8
|
+
* + collapsed thinking
|
|
9
|
+
* - expanded thinking header
|
|
10
|
+
* ! error
|
|
11
|
+
* (no prefix) assistant text
|
|
12
|
+
*/
|
|
13
|
+
import { line, glassHeader } from './types';
|
|
14
|
+
import { applyScrollIndicators } from './text-utils';
|
|
15
|
+
/**
|
|
16
|
+
* Format a single ChatLine into one or more display strings,
|
|
17
|
+
* word-wrapping at maxChars.
|
|
18
|
+
*/
|
|
19
|
+
export function formatChatLine(chatLine, maxChars = 44) {
|
|
20
|
+
const { type, text } = chatLine;
|
|
21
|
+
let prefix;
|
|
22
|
+
switch (type) {
|
|
23
|
+
case 'prompt':
|
|
24
|
+
prefix = '> ';
|
|
25
|
+
break;
|
|
26
|
+
case 'tool':
|
|
27
|
+
prefix = '>> ';
|
|
28
|
+
break;
|
|
29
|
+
case 'thinking-collapsed':
|
|
30
|
+
prefix = '+ ';
|
|
31
|
+
break;
|
|
32
|
+
case 'thinking-expanded':
|
|
33
|
+
prefix = '- ';
|
|
34
|
+
break;
|
|
35
|
+
case 'thinking-body':
|
|
36
|
+
prefix = ' ';
|
|
37
|
+
break;
|
|
38
|
+
case 'error':
|
|
39
|
+
prefix = '! ';
|
|
40
|
+
break;
|
|
41
|
+
case 'system':
|
|
42
|
+
prefix = '= ';
|
|
43
|
+
break;
|
|
44
|
+
case 'text':
|
|
45
|
+
default:
|
|
46
|
+
prefix = '';
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
const available = maxChars - prefix.length;
|
|
50
|
+
if (text.length <= available) {
|
|
51
|
+
return [`${prefix}${text}`];
|
|
52
|
+
}
|
|
53
|
+
// Word-wrap: try to break at word boundaries
|
|
54
|
+
const lines = [];
|
|
55
|
+
let remaining = text;
|
|
56
|
+
let isFirst = true;
|
|
57
|
+
while (remaining.length > 0) {
|
|
58
|
+
const pfx = isFirst ? prefix : ' '.repeat(prefix.length);
|
|
59
|
+
const avail = maxChars - pfx.length;
|
|
60
|
+
if (remaining.length <= avail) {
|
|
61
|
+
lines.push(`${pfx}${remaining}`);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
// Find last space within available width
|
|
65
|
+
let breakAt = remaining.lastIndexOf(' ', avail);
|
|
66
|
+
if (breakAt <= 0)
|
|
67
|
+
breakAt = avail; // no space found, hard break
|
|
68
|
+
lines.push(`${pfx}${remaining.slice(0, breakAt)}`);
|
|
69
|
+
remaining = remaining.slice(breakAt).trimStart();
|
|
70
|
+
isFirst = false;
|
|
71
|
+
}
|
|
72
|
+
return lines;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build a complete chat display for G2 glasses.
|
|
76
|
+
* Auto-scrolls to bottom (latest content) unless scrollOffset > 0.
|
|
77
|
+
* Returns DisplayData with header + scrollable content.
|
|
78
|
+
*/
|
|
79
|
+
export function buildChatDisplay(opts) {
|
|
80
|
+
const { title, actionBar, chatLines, scrollOffset, contentSlots = 7, maxChars = 44, } = opts;
|
|
81
|
+
const lines = [...glassHeader(title, actionBar)];
|
|
82
|
+
// Format all chat lines into display strings
|
|
83
|
+
const allLines = [];
|
|
84
|
+
for (const cl of chatLines) {
|
|
85
|
+
allLines.push(...formatChatLine(cl, maxChars));
|
|
86
|
+
}
|
|
87
|
+
if (allLines.length === 0) {
|
|
88
|
+
lines.push(line(' Waiting for output...', 'meta'));
|
|
89
|
+
return { lines };
|
|
90
|
+
}
|
|
91
|
+
// Scroll from bottom: offset 0 = show last contentSlots lines
|
|
92
|
+
const totalLines = allLines.length;
|
|
93
|
+
const maxFromBottom = Math.max(0, totalLines - contentSlots);
|
|
94
|
+
const clampedOffset = Math.min(scrollOffset, maxFromBottom);
|
|
95
|
+
const start = Math.max(0, totalLines - contentSlots - clampedOffset);
|
|
96
|
+
const visible = allLines.slice(start, start + contentSlots);
|
|
97
|
+
const contentDisplayLines = visible.map((t) => line(t, 'normal'));
|
|
98
|
+
applyScrollIndicators(contentDisplayLines, start, totalLines, contentSlots, (t) => line(t, 'meta'));
|
|
99
|
+
lines.push(...contentDisplayLines);
|
|
100
|
+
return { lines };
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=glass-chat-display.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glass-chat-display.js","sourceRoot":"","sources":["../../glasses/glass-chat-display.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glass display formatting constants and helpers.
|
|
3
|
+
* Safe Unicode characters confirmed working on G2 LVGL display.
|
|
4
|
+
* Inspired by tesla-even-g2 formatting patterns.
|
|
5
|
+
*/
|
|
6
|
+
/** Middle dot · field separator */
|
|
7
|
+
export declare const SEP = "\u00B7";
|
|
8
|
+
/** Single right-pointing angle › drill-in indicator */
|
|
9
|
+
export declare const DRILL = "\u203A";
|
|
10
|
+
/** Single left-pointing angle ‹ back prefix */
|
|
11
|
+
export declare const BACK_CHAR = "\u2039";
|
|
12
|
+
/** En-dash – result separator */
|
|
13
|
+
export declare const DASH = "\u2013";
|
|
14
|
+
/** Heavy horizontal ═ filled progress bar segment */
|
|
15
|
+
export declare const BAR_FILL = "\u2501";
|
|
16
|
+
/** Light horizontal ─ empty progress bar segment */
|
|
17
|
+
export declare const BAR_EMPTY = "\u2500";
|
|
18
|
+
/**
|
|
19
|
+
* Join non-empty values with · separator.
|
|
20
|
+
* Falsy values are filtered out.
|
|
21
|
+
*
|
|
22
|
+
* @example fieldJoin('CLAUDE', 'opus', 'running') → "CLAUDE · opus · running"
|
|
23
|
+
* @example fieldJoin('HOSTS', false, '2 total') → "HOSTS · 2 total"
|
|
24
|
+
*/
|
|
25
|
+
export declare function fieldJoin(...parts: (string | undefined | null | false)[]): string;
|
|
26
|
+
/**
|
|
27
|
+
* Render an ASCII progress bar.
|
|
28
|
+
* Uses ═ for filled and ─ for empty.
|
|
29
|
+
*
|
|
30
|
+
* @example progressBar(67) → "═══════───"
|
|
31
|
+
* @example progressBar(30, 20) → "══════──────────────"
|
|
32
|
+
*/
|
|
33
|
+
export declare function progressBar(percent: number, width?: number): string;
|
|
34
|
+
/**
|
|
35
|
+
* Format a key · value pair, truncating value if needed.
|
|
36
|
+
*
|
|
37
|
+
* @example kvLine('Language', 'EN') → "Language · EN"
|
|
38
|
+
*/
|
|
39
|
+
export declare function kvLine(label: string, value: string, maxWidth?: number): string;
|
|
40
|
+
/**
|
|
41
|
+
* Append › drill-in indicator to a label.
|
|
42
|
+
*
|
|
43
|
+
* @example drillLabel('Sessions') → "Sessions ›"
|
|
44
|
+
*/
|
|
45
|
+
export declare function drillLabel(text: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Build a "‹ Back" label for menu items.
|
|
48
|
+
*/
|
|
49
|
+
export declare function backLabel(): string;
|
|
50
|
+
//# sourceMappingURL=glass-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glass-format.d.ts","sourceRoot":"","sources":["../../glasses/glass-format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,mCAAmC;AACnC,eAAO,MAAM,GAAG,WAAW,CAAC;AAE5B,uDAAuD;AACvD,eAAO,MAAM,KAAK,WAAW,CAAC;AAE9B,+CAA+C;AAC/C,eAAO,MAAM,SAAS,WAAW,CAAC;AAElC,iCAAiC;AACjC,eAAO,MAAM,IAAI,WAAW,CAAC;AAE7B,qDAAqD;AACrD,eAAO,MAAM,QAAQ,WAAW,CAAC;AAEjC,oDAAoD;AACpD,eAAO,MAAM,SAAS,WAAW,CAAC;AAElC;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,KAAK,EAAE,CAAC,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,CAAC,EAAE,GAAG,MAAM,CAEjF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAI/D;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,SAAK,GAAG,MAAM,CAK1E;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAElC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glass display formatting constants and helpers.
|
|
3
|
+
* Safe Unicode characters confirmed working on G2 LVGL display.
|
|
4
|
+
* Inspired by tesla-even-g2 formatting patterns.
|
|
5
|
+
*/
|
|
6
|
+
/** Middle dot · field separator */
|
|
7
|
+
export const SEP = '\u00B7';
|
|
8
|
+
/** Single right-pointing angle › drill-in indicator */
|
|
9
|
+
export const DRILL = '\u203A';
|
|
10
|
+
/** Single left-pointing angle ‹ back prefix */
|
|
11
|
+
export const BACK_CHAR = '\u2039';
|
|
12
|
+
/** En-dash – result separator */
|
|
13
|
+
export const DASH = '\u2013';
|
|
14
|
+
/** Heavy horizontal ═ filled progress bar segment */
|
|
15
|
+
export const BAR_FILL = '\u2501';
|
|
16
|
+
/** Light horizontal ─ empty progress bar segment */
|
|
17
|
+
export const BAR_EMPTY = '\u2500';
|
|
18
|
+
/**
|
|
19
|
+
* Join non-empty values with · separator.
|
|
20
|
+
* Falsy values are filtered out.
|
|
21
|
+
*
|
|
22
|
+
* @example fieldJoin('CLAUDE', 'opus', 'running') → "CLAUDE · opus · running"
|
|
23
|
+
* @example fieldJoin('HOSTS', false, '2 total') → "HOSTS · 2 total"
|
|
24
|
+
*/
|
|
25
|
+
export function fieldJoin(...parts) {
|
|
26
|
+
return parts.filter(Boolean).join(` ${SEP} `);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Render an ASCII progress bar.
|
|
30
|
+
* Uses ═ for filled and ─ for empty.
|
|
31
|
+
*
|
|
32
|
+
* @example progressBar(67) → "═══════───"
|
|
33
|
+
* @example progressBar(30, 20) → "══════──────────────"
|
|
34
|
+
*/
|
|
35
|
+
export function progressBar(percent, width = 10) {
|
|
36
|
+
const clamped = Math.max(0, Math.min(100, percent));
|
|
37
|
+
const filled = Math.round((clamped / 100) * width);
|
|
38
|
+
return BAR_FILL.repeat(filled) + BAR_EMPTY.repeat(width - filled);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format a key · value pair, truncating value if needed.
|
|
42
|
+
*
|
|
43
|
+
* @example kvLine('Language', 'EN') → "Language · EN"
|
|
44
|
+
*/
|
|
45
|
+
export function kvLine(label, value, maxWidth = 44) {
|
|
46
|
+
const sep = ` ${SEP} `;
|
|
47
|
+
const available = maxWidth - label.length - sep.length;
|
|
48
|
+
const val = value.length > available ? value.slice(0, available - 1) + '~' : value;
|
|
49
|
+
return `${label}${sep}${val}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Append › drill-in indicator to a label.
|
|
53
|
+
*
|
|
54
|
+
* @example drillLabel('Sessions') → "Sessions ›"
|
|
55
|
+
*/
|
|
56
|
+
export function drillLabel(text) {
|
|
57
|
+
return `${text} ${DRILL}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build a "‹ Back" label for menu items.
|
|
61
|
+
*/
|
|
62
|
+
export function backLabel() {
|
|
63
|
+
return `${BACK_CHAR} Back`;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=glass-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glass-format.js","sourceRoot":"","sources":["../../glasses/glass-format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,mCAAmC;AACnC,MAAM,CAAC,MAAM,GAAG,GAAG,QAAQ,CAAC;AAE5B,uDAAuD;AACvD,MAAM,CAAC,MAAM,KAAK,GAAG,QAAQ,CAAC;AAE9B,+CAA+C;AAC/C,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC;AAElC,iCAAiC;AACjC,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC;AAE7B,qDAAqD;AACrD,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AAEjC,oDAAoD;AACpD,MAAM,CAAC,MAAM,SAAS,GAAG,QAAQ,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAG,KAA4C;IACvE,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,KAAK,GAAG,EAAE;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IACnD,OAAO,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,CAAC;AACpE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,KAAa,EAAE,QAAQ,GAAG,EAAE;IAChE,MAAM,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;IACvB,MAAM,SAAS,GAAG,QAAQ,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC;IACvD,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;IACnF,OAAO,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS;IACvB,OAAO,GAAG,SAAS,OAAO,CAAC;AAC7B,CAAC"}
|
package/dist/glasses/index.d.ts
CHANGED
|
@@ -10,4 +10,6 @@ export * from './glass-display-builders';
|
|
|
10
10
|
export * from './glass-mode';
|
|
11
11
|
export * from './glass-router';
|
|
12
12
|
export * from './glass-screen-router';
|
|
13
|
+
export * from './glass-format';
|
|
14
|
+
export * from './glass-chat-display';
|
|
13
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../glasses/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../glasses/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
|
package/dist/glasses/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../glasses/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,4FAA4F;AAE5F,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../glasses/index.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,4FAA4F;AAE5F,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC"}
|
|
@@ -41,7 +41,7 @@ export function useGlasses(config) {
|
|
|
41
41
|
const data = configRef.current.toDisplayData(snapshot, nav);
|
|
42
42
|
const text = data.lines.map(l => {
|
|
43
43
|
if (l.style === 'separator')
|
|
44
|
-
return '\u2500'.repeat(28)
|
|
44
|
+
return '\u2500'.repeat(28);
|
|
45
45
|
if (l.inverted)
|
|
46
46
|
return `\u25B6 ${l.text}`;
|
|
47
47
|
return ` ${l.text}`;
|