@ytspar/devbar 0.0.1 → 1.0.0-canary.e1f65d0
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/LICENSE +21 -0
- package/README.md +168 -28
- package/dist/GlobalDevBar.d.ts +201 -0
- package/dist/GlobalDevBar.js +1979 -0
- package/dist/constants.d.ts +202 -0
- package/dist/constants.js +535 -0
- package/dist/earlyConsoleCapture.d.ts +34 -0
- package/dist/earlyConsoleCapture.js +77 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +13 -0
- package/dist/outline.d.ts +14 -0
- package/dist/outline.js +215 -0
- package/dist/schema.d.ts +14 -0
- package/dist/schema.js +113 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.js +8 -0
- package/dist/ui/buttons.d.ts +21 -0
- package/dist/ui/buttons.js +55 -0
- package/dist/ui/icons.d.ts +13 -0
- package/dist/ui/icons.js +25 -0
- package/dist/ui/index.d.ts +8 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/modals.d.ts +40 -0
- package/dist/ui/modals.js +144 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +13 -0
- package/package.json +58 -6
package/dist/outline.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document Outline Extraction
|
|
3
|
+
*
|
|
4
|
+
* Functions for extracting and formatting the semantic document outline.
|
|
5
|
+
*/
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Semantic Element Sets
|
|
8
|
+
// ============================================================================
|
|
9
|
+
const semanticElements = new Set([
|
|
10
|
+
'article', 'aside', 'nav', 'section',
|
|
11
|
+
'main', 'body',
|
|
12
|
+
'header', 'footer', 'figure', 'figcaption',
|
|
13
|
+
'details', 'summary', 'dialog', 'address', 'hgroup',
|
|
14
|
+
'form', 'fieldset', 'legend',
|
|
15
|
+
'ul', 'ol', 'dl', 'menu',
|
|
16
|
+
'table', 'thead', 'tbody', 'tfoot', 'caption',
|
|
17
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
|
|
18
|
+
]);
|
|
19
|
+
const headingElements = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']);
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Helper Functions
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Get the semantic category for an element tag
|
|
25
|
+
*/
|
|
26
|
+
function getSemanticCategory(tag) {
|
|
27
|
+
if (headingElements.has(tag))
|
|
28
|
+
return 'heading';
|
|
29
|
+
if (['article', 'section', 'aside', 'nav'].includes(tag))
|
|
30
|
+
return 'sectioning';
|
|
31
|
+
if (['main', 'header', 'footer'].includes(tag))
|
|
32
|
+
return 'landmark';
|
|
33
|
+
if (['figure', 'figcaption', 'details', 'summary'].includes(tag))
|
|
34
|
+
return 'grouping';
|
|
35
|
+
if (['form', 'fieldset', 'legend'].includes(tag))
|
|
36
|
+
return 'form';
|
|
37
|
+
if (['table', 'thead', 'tbody', 'tfoot', 'caption'].includes(tag))
|
|
38
|
+
return 'table';
|
|
39
|
+
if (['ul', 'ol', 'dl', 'menu'].includes(tag))
|
|
40
|
+
return 'list';
|
|
41
|
+
return 'other';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Get trimmed text content from an element with optional max length
|
|
45
|
+
*/
|
|
46
|
+
function getTextContent(el, maxLen) {
|
|
47
|
+
return el?.textContent?.trim().slice(0, maxLen) || '';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Mapping of tag names to their child element selector for text extraction
|
|
51
|
+
*/
|
|
52
|
+
const childTextSelectors = {
|
|
53
|
+
figure: 'figcaption',
|
|
54
|
+
details: 'summary',
|
|
55
|
+
fieldset: 'legend',
|
|
56
|
+
table: 'caption',
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Get descriptive text for an element
|
|
60
|
+
*/
|
|
61
|
+
function getElementText(el, tagName) {
|
|
62
|
+
// Check ARIA attributes first
|
|
63
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
64
|
+
if (ariaLabel)
|
|
65
|
+
return ariaLabel;
|
|
66
|
+
const labelledBy = el.getAttribute('aria-labelledby');
|
|
67
|
+
if (labelledBy) {
|
|
68
|
+
const labelEl = document.getElementById(labelledBy);
|
|
69
|
+
if (labelEl)
|
|
70
|
+
return getTextContent(labelEl, 80);
|
|
71
|
+
}
|
|
72
|
+
// Headings: use direct text content
|
|
73
|
+
if (headingElements.has(tagName)) {
|
|
74
|
+
return getTextContent(el, 100);
|
|
75
|
+
}
|
|
76
|
+
// Elements with child selectors (figure, details, fieldset, table)
|
|
77
|
+
const childSelector = childTextSelectors[tagName];
|
|
78
|
+
if (childSelector) {
|
|
79
|
+
const childEl = el.querySelector(childSelector);
|
|
80
|
+
if (childEl)
|
|
81
|
+
return getTextContent(childEl, 80);
|
|
82
|
+
}
|
|
83
|
+
// Form: use name or id attribute
|
|
84
|
+
if (tagName === 'form') {
|
|
85
|
+
const name = el.getAttribute('name') || el.getAttribute('id');
|
|
86
|
+
if (name)
|
|
87
|
+
return name;
|
|
88
|
+
}
|
|
89
|
+
// Nav: try heading first, then first link
|
|
90
|
+
if (tagName === 'nav') {
|
|
91
|
+
const heading = el.querySelector('h1, h2, h3, h4, h5, h6');
|
|
92
|
+
if (heading)
|
|
93
|
+
return getTextContent(heading, 50);
|
|
94
|
+
const firstLink = el.querySelector('a');
|
|
95
|
+
if (firstLink)
|
|
96
|
+
return `Navigation (${getTextContent(firstLink, 30)}...)`;
|
|
97
|
+
}
|
|
98
|
+
// Sectioning elements: try direct child heading, then class name
|
|
99
|
+
if (['section', 'article', 'aside'].includes(tagName)) {
|
|
100
|
+
const heading = el.querySelector(':scope > h1, :scope > h2, :scope > h3, :scope > h4, :scope > h5, :scope > h6');
|
|
101
|
+
if (heading)
|
|
102
|
+
return getTextContent(heading, 80);
|
|
103
|
+
const className = el.className?.toString().split(' ')[0];
|
|
104
|
+
if (className && className.length < 30)
|
|
105
|
+
return className;
|
|
106
|
+
}
|
|
107
|
+
// Lists: count items
|
|
108
|
+
if (['ul', 'ol'].includes(tagName)) {
|
|
109
|
+
return `${el.querySelectorAll(':scope > li').length} items`;
|
|
110
|
+
}
|
|
111
|
+
if (tagName === 'dl') {
|
|
112
|
+
return `${el.querySelectorAll(':scope > dt').length} terms`;
|
|
113
|
+
}
|
|
114
|
+
// Fallback to role attribute
|
|
115
|
+
const role = el.getAttribute('role');
|
|
116
|
+
if (role)
|
|
117
|
+
return `[role="${role}"]`;
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if an element is visible
|
|
122
|
+
*/
|
|
123
|
+
function isVisible(el) {
|
|
124
|
+
const style = window.getComputedStyle(el);
|
|
125
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Recursively extract outline nodes from an element
|
|
129
|
+
*/
|
|
130
|
+
function extractFromElement(root) {
|
|
131
|
+
const nodes = [];
|
|
132
|
+
for (const child of Array.from(root.children)) {
|
|
133
|
+
const tagName = child.tagName.toLowerCase();
|
|
134
|
+
if (!isVisible(child))
|
|
135
|
+
continue;
|
|
136
|
+
if (child.getAttribute('data-devbar'))
|
|
137
|
+
continue;
|
|
138
|
+
if (semanticElements.has(tagName)) {
|
|
139
|
+
const text = getElementText(child, tagName);
|
|
140
|
+
const isHeading = headingElements.has(tagName);
|
|
141
|
+
const isLandmark = ['main', 'nav', 'header', 'footer', 'article', 'section', 'aside'].includes(tagName);
|
|
142
|
+
const hasText = text.length > 0;
|
|
143
|
+
if (isHeading || isLandmark || hasText) {
|
|
144
|
+
const level = isHeading ? parseInt(tagName[1], 10) : 0;
|
|
145
|
+
const node = {
|
|
146
|
+
tagName,
|
|
147
|
+
level,
|
|
148
|
+
text: text || `<${tagName}>`,
|
|
149
|
+
id: child.id || undefined,
|
|
150
|
+
children: [],
|
|
151
|
+
category: getSemanticCategory(tagName)
|
|
152
|
+
};
|
|
153
|
+
if (!isHeading) {
|
|
154
|
+
node.children = extractFromElement(child);
|
|
155
|
+
}
|
|
156
|
+
nodes.push(node);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const childNodes = extractFromElement(child);
|
|
160
|
+
nodes.push(...childNodes);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
const childNodes = extractFromElement(child);
|
|
165
|
+
nodes.push(...childNodes);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return nodes;
|
|
169
|
+
}
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// Public API
|
|
172
|
+
// ============================================================================
|
|
173
|
+
/**
|
|
174
|
+
* Extract the document outline from the page
|
|
175
|
+
*/
|
|
176
|
+
export function extractDocumentOutline() {
|
|
177
|
+
const body = document.body;
|
|
178
|
+
if (!body)
|
|
179
|
+
return [];
|
|
180
|
+
return extractFromElement(body);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Convert an outline to markdown format
|
|
184
|
+
*/
|
|
185
|
+
export function outlineToMarkdown(outline, indent = 0) {
|
|
186
|
+
let md = '';
|
|
187
|
+
if (indent === 0) {
|
|
188
|
+
md += '# Document Outline\n\n';
|
|
189
|
+
md += '**Semantic Categories:**\n';
|
|
190
|
+
md += '- `heading` - h1-h6 elements\n';
|
|
191
|
+
md += '- `sectioning` - article, section, aside, nav\n';
|
|
192
|
+
md += '- `landmark` - main, header, footer\n';
|
|
193
|
+
md += '- `grouping` - figure, details, summary\n';
|
|
194
|
+
md += '- `form` - form, fieldset\n';
|
|
195
|
+
md += '- `table` - table elements\n';
|
|
196
|
+
md += '- `list` - ul, ol, dl\n\n';
|
|
197
|
+
md += '---\n\n';
|
|
198
|
+
}
|
|
199
|
+
for (const node of outline) {
|
|
200
|
+
const prefix = ' '.repeat(indent);
|
|
201
|
+
const tagLabel = `\`<${node.tagName}>\``;
|
|
202
|
+
const anchor = node.id ? ` \`#${node.id}\`` : '';
|
|
203
|
+
const category = node.category ? ` [${node.category}]` : '';
|
|
204
|
+
if (node.category === 'heading' && indent === 0) {
|
|
205
|
+
md += `${'#'.repeat(node.level)} ${tagLabel} ${node.text}${anchor}\n\n`;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
md += `${prefix}- ${tagLabel}${category} ${node.text}${anchor}\n`;
|
|
209
|
+
}
|
|
210
|
+
if (node.children.length > 0) {
|
|
211
|
+
md += outlineToMarkdown(node.children, indent + 1);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return md;
|
|
215
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Schema Extraction
|
|
3
|
+
*
|
|
4
|
+
* Functions for extracting and formatting structured data from pages.
|
|
5
|
+
*/
|
|
6
|
+
import type { PageSchema } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Extract structured data (JSON-LD, meta tags, Open Graph, etc.) from the page
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractPageSchema(): PageSchema;
|
|
11
|
+
/**
|
|
12
|
+
* Convert a page schema to markdown format
|
|
13
|
+
*/
|
|
14
|
+
export declare function schemaToMarkdown(schema: PageSchema): string;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page Schema Extraction
|
|
3
|
+
*
|
|
4
|
+
* Functions for extracting and formatting structured data from pages.
|
|
5
|
+
*/
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Public API
|
|
8
|
+
// ============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* Extract structured data (JSON-LD, meta tags, Open Graph, etc.) from the page
|
|
11
|
+
*/
|
|
12
|
+
export function extractPageSchema() {
|
|
13
|
+
const schema = {
|
|
14
|
+
jsonLd: [],
|
|
15
|
+
metaTags: {},
|
|
16
|
+
openGraph: {},
|
|
17
|
+
twitter: {},
|
|
18
|
+
microdata: []
|
|
19
|
+
};
|
|
20
|
+
// Extract JSON-LD
|
|
21
|
+
const jsonLdScripts = document.querySelectorAll('script[type="application/ld+json"]');
|
|
22
|
+
jsonLdScripts.forEach((script) => {
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(script.textContent || '');
|
|
25
|
+
schema.jsonLd.push(data);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Invalid JSON-LD, skip
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
// Extract meta tags
|
|
32
|
+
const metaTags = document.querySelectorAll('meta[name], meta[property]');
|
|
33
|
+
metaTags.forEach((meta) => {
|
|
34
|
+
const name = meta.getAttribute('name') || meta.getAttribute('property') || '';
|
|
35
|
+
const content = meta.getAttribute('content') || '';
|
|
36
|
+
if (name.startsWith('og:')) {
|
|
37
|
+
schema.openGraph[name.replace('og:', '')] = content;
|
|
38
|
+
}
|
|
39
|
+
else if (name.startsWith('twitter:')) {
|
|
40
|
+
schema.twitter[name.replace('twitter:', '')] = content;
|
|
41
|
+
}
|
|
42
|
+
else if (name) {
|
|
43
|
+
schema.metaTags[name] = content;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// Extract microdata
|
|
47
|
+
const microdataItems = document.querySelectorAll('[itemscope]');
|
|
48
|
+
microdataItems.forEach((item) => {
|
|
49
|
+
const itemType = item.getAttribute('itemtype');
|
|
50
|
+
const props = {};
|
|
51
|
+
item.querySelectorAll('[itemprop]').forEach((prop) => {
|
|
52
|
+
const propName = prop.getAttribute('itemprop') || '';
|
|
53
|
+
const propValue = prop.getAttribute('content') ||
|
|
54
|
+
prop.getAttribute('href') ||
|
|
55
|
+
prop.textContent?.trim().slice(0, 200) || '';
|
|
56
|
+
if (propName)
|
|
57
|
+
props[propName] = propValue;
|
|
58
|
+
});
|
|
59
|
+
if (itemType || Object.keys(props).length > 0) {
|
|
60
|
+
schema.microdata.push({ type: itemType, properties: props });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return schema;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Convert a page schema to markdown format
|
|
67
|
+
*/
|
|
68
|
+
export function schemaToMarkdown(schema) {
|
|
69
|
+
let md = '';
|
|
70
|
+
if (schema.jsonLd.length > 0) {
|
|
71
|
+
md += '## JSON-LD\n\n';
|
|
72
|
+
schema.jsonLd.forEach((item, i) => {
|
|
73
|
+
md += `### Schema ${i + 1}\n\n`;
|
|
74
|
+
md += '```json\n' + JSON.stringify(item, null, 2) + '\n```\n\n';
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (Object.keys(schema.openGraph).length > 0) {
|
|
78
|
+
md += '## Open Graph\n\n';
|
|
79
|
+
for (const [key, value] of Object.entries(schema.openGraph)) {
|
|
80
|
+
md += `- **${key}**: ${value}\n`;
|
|
81
|
+
}
|
|
82
|
+
md += '\n';
|
|
83
|
+
}
|
|
84
|
+
if (Object.keys(schema.twitter).length > 0) {
|
|
85
|
+
md += '## Twitter Cards\n\n';
|
|
86
|
+
for (const [key, value] of Object.entries(schema.twitter)) {
|
|
87
|
+
md += `- **${key}**: ${value}\n`;
|
|
88
|
+
}
|
|
89
|
+
md += '\n';
|
|
90
|
+
}
|
|
91
|
+
if (Object.keys(schema.metaTags).length > 0) {
|
|
92
|
+
md += '## Meta Tags\n\n';
|
|
93
|
+
for (const [key, value] of Object.entries(schema.metaTags)) {
|
|
94
|
+
md += `- **${key}**: ${value}\n`;
|
|
95
|
+
}
|
|
96
|
+
md += '\n';
|
|
97
|
+
}
|
|
98
|
+
if (schema.microdata.length > 0) {
|
|
99
|
+
md += '## Microdata\n\n';
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
+
schema.microdata.forEach((item, i) => {
|
|
102
|
+
md += `### Item ${i + 1}${item.type ? ` (${item.type})` : ''}\n\n`;
|
|
103
|
+
for (const [key, value] of Object.entries(item.properties || {})) {
|
|
104
|
+
md += `- **${key}**: ${value}\n`;
|
|
105
|
+
}
|
|
106
|
+
md += '\n';
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (!md) {
|
|
110
|
+
md = '_No structured data found on this page_\n';
|
|
111
|
+
}
|
|
112
|
+
return md;
|
|
113
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Re-exports shared types from @ytspar/sweetlink and defines DevBar-specific types.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: We import from the types sub-path to avoid pulling in Node.js-only modules.
|
|
7
|
+
*/
|
|
8
|
+
export type { ConsoleLog, SweetlinkCommand, OutlineNode, PageSchema, } from '@ytspar/sweetlink/types';
|
|
9
|
+
/**
|
|
10
|
+
* Options for configuring the GlobalDevBar
|
|
11
|
+
*/
|
|
12
|
+
export interface GlobalDevBarOptions {
|
|
13
|
+
/** Position of the devbar. Default: 'bottom-left' */
|
|
14
|
+
position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | 'bottom-center';
|
|
15
|
+
/** Primary accent color (CSS color). Default: '#10b981' (emerald) */
|
|
16
|
+
accentColor?: string;
|
|
17
|
+
/** Which metrics to show. Default: all enabled */
|
|
18
|
+
showMetrics?: {
|
|
19
|
+
breakpoint?: boolean;
|
|
20
|
+
fcp?: boolean;
|
|
21
|
+
lcp?: boolean;
|
|
22
|
+
pageSize?: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Whether to show the screenshot button. Default: true */
|
|
25
|
+
showScreenshot?: boolean;
|
|
26
|
+
/** Whether to show console error/warning badges. Default: true */
|
|
27
|
+
showConsoleBadges?: boolean;
|
|
28
|
+
/** Whether to show tooltips on hover. Default: true */
|
|
29
|
+
showTooltips?: boolean;
|
|
30
|
+
/** Size overrides for special layouts (e.g., when other dev bars are present) */
|
|
31
|
+
sizeOverrides?: {
|
|
32
|
+
/** Custom width (CSS value). Default: calc(100vw - 140px) for centered, fit-content otherwise */
|
|
33
|
+
width?: string;
|
|
34
|
+
/** Custom max-width (CSS value). Default: 600px for centered, calc(100vw - 32px) otherwise */
|
|
35
|
+
maxWidth?: string;
|
|
36
|
+
/** Custom min-width (CSS value). Optional */
|
|
37
|
+
minWidth?: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Custom control that can be registered by host applications
|
|
42
|
+
*/
|
|
43
|
+
export interface DevBarControl {
|
|
44
|
+
id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
onClick: () => void;
|
|
47
|
+
active?: boolean;
|
|
48
|
+
disabled?: boolean;
|
|
49
|
+
variant?: 'default' | 'warning';
|
|
50
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Buttons
|
|
3
|
+
*
|
|
4
|
+
* Button creation and styling utilities for the DevBar UI.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Get button styling based on active state and color
|
|
8
|
+
*/
|
|
9
|
+
export declare function getButtonStyles(color: string, isActive: boolean, isDisabled: boolean): Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* Create a styled button with common properties
|
|
12
|
+
*/
|
|
13
|
+
export declare function createStyledButton(options: {
|
|
14
|
+
color: string;
|
|
15
|
+
text: string;
|
|
16
|
+
padding?: string;
|
|
17
|
+
borderRadius?: string;
|
|
18
|
+
fontSize?: string;
|
|
19
|
+
width?: string;
|
|
20
|
+
height?: string;
|
|
21
|
+
}): HTMLButtonElement;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Buttons
|
|
3
|
+
*
|
|
4
|
+
* Button creation and styling utilities for the DevBar UI.
|
|
5
|
+
*/
|
|
6
|
+
import { ACTION_BUTTON_BASE_STYLES } from '../constants.js';
|
|
7
|
+
/**
|
|
8
|
+
* Get button styling based on active state and color
|
|
9
|
+
*/
|
|
10
|
+
export function getButtonStyles(color, isActive, isDisabled) {
|
|
11
|
+
return {
|
|
12
|
+
...ACTION_BUTTON_BASE_STYLES,
|
|
13
|
+
borderColor: isActive ? color : `${color}80`,
|
|
14
|
+
backgroundColor: isActive ? `${color}33` : 'transparent',
|
|
15
|
+
color: isActive ? color : `${color}99`,
|
|
16
|
+
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
17
|
+
opacity: '1',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Apply hover effects to a button element (internal helper)
|
|
22
|
+
*/
|
|
23
|
+
function applyButtonHoverEffects(btn, color, isActive = false) {
|
|
24
|
+
btn.onmouseenter = () => {
|
|
25
|
+
btn.style.backgroundColor = `${color}20`;
|
|
26
|
+
};
|
|
27
|
+
btn.onmouseleave = () => {
|
|
28
|
+
btn.style.backgroundColor = isActive ? `${color}33` : 'transparent';
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a styled button with common properties
|
|
33
|
+
*/
|
|
34
|
+
export function createStyledButton(options) {
|
|
35
|
+
const { color, text, padding = '6px 12px', borderRadius = '6px', fontSize = '0.75rem', width, height, } = options;
|
|
36
|
+
const btn = document.createElement('button');
|
|
37
|
+
Object.assign(btn.style, {
|
|
38
|
+
padding: width ? undefined : padding,
|
|
39
|
+
width,
|
|
40
|
+
height,
|
|
41
|
+
display: 'flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
justifyContent: 'center',
|
|
44
|
+
backgroundColor: 'transparent',
|
|
45
|
+
border: `1px solid ${color}60`,
|
|
46
|
+
borderRadius,
|
|
47
|
+
color,
|
|
48
|
+
fontSize,
|
|
49
|
+
cursor: 'pointer',
|
|
50
|
+
transition: 'all 150ms',
|
|
51
|
+
});
|
|
52
|
+
btn.textContent = text;
|
|
53
|
+
applyButtonHoverEffects(btn, color);
|
|
54
|
+
return btn;
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Icons
|
|
3
|
+
*
|
|
4
|
+
* SVG icon creation utilities for the DevBar UI.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Create an SVG icon element with the given path data
|
|
8
|
+
*/
|
|
9
|
+
export declare function createSvgIcon(pathData: string, options: {
|
|
10
|
+
viewBox?: string;
|
|
11
|
+
fill?: boolean;
|
|
12
|
+
stroke?: boolean;
|
|
13
|
+
}): SVGSVGElement;
|
package/dist/ui/icons.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Icons
|
|
3
|
+
*
|
|
4
|
+
* SVG icon creation utilities for the DevBar UI.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Create an SVG icon element with the given path data
|
|
8
|
+
*/
|
|
9
|
+
export function createSvgIcon(pathData, options) {
|
|
10
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
11
|
+
svg.setAttribute('width', '12');
|
|
12
|
+
svg.setAttribute('height', '12');
|
|
13
|
+
svg.setAttribute('viewBox', options.viewBox || '0 0 24 24');
|
|
14
|
+
if (options.fill) {
|
|
15
|
+
svg.style.fill = 'currentColor';
|
|
16
|
+
}
|
|
17
|
+
if (options.stroke) {
|
|
18
|
+
svg.style.stroke = 'currentColor';
|
|
19
|
+
svg.style.fill = 'none';
|
|
20
|
+
}
|
|
21
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
22
|
+
path.setAttribute('d', pathData);
|
|
23
|
+
svg.appendChild(path);
|
|
24
|
+
return svg;
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar UI Components
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all UI utilities.
|
|
5
|
+
*/
|
|
6
|
+
export { createSvgIcon } from './icons.js';
|
|
7
|
+
export { getButtonStyles, createStyledButton } from './buttons.js';
|
|
8
|
+
export { createModalOverlay, createModalBox, createModalHeader, createModalContent, createEmptyMessage, createInfoBox, type ModalConfig } from './modals.js';
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar UI Components
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all UI utilities.
|
|
5
|
+
*/
|
|
6
|
+
export { createSvgIcon } from './icons.js';
|
|
7
|
+
export { getButtonStyles, createStyledButton } from './buttons.js';
|
|
8
|
+
export { createModalOverlay, createModalBox, createModalHeader, createModalContent, createEmptyMessage, createInfoBox } from './modals.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevBar Modals
|
|
3
|
+
*
|
|
4
|
+
* Modal creation utilities for the DevBar UI.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for creating a modal
|
|
8
|
+
*/
|
|
9
|
+
export interface ModalConfig {
|
|
10
|
+
color: string;
|
|
11
|
+
title: string;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
onCopyMd: () => Promise<void>;
|
|
14
|
+
onSave?: () => void;
|
|
15
|
+
sweetlinkConnected: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create modal overlay with click-outside-to-close behavior
|
|
19
|
+
*/
|
|
20
|
+
export declare function createModalOverlay(onClose: () => void): HTMLDivElement;
|
|
21
|
+
/**
|
|
22
|
+
* Create modal box with border and shadow
|
|
23
|
+
*/
|
|
24
|
+
export declare function createModalBox(color: string): HTMLDivElement;
|
|
25
|
+
/**
|
|
26
|
+
* Create modal header with title, copy/save/close buttons
|
|
27
|
+
*/
|
|
28
|
+
export declare function createModalHeader(config: ModalConfig): HTMLDivElement;
|
|
29
|
+
/**
|
|
30
|
+
* Create modal content container
|
|
31
|
+
*/
|
|
32
|
+
export declare function createModalContent(): HTMLDivElement;
|
|
33
|
+
/**
|
|
34
|
+
* Create empty state message for modals
|
|
35
|
+
*/
|
|
36
|
+
export declare function createEmptyMessage(text: string): HTMLDivElement;
|
|
37
|
+
/**
|
|
38
|
+
* Create a colored info box (for error states, cost estimates, etc.)
|
|
39
|
+
*/
|
|
40
|
+
export declare function createInfoBox(color: string, title: string, content: string | HTMLElement[]): HTMLDivElement;
|