cotomy 2.0.3 → 2.0.4
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/README.md +3 -0
- package/dist/browser/cotomy.js +214 -3
- package/dist/browser/cotomy.js.map +1 -1
- package/dist/browser/cotomy.min.js +1 -1
- package/dist/browser/cotomy.min.js.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/view.js +214 -3
- package/dist/esm/view.js.map +1 -1
- package/dist/types/view.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -149,6 +149,9 @@ Scoped CSS sharing/re-hydrationとインスタンス単位のイベント管理
|
|
|
149
149
|
|
|
150
150
|
```bash
|
|
151
151
|
npm test -- --run tests/view.spec.ts -t "constructs from multiple sources and applies scoped css"
|
|
152
|
+
npm test -- --run tests/view.spec.ts -t "auto-prefixes [root] when no scope placeholder is present"
|
|
153
|
+
npm test -- --run tests/view.spec.ts -t "auto-prefixes every selector without a scope placeholder"
|
|
154
|
+
npm test -- --run tests/view.spec.ts -t "scopes nested rules while preserving non-style at-rules"
|
|
152
155
|
npm test -- --run tests/view.spec.ts -t "preserves scope ids when cloning, including descendants"
|
|
153
156
|
npm test -- --run tests/view.spec.ts -t "regenerates instance ids and lifecycle hooks when cloning"
|
|
154
157
|
npm test -- --run tests/view.spec.ts -t "keeps event handlers isolated by instance even when sharing scope"
|
package/dist/browser/cotomy.js
CHANGED
|
@@ -1690,9 +1690,7 @@ class CotomyElement {
|
|
|
1690
1690
|
const cssid = this.scopedCssElementId;
|
|
1691
1691
|
CotomyElement.find(`#${cssid}`).forEach(e => e.remove());
|
|
1692
1692
|
const element = document.createElement("style");
|
|
1693
|
-
const
|
|
1694
|
-
const normalizedCss = hasRoot ? css : `[root] ${css}`;
|
|
1695
|
-
const writeCss = normalizedCss.replace(/\[root\]/g, `[data-cotomy-scopeid="${this.scopeId}"]`);
|
|
1693
|
+
const writeCss = this.createScopedCss(css);
|
|
1696
1694
|
const node = document.createTextNode(writeCss);
|
|
1697
1695
|
element.appendChild(node);
|
|
1698
1696
|
element.id = cssid;
|
|
@@ -1708,6 +1706,219 @@ class CotomyElement {
|
|
|
1708
1706
|
}
|
|
1709
1707
|
return this;
|
|
1710
1708
|
}
|
|
1709
|
+
createScopedCss(css) {
|
|
1710
|
+
return this.scopeCssRules(css);
|
|
1711
|
+
}
|
|
1712
|
+
scopeCssRules(css) {
|
|
1713
|
+
let scoped = "";
|
|
1714
|
+
let cursor = 0;
|
|
1715
|
+
while (cursor < css.length) {
|
|
1716
|
+
const open = this.findNextCssBlockStart(css, cursor);
|
|
1717
|
+
if (open < 0) {
|
|
1718
|
+
scoped += css.slice(cursor);
|
|
1719
|
+
break;
|
|
1720
|
+
}
|
|
1721
|
+
const prelude = css.slice(cursor, open);
|
|
1722
|
+
const statementEnd = this.findCssStatementEnd(prelude);
|
|
1723
|
+
if (prelude.trimStart().startsWith("@") && statementEnd >= 0) {
|
|
1724
|
+
scoped += css.slice(cursor, cursor + statementEnd + 1);
|
|
1725
|
+
cursor += statementEnd + 1;
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
const close = this.findCssBlockEnd(css, open);
|
|
1729
|
+
if (close < 0) {
|
|
1730
|
+
scoped += css.slice(cursor);
|
|
1731
|
+
break;
|
|
1732
|
+
}
|
|
1733
|
+
const body = css.slice(open + 1, close);
|
|
1734
|
+
scoped += this.scopeCssPrelude(prelude) + "{";
|
|
1735
|
+
scoped += this.shouldScopeNestedCss(prelude) ? this.scopeCssRules(body) : body;
|
|
1736
|
+
scoped += "}";
|
|
1737
|
+
cursor = close + 1;
|
|
1738
|
+
}
|
|
1739
|
+
return scoped;
|
|
1740
|
+
}
|
|
1741
|
+
scopeCssPrelude(prelude) {
|
|
1742
|
+
const trimmed = prelude.trimStart();
|
|
1743
|
+
if (trimmed.startsWith("@"))
|
|
1744
|
+
return prelude;
|
|
1745
|
+
return this.scopeSelectorList(prelude);
|
|
1746
|
+
}
|
|
1747
|
+
shouldScopeNestedCss(prelude) {
|
|
1748
|
+
const atRule = prelude.trimStart().match(/^@([a-z-]+)/i)?.[1].toLowerCase();
|
|
1749
|
+
return atRule === "media"
|
|
1750
|
+
|| atRule === "supports"
|
|
1751
|
+
|| atRule === "container"
|
|
1752
|
+
|| atRule === "layer"
|
|
1753
|
+
|| atRule === "document";
|
|
1754
|
+
}
|
|
1755
|
+
scopeSelectorList(selectors) {
|
|
1756
|
+
return this.splitCssSelectorList(selectors).map(selector => {
|
|
1757
|
+
if (!selector.trim())
|
|
1758
|
+
return selector;
|
|
1759
|
+
const leading = selector.match(/^\s*/)?.[0] ?? "";
|
|
1760
|
+
const trailing = selector.match(/\s*$/)?.[0] ?? "";
|
|
1761
|
+
const body = selector.slice(leading.length, selector.length - trailing.length);
|
|
1762
|
+
if (/\[root\]/.test(body)) {
|
|
1763
|
+
return leading + body.replace(/\[root\]/g, this.scopedRootSelector) + trailing;
|
|
1764
|
+
}
|
|
1765
|
+
return `${leading}${this.scopedRootSelector} ${body}${trailing}`;
|
|
1766
|
+
}).join(",");
|
|
1767
|
+
}
|
|
1768
|
+
splitCssSelectorList(selectors) {
|
|
1769
|
+
const result = [];
|
|
1770
|
+
let start = 0;
|
|
1771
|
+
let stringQuote = null;
|
|
1772
|
+
let bracketDepth = 0;
|
|
1773
|
+
let parenthesisDepth = 0;
|
|
1774
|
+
for (let i = 0; i < selectors.length; i++) {
|
|
1775
|
+
const char = selectors[i];
|
|
1776
|
+
const next = selectors[i + 1];
|
|
1777
|
+
if (stringQuote) {
|
|
1778
|
+
if (char === "\\") {
|
|
1779
|
+
i++;
|
|
1780
|
+
}
|
|
1781
|
+
else if (char === stringQuote) {
|
|
1782
|
+
stringQuote = null;
|
|
1783
|
+
}
|
|
1784
|
+
continue;
|
|
1785
|
+
}
|
|
1786
|
+
if (char === "/" && next === "*") {
|
|
1787
|
+
i = selectors.indexOf("*/", i + 2);
|
|
1788
|
+
if (i < 0)
|
|
1789
|
+
break;
|
|
1790
|
+
i++;
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
if (char === "\"" || char === "'") {
|
|
1794
|
+
stringQuote = char;
|
|
1795
|
+
}
|
|
1796
|
+
else if (char === "[") {
|
|
1797
|
+
bracketDepth++;
|
|
1798
|
+
}
|
|
1799
|
+
else if (char === "]") {
|
|
1800
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
1801
|
+
}
|
|
1802
|
+
else if (char === "(") {
|
|
1803
|
+
parenthesisDepth++;
|
|
1804
|
+
}
|
|
1805
|
+
else if (char === ")") {
|
|
1806
|
+
parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
1807
|
+
}
|
|
1808
|
+
else if (char === "," && bracketDepth === 0 && parenthesisDepth === 0) {
|
|
1809
|
+
result.push(selectors.slice(start, i));
|
|
1810
|
+
start = i + 1;
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
result.push(selectors.slice(start));
|
|
1814
|
+
return result;
|
|
1815
|
+
}
|
|
1816
|
+
findCssStatementEnd(css) {
|
|
1817
|
+
let stringQuote = null;
|
|
1818
|
+
let parenthesisDepth = 0;
|
|
1819
|
+
for (let i = 0; i < css.length; i++) {
|
|
1820
|
+
const char = css[i];
|
|
1821
|
+
const next = css[i + 1];
|
|
1822
|
+
if (stringQuote) {
|
|
1823
|
+
if (char === "\\") {
|
|
1824
|
+
i++;
|
|
1825
|
+
}
|
|
1826
|
+
else if (char === stringQuote) {
|
|
1827
|
+
stringQuote = null;
|
|
1828
|
+
}
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (char === "/" && next === "*") {
|
|
1832
|
+
i = css.indexOf("*/", i + 2);
|
|
1833
|
+
if (i < 0)
|
|
1834
|
+
return -1;
|
|
1835
|
+
i++;
|
|
1836
|
+
continue;
|
|
1837
|
+
}
|
|
1838
|
+
if (char === "\"" || char === "'") {
|
|
1839
|
+
stringQuote = char;
|
|
1840
|
+
}
|
|
1841
|
+
else if (char === "(") {
|
|
1842
|
+
parenthesisDepth++;
|
|
1843
|
+
}
|
|
1844
|
+
else if (char === ")") {
|
|
1845
|
+
parenthesisDepth = Math.max(0, parenthesisDepth - 1);
|
|
1846
|
+
}
|
|
1847
|
+
else if (char === ";" && parenthesisDepth === 0) {
|
|
1848
|
+
return i;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
return -1;
|
|
1852
|
+
}
|
|
1853
|
+
findNextCssBlockStart(css, start) {
|
|
1854
|
+
let stringQuote = null;
|
|
1855
|
+
for (let i = start; i < css.length; i++) {
|
|
1856
|
+
const char = css[i];
|
|
1857
|
+
const next = css[i + 1];
|
|
1858
|
+
if (stringQuote) {
|
|
1859
|
+
if (char === "\\") {
|
|
1860
|
+
i++;
|
|
1861
|
+
}
|
|
1862
|
+
else if (char === stringQuote) {
|
|
1863
|
+
stringQuote = null;
|
|
1864
|
+
}
|
|
1865
|
+
continue;
|
|
1866
|
+
}
|
|
1867
|
+
if (char === "/" && next === "*") {
|
|
1868
|
+
i = css.indexOf("*/", i + 2);
|
|
1869
|
+
if (i < 0)
|
|
1870
|
+
return -1;
|
|
1871
|
+
i++;
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
if (char === "\"" || char === "'") {
|
|
1875
|
+
stringQuote = char;
|
|
1876
|
+
}
|
|
1877
|
+
else if (char === "{") {
|
|
1878
|
+
return i;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return -1;
|
|
1882
|
+
}
|
|
1883
|
+
findCssBlockEnd(css, open) {
|
|
1884
|
+
let depth = 0;
|
|
1885
|
+
let stringQuote = null;
|
|
1886
|
+
for (let i = open; i < css.length; i++) {
|
|
1887
|
+
const char = css[i];
|
|
1888
|
+
const next = css[i + 1];
|
|
1889
|
+
if (stringQuote) {
|
|
1890
|
+
if (char === "\\") {
|
|
1891
|
+
i++;
|
|
1892
|
+
}
|
|
1893
|
+
else if (char === stringQuote) {
|
|
1894
|
+
stringQuote = null;
|
|
1895
|
+
}
|
|
1896
|
+
continue;
|
|
1897
|
+
}
|
|
1898
|
+
if (char === "/" && next === "*") {
|
|
1899
|
+
i = css.indexOf("*/", i + 2);
|
|
1900
|
+
if (i < 0)
|
|
1901
|
+
return -1;
|
|
1902
|
+
i++;
|
|
1903
|
+
continue;
|
|
1904
|
+
}
|
|
1905
|
+
if (char === "\"" || char === "'") {
|
|
1906
|
+
stringQuote = char;
|
|
1907
|
+
}
|
|
1908
|
+
else if (char === "{") {
|
|
1909
|
+
depth++;
|
|
1910
|
+
}
|
|
1911
|
+
else if (char === "}") {
|
|
1912
|
+
depth--;
|
|
1913
|
+
if (depth === 0)
|
|
1914
|
+
return i;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
return -1;
|
|
1918
|
+
}
|
|
1919
|
+
get scopedRootSelector() {
|
|
1920
|
+
return `[data-cotomy-scopeid="${this.scopeId}"]`;
|
|
1921
|
+
}
|
|
1711
1922
|
ensureScopedCss() {
|
|
1712
1923
|
if (!this._scopedCss || !this.stylable)
|
|
1713
1924
|
return;
|