@rogieking/figui3 2.32.0 → 2.33.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.
@@ -143,7 +143,7 @@ export default defineConfig({
143
143
  ### Documentation and Demos
144
144
 
145
145
  - Update `README.md` component docs when public API or behavior changes.
146
- - Update demo pages (`index.html` and `propkit.html` where relevant) for visible behavior changes.
146
+ - Update demo surfaces (`index.html` and `playground/` routes where relevant) for visible behavior changes.
147
147
  - Prefer realistic examples that mirror plugin/property panel usage.
148
148
  - If introducing an experimental feature, document activation and fallback behavior clearly.
149
149
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: propkit
3
- description: Guides creation and refinement of Figma-style property panel patterns ("PropKit") using FigUI3 components. Applies when building or modifying property fields in `propkit.html`, generating consistent field prompts, composing horizontal `fig-field` rows, or tuning panel UX for controls like image, color, fill, slider, switch, dropdown, segmented control, easing, and angle.
3
+ description: Guides creation and refinement of Figma-style property panel patterns ("PropKit") using FigUI3 components. Applies when building or modifying property fields in the playground app (`/propkit` route), generating consistent field prompts, composing horizontal `fig-field` rows, or tuning panel UX for controls like image, color, fill, slider, switch, dropdown, segmented control, easing, and angle.
4
4
  user-invocable: false
5
5
  ---
6
6
 
@@ -13,7 +13,7 @@ Patterns for composing clean, production-ready Figma property panels with FigUI3
13
13
  ## Current Project Context
14
14
 
15
15
  ```json
16
- !`node -e "const fs=require('fs'); const ok=fs.existsSync('propkit.html'); console.log(JSON.stringify({propkit:ok, example:'horizontal fig-field + label + fig-* control'},null,2))" 2>/dev/null || echo '{"error":"context unavailable"}'`
16
+ !`node -e "const fs=require('fs'); const ok=fs.existsSync('playground/src/main.tsx'); console.log(JSON.stringify({playground:ok, route:'/propkit', example:'horizontal fig-field + label + fig-* control'},null,2))" 2>/dev/null || echo '{"error":"context unavailable"}'`
17
17
  ```
18
18
 
19
19
  ## Principles
@@ -199,7 +199,7 @@ Use a horizontal fig-field, with a fig-slider, min=0 max=100 text=true units=%.
199
199
  - Confirm control choice matches intent (continuous vs discrete vs boolean vs exact numeric entry).
200
200
  - Verify row density and panel width feel consistent with existing PropKit sections.
201
201
  - Verify keyboard navigation and label association for every field row.
202
- - Verify changes in `propkit.html` still mirror recommended patterns in this skill.
202
+ - Verify changes in `playground/src/data/sections.ts` still mirror recommended patterns in this skill.
203
203
 
204
204
  ## Quick Reference
205
205
 
@@ -218,7 +218,7 @@ Common PropKit controls:
218
218
 
219
219
  ## Primary Files
220
220
 
221
- - `propkit.html` - canonical PropKit examples and prompt-copy behavior
221
+ - `playground/src/data/sections.ts` - canonical PropKit examples and prompt-copy behavior
222
222
  - `fig.js` - control behavior and emitted events
223
223
  - `components.css` - visual treatment and layout constraints
224
224
  - `README.md` - component API details and usage
package/README.md CHANGED
@@ -9,7 +9,7 @@ A lightweight, zero-dependency web components library for building Figma plugin
9
9
 
10
10
  View the interactive component documentation at **[rogie.github.io/figui3](https://rogie.github.io/figui3/)**
11
11
 
12
- The demo page (`index.html`) is included in the npm package, so you can also open it locally after installing.
12
+ The docs page source is kept in this repo as `old.html` for reference and local testing.
13
13
 
14
14
  ## Features
15
15
 
@@ -62,7 +62,26 @@ Or via esm.sh:
62
62
  git clone https://github.com/rogie/figui3.git
63
63
  cd figui3
64
64
  bun install
65
- bun dev # Opens documentation at http://localhost:3000
65
+ bun dev # Core component docs at http://localhost:3000
66
+ npm run dev:playground # Interactive playground app (routes: /figui3, /propkit)
67
+ npm run build:playground # Build playground app
68
+ bun build # Build dist/fig.js
69
+ ```
70
+
71
+ ### Playground (`/figui3` and `/propkit`)
72
+
73
+ The playground app is the fastest way to author and validate component markup.
74
+
75
+ - **`/figui3`**: component-focused examples and attribute controls for FigUI3 primitives.
76
+ - **`/propkit`**: property-panel patterns composed from FigUI3 controls.
77
+ - Live preview, attributes editing, and code view stay synchronized.
78
+ - Attribute controls write real component markup and preserve internal-only playground metadata where needed.
79
+
80
+ Open locally:
81
+
82
+ ```bash
83
+ npm run dev:playground
84
+ # then visit http://localhost:5173/figui3 or http://localhost:5173/propkit
66
85
  ```
67
86
 
68
87
  ## Quick Start
@@ -215,6 +234,42 @@ A modal dialog component with drag support.
215
234
 
216
235
  ---
217
236
 
237
+ ### Popup (`<fig-popup>`)
238
+
239
+ An anchored floating surface built on `<dialog>`, with collision-aware positioning and optional popover beak styling.
240
+
241
+ | Attribute | Type | Default | Description |
242
+ |-----------|------|---------|-------------|
243
+ | `anchor` | string | — | CSS selector for the anchor element |
244
+ | `position` | string | `"top center"` | Placement (`"top"`, `"left"`, `"center right"`, etc.) |
245
+ | `offset` | string | `"0 0"` | X/Y offset in px-like tokens (`"8 8"`) |
246
+ | `viewport-margin` | string | `"8"` | Viewport safety margin (CSS shorthand style) |
247
+ | `variant` | string | — | Use `"popover"` to render a beak |
248
+ | `theme` | string | — | `"light"`, `"dark"`, or `"menu"` |
249
+ | `closedby` | string | `"any"` | Dismiss behavior: `"any"`, `"closerequest"`, `"none"` |
250
+ | `open` | boolean/string | `false` | Open when present and not `"false"` |
251
+
252
+ ```html
253
+ <fig-button id="popup-anchor" onclick="const p=document.getElementById('demo-popup'); p.toggleAttribute('open');">
254
+ Toggle popup
255
+ </fig-button>
256
+
257
+ <dialog
258
+ id="demo-popup"
259
+ is="fig-popup"
260
+ anchor="#popup-anchor"
261
+ position="center right"
262
+ offset="8 8"
263
+ viewport-margin="8"
264
+ variant="popover"
265
+ closedby="none"
266
+ >
267
+ <fig-header><h3>Popup</h3></fig-header>
268
+ </dialog>
269
+ ```
270
+
271
+ ---
272
+
218
273
  ### Tabs (`<fig-tabs>` / `<fig-tab>`)
219
274
 
220
275
  Tabbed navigation component.
@@ -529,6 +584,7 @@ A form field wrapper with flexible layout. Automatically links `<label>` element
529
584
  | Attribute | Type | Default | Description |
530
585
  |-----------|------|---------|-------------|
531
586
  | `direction` | string | `"column"` | Layout: `"column"`, `"row"`, `"horizontal"` |
587
+ | `columns` | string | — | Optional horizontal split preset: `"thirds"` or `"half"` |
532
588
  | `label` | string | — | Programmatically set the label text |
533
589
 
534
590
  ```html
@@ -543,6 +599,12 @@ A form field wrapper with flexible layout. Automatically links `<label>` element
543
599
  <label>Volume</label>
544
600
  <fig-slider min="0" max="100" value="50"></fig-slider>
545
601
  </fig-field>
602
+
603
+ <!-- Horizontal with 1/3 + 2/3 split -->
604
+ <fig-field direction="horizontal" columns="thirds">
605
+ <label>Opacity</label>
606
+ <fig-slider value="50"></fig-slider>
607
+ </fig-field>
546
608
  ```
547
609
 
548
610
  ---
@@ -674,16 +736,13 @@ A toast notification component.
674
736
  | `duration` | number | `5000` | Auto-dismiss ms (0 = no dismiss) |
675
737
  | `offset` | number | `16` | Distance from bottom |
676
738
  | `theme` | string | `"dark"` | Theme: `"dark"`, `"light"`, `"danger"`, `"brand"` |
677
- | `open` | boolean | `false` | Whether visible |
678
739
 
679
740
  ```html
741
+ <fig-button onclick="document.getElementById('myToast').showToast()">Show toast</fig-button>
742
+
680
743
  <fig-toast id="myToast" theme="brand" duration="3000">
681
744
  Settings saved successfully!
682
745
  </fig-toast>
683
-
684
- <script>
685
- document.getElementById('myToast').show();
686
- </script>
687
746
  ```
688
747
 
689
748
  ---
package/components.css CHANGED
@@ -1212,8 +1212,6 @@ fig-chit {
1212
1212
  background-repeat: no-repeat;
1213
1213
  border-radius: 0.125rem;
1214
1214
  box-shadow: inset 0 0 0 1px var(--figma-color-bordertranslucent);
1215
- }
1216
- &[data-type="image"]::after {
1217
1215
  mask-image: linear-gradient(
1218
1216
  to right,
1219
1217
  black 0%,
@@ -2355,6 +2353,7 @@ dialog[is="fig-dialog"] {
2355
2353
 
2356
2354
  dialog[is="fig-popup"] {
2357
2355
  --z-index: 999999;
2356
+ --beak-offset: 1px;
2358
2357
  z-index: var(--z-index);
2359
2358
  position: fixed;
2360
2359
  margin: 0;
@@ -2419,13 +2418,13 @@ dialog[is="fig-popup"] {
2419
2418
  }
2420
2419
 
2421
2420
  &[data-beak-side="left"]:after {
2422
- left: 1px;
2421
+ left: 6px;
2423
2422
  top: var(--beak-offset, 50%);
2424
2423
  transform: translate(-100%, -50%) rotate(90deg);
2425
2424
  }
2426
2425
 
2427
2426
  &[data-beak-side="right"]:after {
2428
- left: calc(100% - 1px);
2427
+ left: calc(100% - 6px);
2429
2428
  top: var(--beak-offset, 50%);
2430
2429
  transform: translate(0, -50%) rotate(-90deg);
2431
2430
  }
@@ -2894,11 +2893,36 @@ fig-field,
2894
2893
  gap: var(--spacer-2);
2895
2894
  align-items: start;
2896
2895
  flex-direction: row;
2897
- width: auto;
2898
2896
 
2899
2897
  & > label {
2900
2898
  min-width: 4rem;
2901
2899
  }
2900
+ &[columns="thirds"] {
2901
+ display: grid;
2902
+ grid-template-columns: repeat(3, 1fr);
2903
+ gap: var(--spacer-2);
2904
+
2905
+ & > label {
2906
+ grid-column: 1;
2907
+ }
2908
+
2909
+ & > label ~ * {
2910
+ grid-column: 2 / span 2;
2911
+ }
2912
+ }
2913
+ &[columns="half"] {
2914
+ display: grid;
2915
+ grid-template-columns: repeat(2, 1fr);
2916
+ gap: var(--spacer-2);
2917
+
2918
+ & > label {
2919
+ grid-column: 1;
2920
+ }
2921
+
2922
+ & > label ~ * {
2923
+ grid-column: 2 / span 2;
2924
+ }
2925
+ }
2902
2926
  }
2903
2927
  }
2904
2928
 
@@ -3142,7 +3166,7 @@ fig-input-angle {
3142
3166
 
3143
3167
  /* Layer */
3144
3168
  fig-layer {
3145
- --indent: var(--spacer-4);
3169
+ --indent: var(--spacer-2);
3146
3170
  display: block;
3147
3171
  color: var(--figma-color-text);
3148
3172
  position: relative;
@@ -3154,14 +3178,11 @@ fig-layer {
3154
3178
  & > .fig-layer-row {
3155
3179
  & > .fig-layer-chevron {
3156
3180
  visibility: visible;
3181
+ margin-left: calc(-1 * (var(--spacer-3) + var(--spacer-3) + 6px));
3182
+ margin-right: 0;
3157
3183
  }
3158
3184
  }
3159
3185
  }
3160
- &:not(:has(fig-layer)):not(:has(.fig-layer-icon)) {
3161
- & > .fig-layer-row {
3162
- padding-left: var(--spacer-3);
3163
- }
3164
- }
3165
3186
 
3166
3187
  &:not(:has(.fig-layer-icon)) {
3167
3188
  & > .fig-layer-row {
@@ -3186,7 +3207,7 @@ fig-layer {
3186
3207
  mask-position: center;
3187
3208
  display: flex;
3188
3209
  visibility: hidden;
3189
- margin-left: calc(-1 * (var(--spacer-3) + var(--spacer-1) + 2px));
3210
+ margin-left: calc(-1 * (var(--spacer-3) + var(--spacer-3)));
3190
3211
  margin-right: calc(-1 * (var(--spacer-1) + 2px));
3191
3212
  background: var(--figma-color-text-tertiary);
3192
3213
  width: var(--spacer-3);
package/fig.js CHANGED
@@ -1813,17 +1813,24 @@ class FigPopup extends HTMLDialogElement {
1813
1813
 
1814
1814
  const anchorCenterX = anchorRect.left + anchorRect.width / 2;
1815
1815
  const anchorCenterY = anchorRect.top + anchorRect.height / 2;
1816
+ const measuredRect = this.getBoundingClientRect();
1817
+ const rect =
1818
+ measuredRect.width > 0 && measuredRect.height > 0 ? measuredRect : popupRect;
1819
+ // Always use the rendered popup rect so beak alignment matches real final placement.
1820
+ const resolvedLeft = rect.left;
1821
+ const resolvedTop = rect.top;
1822
+ const edgeInset = 10;
1816
1823
 
1817
1824
  let beakOffset;
1818
1825
  if (beakSide === "top" || beakSide === "bottom") {
1819
- beakOffset = anchorCenterX - left;
1820
- const min = 8;
1821
- const max = Math.max(min, popupRect.width - 8);
1826
+ beakOffset = anchorCenterX - resolvedLeft;
1827
+ const min = edgeInset;
1828
+ const max = Math.max(min, rect.width - edgeInset);
1822
1829
  beakOffset = Math.min(max, Math.max(min, beakOffset));
1823
1830
  } else {
1824
- beakOffset = anchorCenterY - top;
1825
- const min = 8;
1826
- const max = Math.max(min, popupRect.height - 8);
1831
+ beakOffset = anchorCenterY - resolvedTop;
1832
+ const min = edgeInset;
1833
+ const max = Math.max(min, rect.height - edgeInset);
1827
1834
  beakOffset = Math.min(max, Math.max(min, beakOffset));
1828
1835
  }
1829
1836
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "2.32.0",
3
+ "version": "2.33.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",
@@ -12,16 +12,13 @@
12
12
  "./fig.js": "./fig.js",
13
13
  "./fig.css": "./fig.css",
14
14
  "./base.css": "./base.css",
15
- "./components.css": "./components.css",
16
- "./propkit.html": "./propkit.html"
15
+ "./components.css": "./components.css"
17
16
  },
18
17
  "files": [
19
18
  "fig.js",
20
19
  "fig.css",
21
20
  "base.css",
22
21
  "components.css",
23
- "index.html",
24
- "propkit.html",
25
22
  "dist/",
26
23
  ".cursor/skills/",
27
24
  "README.md",
@@ -33,8 +30,8 @@
33
30
  "scripts": {
34
31
  "dev": "bun --hot server.ts",
35
32
  "build": "bun build fig.js --minify --outdir dist",
36
- "dev:propkit": "cd propkit && npm run dev",
37
- "build:propkit": "cd propkit && npm run build"
33
+ "dev:playground": "cd playground && npm run dev",
34
+ "build:playground": "cd playground && npm run build"
38
35
  },
39
36
  "repository": {
40
37
  "type": "git",