@rogieking/figui3 2.29.3 → 2.30.1

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.
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: nested-css-selectors
3
+ description: Implements CSS nesting and advanced selectors with progressive enhancement and maintainable specificity. Use when writing or refactoring nested CSS, or when the user mentions :has, :is, :where, :not, or complex selector composition.
4
+ user-invocable: false
5
+ ---
6
+
7
+ # Nested CSS + Selectors
8
+
9
+ ## Purpose
10
+
11
+ Write modern, readable CSS using nesting and selector composition without breaking fallback behavior or creating specificity traps.
12
+
13
+ ## Core Rules
14
+
15
+ 1. Start with a stable baseline rule first.
16
+ 2. Use nesting to improve readability, not to increase selector depth.
17
+ 3. Keep selector chains short; avoid deeply coupled DOM paths.
18
+ 4. Prefer low-specificity patterns (`:where(...)`) for reusable component styles.
19
+ 5. Gate risky/brand-new behavior behind `@supports selector(...)` when needed.
20
+
21
+ ## Nesting Guidelines
22
+
23
+ - Nest only when the child selector is semantically tied to the parent block.
24
+ - Keep nesting to ~2 levels in normal component styles.
25
+ - Use `&` explicitly for pseudo-classes, variants, and stateful modifiers.
26
+ - Avoid implicit descendant nesting that recreates fragile long selectors.
27
+
28
+ ```css
29
+ .card {
30
+ padding: 12px;
31
+
32
+ &__title {
33
+ font-weight: 600;
34
+ }
35
+
36
+ &:hover {
37
+ background: var(--figma-color-bg-secondary);
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Advanced Selector Guidelines
43
+
44
+ - Use `:is(...)` to group alternatives without repeating declarations.
45
+ - Use `:where(...)` when you want grouping with near-zero specificity cost.
46
+ - Use `:has(...)` for parent/state relationships that previously required JS classes.
47
+ - Keep `:not(...)` small and explicit; avoid broad negative matching.
48
+
49
+ ```css
50
+ .field :is(input, select, textarea) {
51
+ font: inherit;
52
+ }
53
+
54
+ .panel :where(button, [role="button"]) {
55
+ cursor: pointer;
56
+ }
57
+ ```
58
+
59
+ ## Progressive Enhancement Pattern
60
+
61
+ ```css
62
+ /* baseline behavior */
63
+ .form-row {
64
+ border: 1px solid var(--figma-color-border);
65
+ }
66
+
67
+ /* enhanced behavior only where supported */
68
+ @supports selector(.form-row:has(input:focus-visible)) {
69
+ .form-row:has(input:focus-visible) {
70
+ border-color: var(--figma-color-border-selected);
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Specificity Guardrails
76
+
77
+ - Prefer class-based selectors over element+class combinations unless necessary.
78
+ - Avoid IDs in component styling.
79
+ - If a rule should be easy to override, wrap grouping parts in `:where(...)`.
80
+ - If a selector becomes hard to reason about, split it into two simpler rules.
81
+
82
+ ## Refactor Workflow
83
+
84
+ 1. Identify repeated selector/declaration blocks.
85
+ 2. Consolidate with `:is(...)` or `:where(...)` where intent matches.
86
+ 3. Introduce nesting inside the owning component block.
87
+ 4. Recheck specificity effects (especially when replacing comma groups).
88
+ 5. Verify hover/focus/disabled/error states are unchanged.
89
+
90
+ ## Quick Checklist
91
+
92
+ - [ ] Baseline works without advanced selectors.
93
+ - [ ] Nesting depth remains shallow and readable.
94
+ - [ ] `:has(...)` usage is support-gated when compatibility matters.
95
+ - [ ] Specificity is intentional (`:where` for low-cost overrides).
96
+ - [ ] No fragile selector tied to incidental DOM structure.
package/components.css CHANGED
@@ -792,8 +792,10 @@ fig-button {
792
792
  color: var(--figma-color-text);
793
793
  box-shadow: 0 0 0 1px var(--figma-color-bordertranslucent);
794
794
 
795
- &:active {
795
+ &:active,
796
+ &:has(:active) {
796
797
  background-color: var(--figma-color-bg-secondary);
798
+ color: var(--figma-color-text);
797
799
  }
798
800
  }
799
801
 
@@ -1588,7 +1590,7 @@ fig-3d-rotate {
1588
1590
 
1589
1591
  & > label {
1590
1592
  align-self: stretch;
1591
- color: inherit;
1593
+ color: var(--figma-color-text);
1592
1594
  }
1593
1595
 
1594
1596
  & > * {
@@ -2030,8 +2032,7 @@ fig-slider {
2030
2032
  border: none;
2031
2033
  position: relative;
2032
2034
  z-index: 1;
2033
- cursor: grab;
2034
- cursor: -webkit-grab;
2035
+ cursor: default;
2035
2036
  box-shadow: var(--slider-handle-shadow);
2036
2037
  }
2037
2038
 
@@ -2105,8 +2106,7 @@ fig-slider {
2105
2106
  border: none;
2106
2107
  position: relative;
2107
2108
  z-index: 1;
2108
- cursor: grab;
2109
- cursor: -moz-grab;
2109
+ cursor: default;
2110
2110
  box-shadow: var(--slider-handle-shadow);
2111
2111
  transition: var(--handle-transition);
2112
2112
  }
package/fig.js CHANGED
@@ -5237,8 +5237,11 @@ class FigImage extends HTMLElement {
5237
5237
  }
5238
5238
  connectedCallback() {
5239
5239
  this.#src = this.getAttribute("src") || "";
5240
- this.upload = this.getAttribute("upload") === "true";
5241
- this.download = this.getAttribute("download") === "true";
5240
+ this.upload =
5241
+ this.hasAttribute("upload") && this.getAttribute("upload") !== "false";
5242
+ this.download =
5243
+ this.hasAttribute("download") &&
5244
+ this.getAttribute("download") !== "false";
5242
5245
  this.label = this.getAttribute("label") || "Upload";
5243
5246
  this.size = this.getAttribute("size") || "small";
5244
5247
  this.innerHTML = this.#getInnerHTML();
@@ -5377,7 +5380,7 @@ class FigImage extends HTMLElement {
5377
5380
  this.setAttribute("src", this.blob);
5378
5381
  }
5379
5382
  static get observedAttributes() {
5380
- return ["src", "upload", "aspect-ratio", "fit"];
5383
+ return ["src", "upload", "download", "aspect-ratio", "fit"];
5381
5384
  }
5382
5385
  get src() {
5383
5386
  return this.#src;
@@ -5400,9 +5403,13 @@ class FigImage extends HTMLElement {
5400
5403
  this.#loadImage(this.#src);
5401
5404
  }
5402
5405
  }
5403
- if (name === "upload" || name === "download") {
5404
- this.upload = newValue.toLowerCase() === "true";
5405
- this.download = newValue.toLowerCase() === "true";
5406
+ if (name === "upload") {
5407
+ this.upload = newValue !== null && newValue !== "false";
5408
+ this.innerHTML = this.#getInnerHTML();
5409
+ this.#updateRefs();
5410
+ }
5411
+ if (name === "download") {
5412
+ this.download = newValue !== null && newValue !== "false";
5406
5413
  this.innerHTML = this.#getInnerHTML();
5407
5414
  this.#updateRefs();
5408
5415
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.29.3",
3
+ "version": "2.30.1",
4
4
  "description": "A lightweight web components library for building Figma plugin and widget UIs with native look and feel",
5
5
  "author": "Rogie King",
6
6
  "license": "MIT",
@@ -32,7 +32,9 @@
32
32
  ],
33
33
  "scripts": {
34
34
  "dev": "bun --hot server.ts",
35
- "build": "bun build fig.js --minify --outdir dist"
35
+ "build": "bun build fig.js --minify --outdir dist",
36
+ "dev:propkit": "cd propkit && npm run dev",
37
+ "build:propkit": "cd propkit && npm run build"
36
38
  },
37
39
  "repository": {
38
40
  "type": "git",
@@ -71,7 +73,8 @@
71
73
  "a11y"
72
74
  ],
73
75
  "devDependencies": {
74
- "@types/bun": "latest"
76
+ "@types/bun": "latest",
77
+ "playwright": "^1.58.2"
75
78
  },
76
79
  "peerDependencies": {
77
80
  "typescript": "^5.0.0"