tosijs-ui 1.5.7 → 1.5.8
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/dist/doc-browser.js +77 -66
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +21 -8
- package/dist/iife.js +37 -37
- package/dist/iife.js.map +5 -5
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
package/dist/doc-browser.js
CHANGED
|
@@ -288,8 +288,8 @@ export function createDocBrowser(options) {
|
|
|
288
288
|
}
|
|
289
289
|
testResultsResolve(allResults);
|
|
290
290
|
testResultsResolve = undefined;
|
|
291
|
-
// Post results to dev server on localhost
|
|
292
|
-
if (isLocalhost) {
|
|
291
|
+
// Post results to dev server on localhost (not from test iframes)
|
|
292
|
+
if (isLocalhost && !isTestFrame) {
|
|
293
293
|
fetch('/report', {
|
|
294
294
|
method: 'POST',
|
|
295
295
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -680,17 +680,46 @@ export function createDocBrowser(options) {
|
|
|
680
680
|
lines.unshift(`**Summary: ${totalPassed} passed, ${totalFailed} failed**`, '');
|
|
681
681
|
return lines.join('\n');
|
|
682
682
|
}
|
|
683
|
+
// Detect if running as background test iframe
|
|
684
|
+
const searchParams = new URLSearchParams(window.location.search);
|
|
685
|
+
const isTestFrame = searchParams.get('_testMode') === '1';
|
|
686
|
+
const testFrameFilename = isTestFrame
|
|
687
|
+
? window.location.search.substring(1).split('&')[0]
|
|
688
|
+
: null;
|
|
683
689
|
// Listen for test completion events
|
|
684
690
|
container.addEventListener('testcomplete', ((event) => {
|
|
685
691
|
handleTestComplete(event);
|
|
686
692
|
updateTestWidget();
|
|
693
|
+
// If running in test iframe, post results to parent
|
|
694
|
+
if (isTestFrame && window.parent !== window && testFrameFilename) {
|
|
695
|
+
const { results } = event.detail;
|
|
696
|
+
window.parent.postMessage({ type: 'tosi-test-results', filename: testFrameFilename, results }, '*');
|
|
697
|
+
}
|
|
687
698
|
}));
|
|
699
|
+
// If running as test iframe, signal when all tests on this page are done
|
|
700
|
+
if (isTestFrame && testFrameFilename) {
|
|
701
|
+
const signalDone = () => {
|
|
702
|
+
const examples = container.querySelectorAll('tosi-example');
|
|
703
|
+
const withTests = [...examples].filter((ex) => ex.classList.contains('-has-tests'));
|
|
704
|
+
const running = withTests.filter((ex) => ex.classList.contains('-test-running'));
|
|
705
|
+
if (withTests.length > 0 && running.length === 0) {
|
|
706
|
+
window.parent.postMessage({ type: 'tosi-tests-done', filename: testFrameFilename }, '*');
|
|
707
|
+
}
|
|
708
|
+
else {
|
|
709
|
+
setTimeout(signalDone, 100);
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
// Give time for examples to start running
|
|
713
|
+
setTimeout(signalDone, 500);
|
|
714
|
+
}
|
|
688
715
|
// Background test runner for all doc pages
|
|
689
716
|
const runBackgroundTests = async () => {
|
|
690
717
|
if (backgroundTestsStarted)
|
|
691
718
|
return;
|
|
692
719
|
if (!testManager.enabled.value)
|
|
693
720
|
return;
|
|
721
|
+
if (isTestFrame)
|
|
722
|
+
return; // Don't run background tests in test iframe
|
|
694
723
|
backgroundTestsStarted = true;
|
|
695
724
|
// Find all docs that have test blocks
|
|
696
725
|
const docsWithTests = docs.filter((doc) => doc.text.includes('```test'));
|
|
@@ -699,89 +728,71 @@ export function createDocBrowser(options) {
|
|
|
699
728
|
setTestWidgetRunning();
|
|
700
729
|
}
|
|
701
730
|
if (pagesWithTests === 0) {
|
|
702
|
-
// No tests to run, resolve immediately
|
|
703
731
|
if (testResultsResolve) {
|
|
704
732
|
testResultsResolve({ passed: 0, failed: 0, pages: {} });
|
|
705
733
|
testResultsResolve = undefined;
|
|
706
734
|
}
|
|
707
735
|
return;
|
|
708
736
|
}
|
|
709
|
-
|
|
737
|
+
const currentFilename = String(app.currentDoc.filename);
|
|
738
|
+
// Create a hidden iframe that loads the full page
|
|
710
739
|
const testFrame = document.createElement('iframe');
|
|
711
740
|
testFrame.style.cssText =
|
|
712
741
|
'position: fixed; left: 0; top: 0; width: 800px; height: 600px; opacity: 0; pointer-events: none;';
|
|
713
742
|
document.body.appendChild(testFrame);
|
|
714
|
-
|
|
743
|
+
// Listen for test results posted from the iframe
|
|
744
|
+
const messageHandler = (event) => {
|
|
745
|
+
if (event.data?.type !== 'tosi-test-results')
|
|
746
|
+
return;
|
|
747
|
+
const { filename, results } = event.data;
|
|
748
|
+
if (!pageTestResults[filename]) {
|
|
749
|
+
pageTestResults[filename] = {
|
|
750
|
+
passed: true,
|
|
751
|
+
tests: [],
|
|
752
|
+
totalPassed: 0,
|
|
753
|
+
totalFailed: 0,
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const pageResults = pageTestResults[filename];
|
|
757
|
+
pageResults.tests.push(...results.tests);
|
|
758
|
+
pageResults.totalPassed += results.passed;
|
|
759
|
+
pageResults.totalFailed += results.failed;
|
|
760
|
+
pageResults.passed = pageResults.totalFailed === 0;
|
|
761
|
+
updateDocTestStatus(filename);
|
|
762
|
+
updateTestWidget();
|
|
763
|
+
};
|
|
764
|
+
window.addEventListener('message', messageHandler);
|
|
715
765
|
for (const doc of docsWithTests) {
|
|
716
|
-
// Skip current page
|
|
717
|
-
if (doc.filename === currentFilename)
|
|
766
|
+
// Skip current page — it runs tests naturally
|
|
767
|
+
if (doc.filename === currentFilename)
|
|
718
768
|
continue;
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
769
|
+
// Navigate iframe to the page
|
|
770
|
+
const base = window.location.origin + window.location.pathname;
|
|
771
|
+
testFrame.src = `${base}?${doc.filename}&_testMode=1`;
|
|
772
|
+
// Wait for the iframe to signal it's done (max 30s per page)
|
|
773
|
+
await new Promise((resolve) => {
|
|
774
|
+
const deadline = Date.now() + 30_000;
|
|
775
|
+
const onDone = (event) => {
|
|
776
|
+
if (event.data?.type === 'tosi-tests-done' &&
|
|
777
|
+
event.data.filename === doc.filename) {
|
|
778
|
+
window.removeEventListener('message', onDone);
|
|
779
|
+
resolve();
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
window.addEventListener('message', onDone);
|
|
783
|
+
setTimeout(() => {
|
|
784
|
+
window.removeEventListener('message', onDone);
|
|
785
|
+
resolve();
|
|
786
|
+
}, deadline - Date.now());
|
|
734
787
|
});
|
|
735
|
-
testContainer.appendChild(viewer);
|
|
736
|
-
// Listen for test results from this container
|
|
737
|
-
const handleBgTest = (event) => {
|
|
738
|
-
const { results } = event.detail;
|
|
739
|
-
const pageResults = pageTestResults[doc.filename];
|
|
740
|
-
pageResults.tests.push(...results.tests);
|
|
741
|
-
pageResults.totalPassed += results.passed;
|
|
742
|
-
pageResults.totalFailed += results.failed;
|
|
743
|
-
pageResults.passed = pageResults.totalFailed === 0;
|
|
744
|
-
updateDocTestStatus(doc.filename);
|
|
745
|
-
updateTestWidget();
|
|
746
|
-
};
|
|
747
|
-
testContainer.addEventListener('testcomplete', handleBgTest);
|
|
748
|
-
// Append to iframe for execution
|
|
749
|
-
const frameDoc = testFrame.contentDocument;
|
|
750
|
-
if (frameDoc) {
|
|
751
|
-
frameDoc.body.innerHTML = '';
|
|
752
|
-
frameDoc.body.appendChild(testContainer);
|
|
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
|
-
});
|
|
774
|
-
}
|
|
775
788
|
markPageTested(doc.filename);
|
|
776
789
|
}
|
|
777
790
|
// Clean up
|
|
791
|
+
window.removeEventListener('message', messageHandler);
|
|
778
792
|
testFrame.remove();
|
|
779
793
|
// Mark current page as tested if it has tests
|
|
780
794
|
if (docsWithTests.some((d) => d.filename === currentFilename)) {
|
|
781
|
-
|
|
782
|
-
setTimeout(() => {
|
|
783
|
-
markPageTested(currentFilename);
|
|
784
|
-
}, 1000);
|
|
795
|
+
setTimeout(() => markPageTested(currentFilename), 1000);
|
|
785
796
|
}
|
|
786
797
|
};
|
|
787
798
|
// Run background tests when enabled (initially or when toggled on)
|
package/dist/icons.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface IconRule {
|
|
|
8
8
|
prefix: string | RegExp;
|
|
9
9
|
apply: (baseName: string, match: RegExpMatchArray | string, parts: ElementPart[]) => Element | string | null;
|
|
10
10
|
}
|
|
11
|
+
/** Returns icon name safe for suffix concatenation (appends _ if name ends in digit) */
|
|
12
|
+
export declare const safeIconSuffix: (name: string) => string;
|
|
11
13
|
export declare const iconRules: IconRule[];
|
|
12
14
|
export declare const icons: SVGIconMap;
|
|
13
15
|
export declare class SvgIcon extends WebComponent {
|
package/dist/icons.js
CHANGED
|
@@ -573,6 +573,8 @@ export const svg2DataUrl = (icon, fill, stroke, strokeWidth) => {
|
|
|
573
573
|
const text = encodeURIComponent(svg.outerHTML);
|
|
574
574
|
return `url(data:image/svg+xml;charset=UTF-8,${text})`;
|
|
575
575
|
};
|
|
576
|
+
/** Returns icon name safe for suffix concatenation (appends _ if name ends in digit) */
|
|
577
|
+
export const safeIconSuffix = (name) => /\d$/.test(name) ? name + '_' : name;
|
|
576
578
|
export const iconRules = [
|
|
577
579
|
{
|
|
578
580
|
prefix: /^spin(_?\d+)/,
|
|
@@ -601,19 +603,19 @@ export const iconRules = [
|
|
|
601
603
|
},
|
|
602
604
|
{
|
|
603
605
|
prefix: 'un',
|
|
604
|
-
apply: (baseName) => `slash25o$${baseName}75s75o`,
|
|
606
|
+
apply: (baseName) => `slash25o$${safeIconSuffix(baseName)}75s75o`,
|
|
605
607
|
},
|
|
606
608
|
{
|
|
607
609
|
prefix: 'check',
|
|
608
|
-
apply: (baseName) => `check75o_00aa00S$${baseName}75s50o`,
|
|
610
|
+
apply: (baseName) => `check75o_00aa00S$${safeIconSuffix(baseName)}75s50o`,
|
|
609
611
|
},
|
|
610
612
|
{
|
|
611
613
|
prefix: 'cancel',
|
|
612
|
-
apply: (baseName) => `x75o_cc0000S$${baseName}75s50o`,
|
|
614
|
+
apply: (baseName) => `x75o_cc0000S$${safeIconSuffix(baseName)}75s50o`,
|
|
613
615
|
},
|
|
614
616
|
{
|
|
615
617
|
prefix: 'search',
|
|
616
|
-
apply: (baseName) => `search80s30x30y$${baseName}50o`,
|
|
618
|
+
apply: (baseName) => `search80s30x30y$${safeIconSuffix(baseName)}50o`,
|
|
617
619
|
},
|
|
618
620
|
];
|
|
619
621
|
function makeIcon(spec, parts) {
|
|
@@ -706,21 +708,32 @@ function composeIcon(prop, parts) {
|
|
|
706
708
|
return null;
|
|
707
709
|
}
|
|
708
710
|
const MAX_REDIRECTS = 10;
|
|
709
|
-
// Style suffixes —
|
|
711
|
+
// Style suffixes — value then letter code:
|
|
710
712
|
// 50o (opacity), 75s (scale), 20x (translateX%), _10y (translateY%)
|
|
711
713
|
// 90r (rotate 90°), _45r (rotate -45°), 0f (flipH), 1f (flipV)
|
|
712
714
|
// _FF0000F (fill hex), _f00S (stroke hex), 3W (stroke-width)
|
|
713
715
|
// _brandColorF (fill var), _accentS (stroke var)
|
|
714
|
-
|
|
716
|
+
// Icon names ending in digits need _ separator: edit2_50o
|
|
717
|
+
const SUFFIX_RE = /(?:(?<=[a-zA-Z_])(?:_?\d+[osxyr]|[01]f|\d+W)|_[a-zA-Z0-9]+[FS])+$/;
|
|
715
718
|
function parseStyleSuffixes(name) {
|
|
716
719
|
const match = name.match(SUFFIX_RE);
|
|
717
720
|
if (!match)
|
|
718
721
|
return null;
|
|
719
|
-
|
|
722
|
+
let baseName = name.slice(0, match.index);
|
|
720
723
|
if (!baseName)
|
|
721
724
|
return null;
|
|
725
|
+
// Strip trailing _ separator (for digit-ending names: edit2_50o)
|
|
726
|
+
if (baseName.endsWith('_'))
|
|
727
|
+
baseName = baseName.slice(0, -1);
|
|
728
|
+
if (!baseName)
|
|
729
|
+
return null;
|
|
730
|
+
const data = iconData;
|
|
731
|
+
if (!data[baseName])
|
|
732
|
+
return null;
|
|
722
733
|
const style = {};
|
|
723
|
-
const suffixes = match[0].match(/_?\d
|
|
734
|
+
const suffixes = match[0].match(/_?\d+[osxyr]|[01]f|_[a-zA-Z0-9]+[FS]|\d+W/g);
|
|
735
|
+
if (!suffixes)
|
|
736
|
+
return null;
|
|
724
737
|
let tx = '';
|
|
725
738
|
let ty = '';
|
|
726
739
|
let scale = '';
|