tosijs-ui 1.5.6 → 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.
@@ -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
- // Create a hidden iframe to run tests in background
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
- const currentFilename = String(app.currentDoc.filename);
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 - it will run tests naturally
717
- if (doc.filename === currentFilename) {
766
+ // Skip current page it runs tests naturally
767
+ if (doc.filename === currentFilename)
718
768
  continue;
719
- }
720
- // Reset page results for this doc
721
- pageTestResults[doc.filename] = {
722
- passed: true,
723
- tests: [],
724
- totalPassed: 0,
725
- totalFailed: 0,
726
- };
727
- // Create a container and render the doc content
728
- const testContainer = document.createElement('div');
729
- const viewer = tosiMd({
730
- value: doc.text,
731
- didRender() {
732
- LiveExample.insertExamples(this, context);
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
- // Current page tests will complete naturally, just wait a bit
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/form.d.ts CHANGED
@@ -96,6 +96,7 @@ export declare class TosiForm extends XinComponent {
96
96
  }, isValid: boolean) => void;
97
97
  connectedCallback(): void;
98
98
  private handleElementChange;
99
+ private syncFieldValues;
99
100
  private initializeNamedElements;
100
101
  }
101
102
  /** @deprecated Use TosiField instead */
package/dist/form.js CHANGED
@@ -669,7 +669,8 @@ export class TosiForm extends XinComponent {
669
669
  handleSubmit = (event) => {
670
670
  event.preventDefault();
671
671
  event.stopPropagation();
672
- // Access fields to ensure value is parsed from JSON string if needed
672
+ // Sync field values before submitting
673
+ this.syncFieldValues();
673
674
  const value = this.fields;
674
675
  this.submitCallback(value, this.isValid);
675
676
  };
@@ -696,13 +697,30 @@ export class TosiForm extends XinComponent {
696
697
  this.fields[name] = target.value;
697
698
  }
698
699
  };
700
+ syncFieldValues() {
701
+ const formValue = this.fields;
702
+ const namedElements = this.querySelectorAll('[name], [key]');
703
+ for (const el of namedElements) {
704
+ const key = el.getAttribute('name') || el.getAttribute('key');
705
+ if (!key)
706
+ continue;
707
+ if (formValue[key] === undefined) {
708
+ const val = el.value ?? el.getAttribute('value');
709
+ if (val != null) {
710
+ formValue[key] = val;
711
+ }
712
+ }
713
+ }
714
+ }
699
715
  initializeNamedElements() {
700
716
  const formValue = this.fields;
701
717
  // Handle both 'name' (formAssociated) and 'key' (tosi-field) attributes
702
718
  const namedElements = this.querySelectorAll('[name], [key]');
703
719
  for (const el of namedElements) {
704
720
  const key = el.getAttribute('name') || el.getAttribute('key');
705
- if (key && formValue[key] !== undefined) {
721
+ if (!key)
722
+ continue;
723
+ if (formValue[key] !== undefined) {
706
724
  ;
707
725
  el.value = formValue[key];
708
726
  }
package/dist/icon-data.js CHANGED
@@ -68,7 +68,7 @@ export default {
68
68
  zapOff: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><polyline points=\"12.41 6.75 13 2 10.57 4.92\"></polyline><polyline points=\"18.57 12.91 21 10 15.66 10\"></polyline><polyline points=\"8 8 3 14 12 14 11 22 16 16\"></polyline><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"></line></svg>",
69
69
  x: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line></svg>",
70
70
  barChart: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"10\"></line><line x1=\"18\" y1=\"20\" x2=\"18\" y2=\"4\"></line><line x1=\"6\" y1=\"20\" x2=\"6\" y2=\"16\"></line></svg>",
71
- lock: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path></svg>",
71
+ lock: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\"></rect><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"></path></svg> ",
72
72
  logIn: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4\"></path><polyline points=\"10 17 15 12 10 7\"></polyline><line x1=\"15\" y1=\"12\" x2=\"3\" y2=\"12\"></line></svg>",
73
73
  shoppingBag: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z\"></path><line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"></line><path d=\"M16 10a4 4 0 0 1-8 0\"></path></svg>",
74
74
  divide: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"6\" r=\"2\"></circle><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line><circle cx=\"12\" cy=\"18\" r=\"2\"></circle></svg>",
@@ -142,7 +142,7 @@ export default {
142
142
  rss: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M4 11a9 9 0 0 1 9 9\"></path><path d=\"M4 4a16 16 0 0 1 16 16\"></path><circle cx=\"5\" cy=\"19\" r=\"1\"></circle></svg>",
143
143
  wifi: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M5 12.55a11 11 0 0 1 14.08 0\"></path><path d=\"M1.42 9a16 16 0 0 1 21.16 0\"></path><path d=\"M8.53 16.11a6 6 0 0 1 6.95 0\"></path><line x1=\"12\" y1=\"20\" x2=\"12.01\" y2=\"20\"></line></svg>",
144
144
  watch: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"7\"></circle><polyline points=\"12 9 12 12 13.5 13.5\"></polyline><path d=\"M16.51 17.35l-.35 3.83a2 2 0 0 1-2 1.82H9.83a2 2 0 0 1-2-1.82l-.35-3.83m.01-10.7l.35-3.83A2 2 0 0 1 9.83 1h4.35a2 2 0 0 1 2 1.82l.35 3.83\"></path></svg>",
145
- info: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"></line><line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"></line></svg>",
145
+ info: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"></line><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"7.75\"></line></svg>",
146
146
  userX: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"></path><circle cx=\"8.5\" cy=\"7\" r=\"4\"></circle><line x1=\"18\" y1=\"8\" x2=\"23\" y2=\"13\"></line><line x1=\"23\" y1=\"8\" x2=\"18\" y2=\"13\"></line></svg>",
147
147
  loader: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"6\"></line><line x1=\"12\" y1=\"18\" x2=\"12\" y2=\"22\"></line><line x1=\"4.93\" y1=\"4.93\" x2=\"7.76\" y2=\"7.76\"></line><line x1=\"16.24\" y1=\"16.24\" x2=\"19.07\" y2=\"19.07\"></line><line x1=\"2\" y1=\"12\" x2=\"6\" y2=\"12\"></line><line x1=\"18\" y1=\"12\" x2=\"22\" y2=\"12\"></line><line x1=\"4.93\" y1=\"19.07\" x2=\"7.76\" y2=\"16.24\"></line><line x1=\"16.24\" y1=\"7.76\" x2=\"19.07\" y2=\"4.93\"></line></svg>",
148
148
  folderPlus: "<svg class=\"stroked\" viewBox=\"0 0 24 24\"><path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"></path><line x1=\"12\" y1=\"11\" x2=\"12\" y2=\"17\"></line><line x1=\"9\" y1=\"14\" x2=\"15\" y2=\"14\"></line></svg>",
package/dist/icons.d.ts CHANGED
@@ -6,8 +6,10 @@ export declare const defineIcons: (newIcons: {
6
6
  export declare const svg2DataUrl: (icon: Element, fill?: string, stroke?: string, strokeWidth?: number) => string;
7
7
  export interface IconRule {
8
8
  prefix: string | RegExp;
9
- apply: (baseName: string, match: RegExpMatchArray | string, parts: ElementPart[]) => Element | null;
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 {
@@ -20,6 +22,7 @@ export declare class SvgIcon extends WebComponent {
20
22
  '--tosi-icon-stroke-linecap': string;
21
23
  '--tosi-icon-fill': string;
22
24
  display: string;
25
+ verticalAlign: string;
23
26
  stroke: string;
24
27
  strokeWidth: string;
25
28
  strokeLinejoin: string;