cotomy 2.0.3 → 2.0.5
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 +6 -1
- package/dist/browser/cotomy.js +224 -7
- 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/api.js +8 -3
- package/dist/esm/api.js.map +1 -1
- package/dist/esm/page.js +2 -1
- package/dist/esm/page.js.map +1 -1
- package/dist/esm/view.js +214 -3
- package/dist/esm/view.js.map +1 -1
- package/dist/types/api.d.ts +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"
|
|
@@ -287,11 +290,13 @@ const view = new CotomyViewRenderer(
|
|
|
287
290
|
new CotomyElement(document.querySelector("#profile")!)
|
|
288
291
|
);
|
|
289
292
|
|
|
290
|
-
await view.applyAsync(apiResponse); //
|
|
293
|
+
await view.applyAsync(apiResponse); // CotomyApiResponse from CotomyApi, or a plain object payload
|
|
291
294
|
// <span data-cotomy-bind="user.birthday" data-cotomy-bindtype="date" data-cotomy-format="MMM D, YYYY"></span>
|
|
292
295
|
// → renders localized date text if the API payload contains user.birthday
|
|
293
296
|
```
|
|
294
297
|
|
|
298
|
+
`applyAsync` accepts either a `CotomyApiResponse` from `CotomyApi` or a plain object payload.
|
|
299
|
+
|
|
295
300
|
#### Array binding
|
|
296
301
|
|
|
297
302
|
- Both `CotomyViewRenderer.applyAsync` and `CotomyEntityFillApiForm.fillAsync` resolve array elements by index via the active `ICotomyBindNameGenerator` (dot style → `items[0].name`, bracket style → `items[0][name]`).
|
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;
|
|
@@ -3284,10 +3495,15 @@ class CotomyViewRenderer {
|
|
|
3284
3495
|
}
|
|
3285
3496
|
}
|
|
3286
3497
|
async applyAsync(respose) {
|
|
3287
|
-
if (
|
|
3288
|
-
|
|
3498
|
+
if (respose instanceof CotomyApiResponse) {
|
|
3499
|
+
if (!respose.available) {
|
|
3500
|
+
throw new Error("Response is not available.");
|
|
3501
|
+
}
|
|
3502
|
+
await this.applyObjectAsync(await respose.objectAsync());
|
|
3503
|
+
}
|
|
3504
|
+
else {
|
|
3505
|
+
await this.applyObjectAsync(respose);
|
|
3289
3506
|
}
|
|
3290
|
-
await this.applyObjectAsync(await respose.objectAsync());
|
|
3291
3507
|
return this;
|
|
3292
3508
|
}
|
|
3293
3509
|
}
|
|
@@ -3970,7 +4186,8 @@ class CotomyPageController {
|
|
|
3970
4186
|
async initializeAsync() {
|
|
3971
4187
|
this.initializeDateTimeElements();
|
|
3972
4188
|
CotomyWindow.instance.pageshow(async (e) => {
|
|
3973
|
-
|
|
4189
|
+
const navType = performance.getEntriesByType("navigation")[0]?.type;
|
|
4190
|
+
if (e.persisted || navType === "back_forward") {
|
|
3974
4191
|
await this.restoreAsync();
|
|
3975
4192
|
if (CotomyWindow.instance.reloading) {
|
|
3976
4193
|
e.stopImmediatePropagation();
|