tosijs-ui 1.5.7 → 1.5.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.
- package/dist/doc-browser.js +77 -66
- package/dist/icons.d.ts +2 -0
- package/dist/icons.js +51 -68
- package/dist/iife.js +33 -33
- 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 +2 -2
- 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
|
@@ -7,26 +7,6 @@
|
|
|
7
7
|
<tosi-icon title="tosi-platform" icon="tosiPlatform" style="--tosi-icon-size: 128px"></tosi-icon>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
A library that provides `ElementCreator` functions that produce SVG icons. It leverages `tosijs`'s
|
|
11
|
-
`svgElements` proxy and is intended to address all the key use-cases for SVG icons in web
|
|
12
|
-
applications along with being very easy to extend and maintain.
|
|
13
|
-
|
|
14
|
-
> ### Supported Use Cases
|
|
15
|
-
> - inline SVGs that can be styled by CSS (for buttons, etc.)
|
|
16
|
-
> - allows both stroked and filled icons (unlike font-based systems)
|
|
17
|
-
> - support for color icons (without requiring multiple glyphs perfectly aligned)
|
|
18
|
-
> - icons can be rendered as data urls, e.g. to insert into CSS… (the little `owl` logo rendered under blockquotes is an example)
|
|
19
|
-
> - icon composition: stack, transform, and style icons via naming conventions (e.g. `lock50s75o$shield`)
|
|
20
|
-
> - extensible rules system for custom prefixes and overlays (e.g. `unLock`, `checkFile`)
|
|
21
|
-
> - icon redirects eliminate redundant SVG data (e.g. `chevronDown` → `chevronRight90r`)
|
|
22
|
-
|
|
23
|
-
### Nice Features
|
|
24
|
-
> - no build process magic needed (your icons are "just javascript", no special CSS files needed, no magic glyph mappings). Adding new, or overriding existing, icons is trivial.
|
|
25
|
-
> - icons are just regular SVG, not a specialized subset.
|
|
26
|
-
> - highly optimized and compressible (the code is comparable in size to what you get with a compressed font built from the same icons, except icon fonts don't support strokes, gradients, etc.)
|
|
27
|
-
|
|
28
|
-
## icons
|
|
29
|
-
|
|
30
10
|
`icons` is a proxy that generates an `ElementCreator` for a given icon on demand,
|
|
31
11
|
e.g. `icons.chevronDown()` produces an `<svg>` element containing a downward-pointing chevron
|
|
32
12
|
icon with the class `icon-chevron-down`.
|
|
@@ -351,7 +331,7 @@ that, for example, treat all colored icons inside buttons the same way.
|
|
|
351
331
|
<tosi-icon icon="lock50s75o_10y$shield_brandColorS" size=128></tosi-icon>
|
|
352
332
|
<tosi-icon icon="unLock_brandColorS" size=128></tosi-icon>
|
|
353
333
|
<tosi-icon icon="checkFile" size=128></tosi-icon>
|
|
354
|
-
<tosi-icon icon="
|
|
334
|
+
<tosi-icon icon="spin120Loader40s_30x_brandColorS$cloud" size=128></tosi-icon>
|
|
355
335
|
|
|
356
336
|
### Why?
|
|
357
337
|
|
|
@@ -371,6 +351,12 @@ variations on every icon?
|
|
|
371
351
|
<tosi-icon icon="pin0f_brandColorS" size=64></tosi-icon>
|
|
372
352
|
<tosi-icon icon="unPin_brandColorS" size=64></tosi-icon>
|
|
373
353
|
|
|
354
|
+
And now we can just create icon languages…
|
|
355
|
+
|
|
356
|
+
<tosi-icon icon="checkUsers" size=64></tosi-icon>
|
|
357
|
+
<tosi-icon icon="searchMap" size=64></tosi-icon>
|
|
358
|
+
<tosi-icon icon="cancelHeart" size=64></tosi-icon>
|
|
359
|
+
|
|
374
360
|
### Icon modifier suffixes
|
|
375
361
|
|
|
376
362
|
The suffix system is inspired by tosijs's CSS variable math, where
|
|
@@ -473,35 +459,6 @@ with `svg2DataUrl`.
|
|
|
473
459
|
If you ask for an icon that isn't defined, the `icons` proxy will print a warning to console
|
|
474
460
|
and render a `square` (in fact, `icons.square()`) as a fallback.
|
|
475
461
|
|
|
476
|
-
## Why?
|
|
477
|
-
|
|
478
|
-
My evolution has been:
|
|
479
|
-
|
|
480
|
-
1. Using Icomoon.io, which I still think is a solid choice for managing custom icon fonts
|
|
481
|
-
2. Processing Icomoon selection.json files into icon-data and then generating SVGs dynamically
|
|
482
|
-
from the data
|
|
483
|
-
3. Ingesting SVGs directly, with a little cleanup
|
|
484
|
-
|
|
485
|
-
The goal is always to have a single source of truth for icons, no magic or convoluted tooling, and
|
|
486
|
-
be able to quickly and easily add and replace icons, distribute them with components, and
|
|
487
|
-
have no mess or fuss.
|
|
488
|
-
|
|
489
|
-
1. Works well, but…
|
|
490
|
-
- color icons are flaky,
|
|
491
|
-
- doesn't play well with others,
|
|
492
|
-
- can't really distribute the icons with your components.
|
|
493
|
-
- difficult to use icons in CSS `content`
|
|
494
|
-
- impossible to use icons in CSS backgrounds
|
|
495
|
-
2. This is `icons.ts` until just now! Solves all the above, but…
|
|
496
|
-
- no fancy SVG effects, like gradients (goodness knows I experimented with converting CSS gradients to SVG gradients) and, most
|
|
497
|
-
- **strokes** need to be converted to outlines
|
|
498
|
-
- outlined strokes can't be styled the way strokes can
|
|
499
|
-
- blocks use of popular icon libraries
|
|
500
|
-
3. This is how everyone else works, except…
|
|
501
|
-
- no build magic needed: `defineIcons({ myIcon: '<svg....>', ... })`
|
|
502
|
-
- if you want build magic, `icons.js` has no dependencies, finds icons and creates an `icon-data.ts` file.
|
|
503
|
-
- smaller icon files, even though I'm now including more icons (including *all the current* feathericons)
|
|
504
|
-
|
|
505
462
|
## Icon Sources
|
|
506
463
|
|
|
507
464
|
Many of these icons are sourced from [Feather Icons](https://github.com/feathericons/feather), but
|
|
@@ -514,6 +471,12 @@ The remaining icons I have created myself using the excellent but sometimes flaw
|
|
|
514
471
|
[Amadine](https://apps.apple.com/us/app/amadine-vector-design-art/id1339198386?mt=12)
|
|
515
472
|
and before that [Graphic](https://apps.apple.com/us/app/graphic/id404705039?mt=12).
|
|
516
473
|
|
|
474
|
+
### Building icon data
|
|
475
|
+
|
|
476
|
+
Use [make-icon-data](/?make-icon-data.js) to generate icon data from SVG files.
|
|
477
|
+
It supports directional folder conventions to auto-generate rotated and flipped
|
|
478
|
+
variants, eliminating redundant SVGs.
|
|
479
|
+
|
|
517
480
|
### Feather Icons Copyright Notice
|
|
518
481
|
|
|
519
482
|
The MIT License (MIT)
|
|
@@ -573,6 +536,8 @@ export const svg2DataUrl = (icon, fill, stroke, strokeWidth) => {
|
|
|
573
536
|
const text = encodeURIComponent(svg.outerHTML);
|
|
574
537
|
return `url(data:image/svg+xml;charset=UTF-8,${text})`;
|
|
575
538
|
};
|
|
539
|
+
/** Returns icon name safe for suffix concatenation (appends _ if name ends in digit) */
|
|
540
|
+
export const safeIconSuffix = (name) => /\d$/.test(name) ? name + '_' : name;
|
|
576
541
|
export const iconRules = [
|
|
577
542
|
{
|
|
578
543
|
prefix: /^spin(_?\d+)/,
|
|
@@ -580,40 +545,38 @@ export const iconRules = [
|
|
|
580
545
|
const dps = match[1].replace('_', '-');
|
|
581
546
|
const duration = 360 / Math.abs(parseFloat(dps));
|
|
582
547
|
const direction = dps.startsWith('-') ? 'reverse' : 'normal';
|
|
583
|
-
//
|
|
584
|
-
const
|
|
585
|
-
const iconName = parsed ? parsed.baseName : baseName;
|
|
586
|
-
const icon = resolveIcon(iconName, []);
|
|
548
|
+
// Resolve baseName with suffixes intact — they apply to the icon
|
|
549
|
+
const icon = resolveIcon(baseName, []);
|
|
587
550
|
const el = icon;
|
|
588
551
|
if (el.animate) {
|
|
589
|
-
el.
|
|
552
|
+
const base = el.style.transform || '';
|
|
553
|
+
el.animate([
|
|
554
|
+
{ transform: `${base} rotate(0deg)` },
|
|
555
|
+
{ transform: `${base} rotate(360deg)` },
|
|
556
|
+
], {
|
|
590
557
|
duration: duration * 1000,
|
|
591
558
|
iterations: Infinity,
|
|
592
559
|
direction,
|
|
593
560
|
});
|
|
594
561
|
}
|
|
595
|
-
|
|
596
|
-
if (parsed) {
|
|
597
|
-
Object.assign(wrapper.style, parsed.style);
|
|
598
|
-
}
|
|
599
|
-
return wrapper;
|
|
562
|
+
return wrapIcon(baseName, parts, icon);
|
|
600
563
|
},
|
|
601
564
|
},
|
|
602
565
|
{
|
|
603
566
|
prefix: 'un',
|
|
604
|
-
apply: (baseName) => `slash25o$${baseName}75s75o`,
|
|
567
|
+
apply: (baseName) => `slash25o$${safeIconSuffix(baseName)}75s75o`,
|
|
605
568
|
},
|
|
606
569
|
{
|
|
607
570
|
prefix: 'check',
|
|
608
|
-
apply: (baseName) => `check75o_00aa00S$${baseName}75s50o`,
|
|
571
|
+
apply: (baseName) => `check75o_00aa00S$${safeIconSuffix(baseName)}75s50o`,
|
|
609
572
|
},
|
|
610
573
|
{
|
|
611
574
|
prefix: 'cancel',
|
|
612
|
-
apply: (baseName) => `x75o_cc0000S$${baseName}75s50o`,
|
|
575
|
+
apply: (baseName) => `x75o_cc0000S$${safeIconSuffix(baseName)}75s50o`,
|
|
613
576
|
},
|
|
614
577
|
{
|
|
615
578
|
prefix: 'search',
|
|
616
|
-
apply: (baseName) => `search80s30x30y$${baseName}50o`,
|
|
579
|
+
apply: (baseName) => `search80s30x30y$${safeIconSuffix(baseName)}50o`,
|
|
617
580
|
},
|
|
618
581
|
];
|
|
619
582
|
function makeIcon(spec, parts) {
|
|
@@ -706,21 +669,41 @@ function composeIcon(prop, parts) {
|
|
|
706
669
|
return null;
|
|
707
670
|
}
|
|
708
671
|
const MAX_REDIRECTS = 10;
|
|
709
|
-
// Style suffixes —
|
|
672
|
+
// Style suffixes — value then letter code:
|
|
710
673
|
// 50o (opacity), 75s (scale), 20x (translateX%), _10y (translateY%)
|
|
711
674
|
// 90r (rotate 90°), _45r (rotate -45°), 0f (flipH), 1f (flipV)
|
|
712
675
|
// _FF0000F (fill hex), _f00S (stroke hex), 3W (stroke-width)
|
|
713
676
|
// _brandColorF (fill var), _accentS (stroke var)
|
|
714
|
-
|
|
677
|
+
// Icon names ending in digits need _ separator: edit2_50o
|
|
678
|
+
const SUFFIX_RE = /(?:(?<=[a-zA-Z_])(?:_?\d+[osxyr]|[01]f|\d+W)|_[a-zA-Z0-9]+[FS])+$/;
|
|
715
679
|
function parseStyleSuffixes(name) {
|
|
716
680
|
const match = name.match(SUFFIX_RE);
|
|
717
681
|
if (!match)
|
|
718
682
|
return null;
|
|
719
|
-
|
|
683
|
+
let baseName = name.slice(0, match.index);
|
|
720
684
|
if (!baseName)
|
|
721
685
|
return null;
|
|
686
|
+
// Strip trailing _ separator (for digit-ending names: edit2_50o)
|
|
687
|
+
if (baseName.endsWith('_'))
|
|
688
|
+
baseName = baseName.slice(0, -1);
|
|
689
|
+
if (!baseName)
|
|
690
|
+
return null;
|
|
691
|
+
const data = iconData;
|
|
692
|
+
// Accept if baseName is in iconData, or matches a composition rule prefix
|
|
693
|
+
if (!data[baseName]) {
|
|
694
|
+
const matchesRule = iconRules.some((rule) => {
|
|
695
|
+
if (rule.prefix instanceof RegExp) {
|
|
696
|
+
return rule.prefix.test(baseName);
|
|
697
|
+
}
|
|
698
|
+
return baseName.startsWith(rule.prefix) && baseName.length > rule.prefix.length;
|
|
699
|
+
});
|
|
700
|
+
if (!matchesRule)
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
722
703
|
const style = {};
|
|
723
|
-
const suffixes = match[0].match(/_?\d
|
|
704
|
+
const suffixes = match[0].match(/_?\d+[osxyr]|[01]f|_[a-zA-Z0-9]+[FS]|\d+W/g);
|
|
705
|
+
if (!suffixes)
|
|
706
|
+
return null;
|
|
724
707
|
let tx = '';
|
|
725
708
|
let ty = '';
|
|
726
709
|
let scale = '';
|