@taybart/corvid 0.2.0 → 0.2.3

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/style.d.ts CHANGED
@@ -3,7 +3,56 @@
3
3
  **************/
4
4
  export declare function cssVar(name: string): string;
5
5
  export declare function render(style: Object): string;
6
- export declare function inject(selector: string, style: Object): string;
6
+ /**
7
+ * A block of css declarations. A value that is itself an object is a nested
8
+ * rule: the key is a selector (`&:focus`, `input`) or an at-rule (`@media ...`)
9
+ * rather than a property.
10
+ */
11
+ export type declarations = {
12
+ [key: string]: string | number | declarations;
13
+ };
14
+ /**
15
+ * Render a declaration block to css without touching the document.
16
+ */
17
+ export declare function css(selector: string, style: declarations): string;
18
+ /**
19
+ * Install a stylesheet for `selector`. Nested objects become nested rules, so
20
+ * a component declares its styles in one call:
21
+ *
22
+ * inject('x-search-form', {
23
+ * marginTop: '33vh',
24
+ * input: {
25
+ * border: 'none',
26
+ * '&:focus': { borderBottomColor: 'var(--ring)' },
27
+ * },
28
+ * })
29
+ *
30
+ * Injecting the same key again replaces that sheet rather than adding to it;
31
+ * `key` defaults to the selector.
32
+ * @return the generated css
33
+ */
34
+ export declare function inject(selector: string, style: declarations, { key }?: {
35
+ key?: string;
36
+ }): string;
37
+ /**
38
+ * Compile `style` for a shadow root, rooted at `:host`.
39
+ *
40
+ * Hands back a constructable sheet where the platform has them — one per key,
41
+ * shared by every instance of the component, so a thousand cards cost one
42
+ * sheet — and the css text otherwise, for the caller to put in a `<style>`
43
+ * inside the root. Re-compiling an existing key replaces that sheet's contents
44
+ * in place, so live instances pick the change up (which is what makes an HMR
45
+ * reload work rather than stack a second sheet).
46
+ *
47
+ * Document styles do not reach into a shadow tree, so this is the only way a
48
+ * shadowed component gets styled at all.
49
+ */
50
+ export declare function shadowSheet(key: string, style: declarations): CSSStyleSheet | string;
51
+ /**
52
+ * Remove a stylesheet installed by `inject`
53
+ * @param key - the key it was injected under (the selector, unless overridden)
54
+ */
55
+ export declare function eject(key: string): void;
7
56
  /**
8
57
  * Check if the current theme is dark
9
58
  */
package/dist/style.js CHANGED
@@ -10,17 +10,161 @@ function render(style) {
10
10
  Object.entries(style).forEach(([k, v])=>s += `${toKebab(k)}:${v};`);
11
11
  return s;
12
12
  }
13
- function inject(selector, style) {
14
- const existing = document.getElementById(`corvid-injected-${encodeURIComponent(selector)}`);
15
- if (existing) existing.remove();
16
- const injected = document.createElement('style');
17
- injected.id = `corvid-injected-${encodeURIComponent(selector)}`;
18
- let s = `${selector} {\n`;
19
- Object.entries(style).forEach(([k, v])=>s += `${toKebab(k)}: ${v};\n`);
20
- s += '}';
21
- injected.textContent = s;
22
- document.head.appendChild(injected);
23
- return s;
13
+ function splitSelectors(selector) {
14
+ const out = [];
15
+ let depth = 0;
16
+ let quote = '';
17
+ let current = '';
18
+ for (const ch of selector){
19
+ if (quote) {
20
+ if (ch === quote) quote = '';
21
+ } else if ('"' === ch || "'" === ch) quote = ch;
22
+ else if ('(' === ch || '[' === ch) depth++;
23
+ else if (')' === ch || ']' === ch) depth--;
24
+ else if (',' === ch && 0 === depth) {
25
+ if (current.trim()) out.push(current.trim());
26
+ current = '';
27
+ continue;
28
+ }
29
+ current += ch;
30
+ }
31
+ if (current.trim()) out.push(current.trim());
32
+ return out;
33
+ }
34
+ function hostCompound(selector) {
35
+ if (!selector.startsWith(':host')) return selector;
36
+ let i = 5;
37
+ let inner = '';
38
+ if ('(' === selector[i]) {
39
+ const open = i;
40
+ let depth = 0;
41
+ let quote = '';
42
+ while(i < selector.length){
43
+ const ch = selector[i];
44
+ if (quote) {
45
+ if (ch === quote) quote = '';
46
+ } else if ('"' === ch || "'" === ch) quote = ch;
47
+ else if ('(' === ch) depth++;
48
+ else if (')' === ch) {
49
+ depth--;
50
+ if (0 === depth) {
51
+ i++;
52
+ break;
53
+ }
54
+ }
55
+ i++;
56
+ }
57
+ inner = selector.slice(open + 1, i - 1);
58
+ }
59
+ const start = i;
60
+ let depth = 0;
61
+ let quote = '';
62
+ while(i < selector.length){
63
+ const ch = selector[i];
64
+ if (quote) {
65
+ if (ch === quote) quote = '';
66
+ } else if ('"' === ch || "'" === ch) quote = ch;
67
+ else if (0 === depth && (' ' === ch || '>' === ch || '+' === ch || '~' === ch)) break;
68
+ else if (0 === depth && ':' === ch && ':' === selector[i + 1]) break;
69
+ else if ('(' === ch || '[' === ch) depth++;
70
+ else if (')' === ch || ']' === ch) depth--;
71
+ i++;
72
+ }
73
+ const compound = selector.slice(start, i);
74
+ if (!compound) return selector;
75
+ return `:host(${inner}${compound})${selector.slice(i)}`;
76
+ }
77
+ function resolve(parents, selector) {
78
+ const out = [];
79
+ for (const parent of parents)for (const part of splitSelectors(selector))out.push(hostCompound(part.includes('&') ? part.replaceAll('&', parent) : `${parent} ${part}`));
80
+ return out;
81
+ }
82
+ function compile(parents, decls, indent = '') {
83
+ const blocks = [];
84
+ const props = [];
85
+ const nested = [];
86
+ for (const [k, v] of Object.entries(decls))if (null !== v && 'object' == typeof v) nested.push([
87
+ k,
88
+ v
89
+ ]);
90
+ else props.push(`${indent} ${toKebab(k)}: ${v};`);
91
+ if (props.length) blocks.push(`${indent}${parents.join(', ')} {\n${props.join('\n')}\n${indent}}`);
92
+ for (const [selector, block] of nested){
93
+ if (selector.startsWith('@')) {
94
+ const inner = selector.startsWith('@keyframes') ? Object.entries(block).flatMap(([step, d])=>null !== d && 'object' == typeof d ? compile([
95
+ step
96
+ ], d, `${indent} `) : []) : compile(parents, block, `${indent} `);
97
+ if (inner.length) blocks.push(`${indent}${selector} {\n${inner.join('\n')}\n${indent}}`);
98
+ continue;
99
+ }
100
+ blocks.push(...compile(resolve(parents, selector), block, indent));
101
+ }
102
+ return blocks;
103
+ }
104
+ function css(selector, style) {
105
+ return compile(splitSelectors(selector), style).join('\n');
106
+ }
107
+ function registry() {
108
+ const doc = document;
109
+ if (!doc.__corvidSheets) doc.__corvidSheets = new Map();
110
+ return doc.__corvidSheets;
111
+ }
112
+ function adoptable() {
113
+ return 'undefined' != typeof CSSStyleSheet && 'replaceSync' in CSSStyleSheet.prototype && 'undefined' != typeof Document && 'adoptedStyleSheets' in Document.prototype;
114
+ }
115
+ function styleTag(key) {
116
+ const tagged = Array.from(document.head.querySelectorAll('style[data-corvid]'));
117
+ for (const node of tagged)if (node.dataset.corvid === key) return node;
118
+ const node = document.createElement('style');
119
+ node.dataset.corvid = key;
120
+ document.head.appendChild(node);
121
+ return node;
122
+ }
123
+ function inject(selector, style, { key = selector } = {}) {
124
+ const rules = css(selector, style);
125
+ const sheets = registry();
126
+ const existing = sheets.get(key);
127
+ if (existing) {
128
+ if ('replaceSync' in existing) existing.replaceSync(rules);
129
+ else existing.textContent = rules;
130
+ return rules;
131
+ }
132
+ if (adoptable()) {
133
+ const sheet = new CSSStyleSheet();
134
+ sheet.replaceSync(rules);
135
+ document.adoptedStyleSheets = [
136
+ ...document.adoptedStyleSheets,
137
+ sheet
138
+ ];
139
+ sheets.set(key, sheet);
140
+ return rules;
141
+ }
142
+ const node = styleTag(key);
143
+ node.textContent = rules;
144
+ sheets.set(key, node);
145
+ return rules;
146
+ }
147
+ function shadowSheet(key, style) {
148
+ const rules = css(':host', style);
149
+ if ('undefined' == typeof CSSStyleSheet || !('replaceSync' in CSSStyleSheet.prototype) || 'undefined' == typeof ShadowRoot || !('adoptedStyleSheets' in ShadowRoot.prototype)) return rules;
150
+ const sheets = registry();
151
+ const existing = sheets.get(key);
152
+ if (existing && 'replaceSync' in existing) {
153
+ existing.replaceSync(rules);
154
+ return existing;
155
+ }
156
+ const sheet = new CSSStyleSheet();
157
+ sheet.replaceSync(rules);
158
+ sheets.set(key, sheet);
159
+ return sheet;
160
+ }
161
+ function eject(key) {
162
+ const sheets = registry();
163
+ const sheet = sheets.get(key);
164
+ if (!sheet) return;
165
+ if ('replaceSync' in sheet) document.adoptedStyleSheets = document.adoptedStyleSheets.filter((s)=>s !== sheet);
166
+ else sheet.remove();
167
+ sheets.delete(key);
24
168
  }
25
169
  function isDarkMode() {
26
170
  return window.matchMedia('(prefers-color-scheme: dark)').matches;
@@ -46,4 +190,4 @@ function gradient(start, end, value) {
46
190
  const blue = Math.round(start.blue + (end.blue - start.blue) * value / 100);
47
191
  return `rgb(${red}, ${green}, ${blue})`;
48
192
  }
49
- export { cssVar, gradient, handleThemeSwitch, inject, isDarkMode, onDarkMode, render, switchTheme };
193
+ export { css, cssVar, eject, gradient, handleThemeSwitch, inject, isDarkMode, onDarkMode, render, shadowSheet, switchTheme };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taybart/corvid",
3
- "version": "0.2.0",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {