tosijs-ui 1.4.8 → 1.4.9

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.
@@ -72,10 +72,10 @@ preview.append(tosiTable({
72
72
  }
73
73
  ```
74
74
  ```test
75
- const table = await new Promise(resolve => {
75
+ const table = await waitFor('tosi-table')
76
+ await new Promise(resolve => {
76
77
  const check = () => {
77
- const t = preview.querySelector('tosi-table')
78
- if (t && t.visibleRows.length > 0) return resolve(t)
78
+ if (table.visibleRows.length > 0) return resolve()
79
79
  setTimeout(check, 100)
80
80
  }
81
81
  check()
@@ -84,18 +84,25 @@ const table = await new Promise(resolve => {
84
84
  test('table renders with data', () => {
85
85
  expect(table.multiple).toBe(true)
86
86
  expect(table.visibleRows.length).toBeGreaterThan(0)
87
+ expect(table.array.length).toBeGreaterThan(0)
87
88
  })
88
89
 
89
- test('row selection works', () => {
90
- const rows = table.visibleRows
90
+ test('row selection via data model', () => {
91
+ const items = table.array
91
92
  table.deSelect()
92
- table.selectRow(rows[0])
93
- table.selectRow(rows[1])
93
+ table.selectRow(items[0])
94
+ table.selectRow(items[1])
95
+
96
+ // Data model reflects selection immediately
97
+ expect(items[0][table.selectedKey]).toBe(true)
98
+ expect(items[1][table.selectedKey]).toBe(true)
94
99
  expect(table.selectedRows.length).toBe(2)
95
- expect(table.querySelectorAll('.tr[aria-selected]').length).toBe(2)
100
+
101
+ // Deselect and verify data model
96
102
  table.deSelect()
97
103
  expect(table.selectedRows.length).toBe(0)
98
- expect(table.querySelectorAll('.tr[aria-selected]').length).toBe(0)
104
+ expect(items[0][table.selectedKey]).not.toBe(true)
105
+ expect(items[1][table.selectedKey]).not.toBe(true)
99
106
  })
100
107
  ```
101
108
 
@@ -709,7 +709,7 @@ export function createDocBrowser(options) {
709
709
  // Create a hidden iframe to run tests in background
710
710
  const testFrame = document.createElement('iframe');
711
711
  testFrame.style.cssText =
712
- 'position: fixed; left: -9999px; width: 800px; height: 600px; visibility: hidden;';
712
+ 'position: fixed; left: 0; top: 0; width: 800px; height: 600px; opacity: 0; pointer-events: none;';
713
713
  document.body.appendChild(testFrame);
714
714
  const currentFilename = String(app.currentDoc.filename);
715
715
  for (const doc of docsWithTests) {
@@ -750,8 +750,27 @@ export function createDocBrowser(options) {
750
750
  if (frameDoc) {
751
751
  frameDoc.body.innerHTML = '';
752
752
  frameDoc.body.appendChild(testContainer);
753
- // Wait for all live examples to finish rendering/testing
754
- await new Promise((resolve) => setTimeout(resolve, 500));
753
+ // Wait for all live examples with tests to finish (max 30s per page)
754
+ await new Promise((resolve) => {
755
+ const deadline = Date.now() + 30_000;
756
+ const checkDone = () => {
757
+ if (Date.now() > deadline) {
758
+ resolve();
759
+ return;
760
+ }
761
+ const examples = testContainer.querySelectorAll('tosi-example');
762
+ const withTests = [...examples].filter((ex) => ex.classList.contains('-has-tests'));
763
+ const running = withTests.filter((ex) => ex.classList.contains('-test-running'));
764
+ if (withTests.length > 0 && running.length === 0) {
765
+ resolve();
766
+ }
767
+ else {
768
+ setTimeout(checkDone, 100);
769
+ }
770
+ };
771
+ // Give initial render time to start
772
+ setTimeout(checkDone, 200);
773
+ });
755
774
  }
756
775
  markPageTested(doc.filename);
757
776
  }
@@ -0,0 +1,54 @@
1
+ import { Component, ElementCreator, elements } from 'tosijs';
2
+ export declare class TosiHeader extends Component {
3
+ static preferredTagName: string;
4
+ static shadowStyleSpec: {
5
+ ':host': {
6
+ display: string;
7
+ alignItems: string;
8
+ padding: string;
9
+ background: string;
10
+ lineHeight: string;
11
+ gap: string;
12
+ };
13
+ '::slotted(*)': {
14
+ display: string;
15
+ alignItems: string;
16
+ };
17
+ };
18
+ content: ({ slot }: typeof elements) => HTMLSlotElement[];
19
+ }
20
+ export declare const tosiHeader: ElementCreator<TosiHeader>;
21
+ export interface HeaderLinks {
22
+ github?: string;
23
+ npm?: string;
24
+ discord?: string;
25
+ blog?: string;
26
+ tosijs?: string;
27
+ [key: string]: string | undefined;
28
+ }
29
+ export declare class TosiHeaderLinks extends Component {
30
+ static preferredTagName: string;
31
+ static lightStyleSpec: {
32
+ ':host': {
33
+ display: string;
34
+ alignItems: string;
35
+ gap: string;
36
+ };
37
+ ':host a': {
38
+ color: string;
39
+ textDecoration: string;
40
+ display: string;
41
+ alignItems: string;
42
+ padding: string;
43
+ opacity: string;
44
+ cursor: string;
45
+ };
46
+ ':host a:hover': {
47
+ opacity: string;
48
+ };
49
+ };
50
+ links: HeaderLinks;
51
+ content: null;
52
+ render(): void;
53
+ }
54
+ export declare const tosiHeaderLinks: ElementCreator<TosiHeaderLinks>;
package/dist/header.js ADDED
@@ -0,0 +1,156 @@
1
+ /*#
2
+ # header
3
+
4
+ A simple flex header. Compose it with `elastic()` and `spacer()` from
5
+ `layout` to arrange content however you like.
6
+
7
+ ## Basic Header
8
+
9
+ ```html
10
+ <tosi-header>
11
+ <h2 class="header-part">My App</h2>
12
+ </tosi-header>
13
+ ```
14
+ ```css
15
+ .header-part {
16
+ color: white;
17
+ margin: 0;
18
+ line-height: 32px;
19
+ }
20
+ ```
21
+
22
+ ## Header with Menu
23
+
24
+ Use `elastic()` to push the menu to the right:
25
+
26
+ ```js
27
+ import { elements } from 'tosijs'
28
+ import { tosiHeader, tosiMenu, elastic, icons } from 'tosijs-ui'
29
+
30
+ const menu = tosiMenu({ class: 'menu-demo' }, icons.moreVertical())
31
+ menu.menuItems = [
32
+ { caption: 'About', tooltip: 'Learn more about this app', action() { alert('About!') } },
33
+ { caption: 'Settings', icon: 'settings', tooltip: 'Appearance options', menuItems: [
34
+ { caption: 'Dark Mode' },
35
+ { caption: 'High Contrast' },
36
+ ]},
37
+ ]
38
+
39
+ preview.append(
40
+ tosiHeader(
41
+ { class: 'menu-demo' },
42
+ elements.h2({ class: 'menu-demo' }, 'Demo'),
43
+ elastic(),
44
+ menu,
45
+ )
46
+ )
47
+ ```
48
+ ```css
49
+ .menu-demo * {
50
+ color: white;
51
+ margin: 0;
52
+ --text-color: white;
53
+ --button-bg: transparent;
54
+ }
55
+ .menu-demo button:hover {
56
+ background: #fff4;
57
+ }
58
+ ```
59
+
60
+ ## Header with Links
61
+
62
+ Use `elastic()` to push `<tosi-header-links>` to the right:
63
+
64
+ ```js
65
+ import { elements } from 'tosijs'
66
+ import { tosiHeader, tosiHeaderLinks, elastic } from 'tosijs-ui'
67
+
68
+ preview.append(
69
+ tosiHeader(
70
+ { class: 'links-demo' },
71
+ elements.h2({ class: 'links-demo' }, 'My Project'),
72
+ elastic(),
73
+ tosiHeaderLinks({
74
+ links: {
75
+ github: 'https://github.com/example/project',
76
+ npm: 'https://www.npmjs.com/package/example',
77
+ discord: 'https://discord.gg/example',
78
+ },
79
+ })
80
+ )
81
+ )
82
+ ```
83
+ ```css
84
+ .links-demo {
85
+ color: white;
86
+ margin: 0;
87
+ }
88
+ ```
89
+ */
90
+ import { Component, elements, varDefault } from 'tosijs';
91
+ import { icons } from './icons';
92
+ const { a } = elements;
93
+ // ============================================================================
94
+ // Header Component
95
+ // ============================================================================
96
+ export class TosiHeader extends Component {
97
+ static preferredTagName = 'tosi-header';
98
+ static shadowStyleSpec = {
99
+ ':host': {
100
+ display: 'flex',
101
+ alignItems: 'center',
102
+ padding: varDefault.tosiHeaderPadding('12px 24px'),
103
+ background: varDefault.tosiHeaderBg('var(--tosi-accent, #EE257B)'),
104
+ lineHeight: varDefault.tosiHeaderLineHeight('32px'),
105
+ gap: varDefault.tosiHeaderGap('8px'),
106
+ },
107
+ '::slotted(*)': {
108
+ display: 'inline-flex',
109
+ alignItems: 'center',
110
+ },
111
+ };
112
+ content = ({ slot }) => [slot()];
113
+ }
114
+ export const tosiHeader = TosiHeader.elementCreator();
115
+ const linkIcons = {
116
+ tosijs: () => icons.tosi(),
117
+ discord: () => icons.discord(),
118
+ blog: () => icons.blog(),
119
+ github: () => icons.github(),
120
+ npm: () => icons.npm(),
121
+ };
122
+ export class TosiHeaderLinks extends Component {
123
+ static preferredTagName = 'tosi-header-links';
124
+ static lightStyleSpec = {
125
+ ':host': {
126
+ display: 'inline-flex',
127
+ alignItems: 'center',
128
+ gap: '4px',
129
+ },
130
+ ':host a': {
131
+ color: 'inherit',
132
+ textDecoration: 'none',
133
+ display: 'inline-flex',
134
+ alignItems: 'center',
135
+ padding: '4px',
136
+ opacity: '0.8',
137
+ cursor: 'pointer',
138
+ },
139
+ ':host a:hover': {
140
+ opacity: '1',
141
+ },
142
+ };
143
+ links = {};
144
+ content = null;
145
+ render() {
146
+ super.render();
147
+ const fragment = [];
148
+ for (const [key, url] of Object.entries(this.links)) {
149
+ if (!url || !linkIcons[key])
150
+ continue;
151
+ fragment.push(a({ title: key, target: '_blank', href: url }, linkIcons[key]()));
152
+ }
153
+ this.replaceChildren(...fragment);
154
+ }
155
+ }
156
+ export const tosiHeaderLinks = TosiHeaderLinks.elementCreator();