@rogieking/figui3 3.5.0 → 3.7.0

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.
Files changed (4) hide show
  1. package/components.css +240 -33
  2. package/dist/fig.js +29 -29
  3. package/fig.js +278 -5
  4. package/package.json +1 -1
package/fig.js CHANGED
@@ -4012,6 +4012,268 @@ class FigField extends HTMLElement {
4012
4012
  }
4013
4013
  customElements.define("fig-field", FigField);
4014
4014
 
4015
+ /* Field + Slider wrapper */
4016
+ class FigFieldSlider extends HTMLElement {
4017
+ #field = null;
4018
+ #label = null;
4019
+ #slider = null;
4020
+ #observer = null;
4021
+ #managedSliderAttrs = new Set();
4022
+ #steppersSyncFrame = 0;
4023
+ #boundHandleSliderInput = null;
4024
+ #boundHandleSliderChange = null;
4025
+ #ignoredSliderAttrs = new Set(["variant", "color", "text", "full"]);
4026
+
4027
+ static get observedAttributes() {
4028
+ return ["label", "direction"];
4029
+ }
4030
+
4031
+ connectedCallback() {
4032
+ if (!this.#field) {
4033
+ this.#initialize();
4034
+ }
4035
+
4036
+ this.#syncField();
4037
+ this.#syncSliderAttributes();
4038
+ this.#bindSliderEvents();
4039
+
4040
+ if (!this.#observer) {
4041
+ this.#observer = new MutationObserver((mutations) => {
4042
+ let syncField = false;
4043
+ let syncSlider = false;
4044
+
4045
+ for (const mutation of mutations) {
4046
+ if (mutation.type === "attributes") {
4047
+ if (
4048
+ mutation.attributeName &&
4049
+ this.#ignoredSliderAttrs.has(mutation.attributeName)
4050
+ ) {
4051
+ continue;
4052
+ }
4053
+ if (
4054
+ mutation.attributeName === "label" ||
4055
+ mutation.attributeName === "direction"
4056
+ ) {
4057
+ syncField = true;
4058
+ } else {
4059
+ syncSlider = true;
4060
+ }
4061
+ }
4062
+ }
4063
+
4064
+ if (syncField) this.#syncField();
4065
+ if (syncSlider) this.#syncSliderAttributes();
4066
+ });
4067
+ }
4068
+
4069
+ this.#observer.observe(this, { attributes: true });
4070
+ }
4071
+
4072
+ disconnectedCallback() {
4073
+ this.#observer?.disconnect();
4074
+ if (this.#steppersSyncFrame) {
4075
+ cancelAnimationFrame(this.#steppersSyncFrame);
4076
+ this.#steppersSyncFrame = 0;
4077
+ }
4078
+ this.#unbindSliderEvents();
4079
+ }
4080
+
4081
+ attributeChangedCallback(name, oldValue, newValue) {
4082
+ if (oldValue === newValue || !this.#field) return;
4083
+ if (name === "label" || name === "direction") {
4084
+ this.#syncField();
4085
+ }
4086
+ }
4087
+
4088
+ #initialize() {
4089
+ const initialChildren = Array.from(this.childNodes).filter((node) => {
4090
+ return (
4091
+ node.nodeType !== Node.TEXT_NODE || Boolean(node.textContent?.trim())
4092
+ );
4093
+ });
4094
+
4095
+ const field = document.createElement("fig-field");
4096
+ const label = document.createElement("label");
4097
+ const slider = document.createElement("fig-slider");
4098
+ slider.setAttribute("text", "true");
4099
+ for (const attrName of this.#getForwardedSliderAttrNames()) {
4100
+ const value = this.getAttribute(attrName);
4101
+ slider.setAttribute(attrName, value ?? "");
4102
+ }
4103
+
4104
+ field.append(label, slider);
4105
+
4106
+ this.#field = field;
4107
+ this.#label = label;
4108
+ this.#slider = slider;
4109
+
4110
+ this.replaceChildren(field);
4111
+
4112
+ for (const node of initialChildren) {
4113
+ this.#slider.appendChild(node);
4114
+ }
4115
+ }
4116
+
4117
+ #syncField() {
4118
+ if (!this.#field || !this.#label) return;
4119
+ const hasLabelAttr = this.hasAttribute("label");
4120
+ const rawLabel = this.getAttribute("label");
4121
+ const isBlankLabel = hasLabelAttr && (rawLabel ?? "").trim() === "";
4122
+
4123
+ if (isBlankLabel) {
4124
+ if (this.#label.parentElement === this.#field) {
4125
+ this.#label.remove();
4126
+ }
4127
+ } else {
4128
+ this.#label.textContent = hasLabelAttr ? rawLabel ?? "" : "Label";
4129
+ if (this.#label.parentElement !== this.#field) {
4130
+ this.#field.prepend(this.#label);
4131
+ }
4132
+ }
4133
+
4134
+ this.#field.setAttribute(
4135
+ "direction",
4136
+ this.getAttribute("direction") || "horizontal",
4137
+ );
4138
+ }
4139
+
4140
+ #syncSliderAttributes() {
4141
+ if (!this.#slider) return;
4142
+ const hostAttrs = this.#getForwardedSliderAttrNames();
4143
+
4144
+ const nextManaged = new Set(hostAttrs.filter((name) => name !== "text"));
4145
+
4146
+ for (const attrName of this.#managedSliderAttrs) {
4147
+ if (!nextManaged.has(attrName)) {
4148
+ this.#slider.removeAttribute(attrName);
4149
+ }
4150
+ }
4151
+
4152
+ for (const attrName of hostAttrs) {
4153
+ if (attrName === "text") continue;
4154
+ const value = this.getAttribute(attrName);
4155
+ this.#slider.setAttribute(attrName, value ?? "");
4156
+ }
4157
+
4158
+ this.#slider.removeAttribute("variant");
4159
+ this.#slider.removeAttribute("color");
4160
+ this.#slider.removeAttribute("transform");
4161
+ this.#slider.removeAttribute("full");
4162
+ this.#slider.setAttribute("text", "true");
4163
+
4164
+ const sliderType = (this.getAttribute("type") || "range").toLowerCase();
4165
+ if (sliderType === "delta" || sliderType === "stepper") {
4166
+ this.#slider.setAttribute(
4167
+ "default",
4168
+ this.getAttribute("default") ?? "50",
4169
+ );
4170
+ } else if (!this.hasAttribute("default")) {
4171
+ this.#slider.removeAttribute("default");
4172
+ }
4173
+ if (sliderType === "stepper") {
4174
+ this.#slider.setAttribute("step", this.getAttribute("step") ?? "10");
4175
+ } else if (!this.hasAttribute("step")) {
4176
+ this.#slider.removeAttribute("step");
4177
+ }
4178
+ if (sliderType === "opacity") {
4179
+ this.#slider.style.setProperty(
4180
+ "--color",
4181
+ "var(--figma-color-bg-tertiary)",
4182
+ );
4183
+ } else {
4184
+ this.#slider.style.removeProperty("--color");
4185
+ }
4186
+
4187
+ this.#managedSliderAttrs = nextManaged;
4188
+ this.#queueSteppersSync();
4189
+ }
4190
+
4191
+ #getForwardedSliderAttrNames() {
4192
+ const reserved = new Set([
4193
+ "label",
4194
+ "direction",
4195
+ "oninput",
4196
+ "onchange",
4197
+ "steppers",
4198
+ ]);
4199
+ return this.getAttributeNames().filter(
4200
+ (name) => !reserved.has(name) && !this.#ignoredSliderAttrs.has(name),
4201
+ );
4202
+ }
4203
+
4204
+ #queueSteppersSync() {
4205
+ if (this.#steppersSyncFrame) {
4206
+ cancelAnimationFrame(this.#steppersSyncFrame);
4207
+ }
4208
+ this.#steppersSyncFrame = requestAnimationFrame(() => {
4209
+ this.#steppersSyncFrame = 0;
4210
+ this.#syncSteppersToNumberInput();
4211
+ });
4212
+ }
4213
+
4214
+ #syncSteppersToNumberInput() {
4215
+ if (!this.#slider) return;
4216
+ const numberInput = this.#slider.querySelector("fig-input-number");
4217
+ if (!numberInput) return;
4218
+
4219
+ const hasSteppers =
4220
+ this.hasAttribute("steppers") &&
4221
+ this.getAttribute("steppers") !== "false";
4222
+ if (!hasSteppers) {
4223
+ numberInput.removeAttribute("steppers");
4224
+ return;
4225
+ }
4226
+
4227
+ const steppersValue = this.getAttribute("steppers");
4228
+ numberInput.setAttribute("steppers", steppersValue ?? "");
4229
+ }
4230
+
4231
+ #bindSliderEvents() {
4232
+ if (!this.#slider) return;
4233
+ if (!this.#boundHandleSliderInput) {
4234
+ this.#boundHandleSliderInput = this.#forwardSliderEvent.bind(
4235
+ this,
4236
+ "input",
4237
+ );
4238
+ }
4239
+ if (!this.#boundHandleSliderChange) {
4240
+ this.#boundHandleSliderChange = this.#forwardSliderEvent.bind(
4241
+ this,
4242
+ "change",
4243
+ );
4244
+ }
4245
+ this.#slider.addEventListener("input", this.#boundHandleSliderInput);
4246
+ this.#slider.addEventListener("change", this.#boundHandleSliderChange);
4247
+ }
4248
+
4249
+ #unbindSliderEvents() {
4250
+ if (!this.#slider) return;
4251
+ if (this.#boundHandleSliderInput) {
4252
+ this.#slider.removeEventListener("input", this.#boundHandleSliderInput);
4253
+ }
4254
+ if (this.#boundHandleSliderChange) {
4255
+ this.#slider.removeEventListener("change", this.#boundHandleSliderChange);
4256
+ }
4257
+ }
4258
+
4259
+ #forwardSliderEvent(type, event) {
4260
+ event.stopPropagation();
4261
+ const detail =
4262
+ event instanceof CustomEvent && event.detail !== undefined
4263
+ ? event.detail
4264
+ : this.#slider?.value;
4265
+ this.dispatchEvent(
4266
+ new CustomEvent(type, {
4267
+ detail,
4268
+ bubbles: true,
4269
+ cancelable: true,
4270
+ composed: true,
4271
+ }),
4272
+ );
4273
+ }
4274
+ }
4275
+ customElements.define("fig-field-slider", FigFieldSlider);
4276
+
4015
4277
  /* Color swatch */
4016
4278
  class FigInputColor extends HTMLElement {
4017
4279
  rgba;
@@ -5803,9 +6065,15 @@ class FigImage extends HTMLElement {
5803
6065
  super();
5804
6066
  }
5805
6067
  #getInnerHTML() {
5806
- const cb = this.hasAttribute("checkerboard") && this.getAttribute("checkerboard") !== "false";
5807
- const bg = this.src ? `url(${this.src})` : (cb ? "url()" : "var(--figma-color-bg-secondary)");
5808
- return `<fig-chit size="large" data-type="image" background="${bg}" disabled${cb ? ' checkerboard' : ''}></fig-chit><div>${
6068
+ const cb =
6069
+ this.hasAttribute("checkerboard") &&
6070
+ this.getAttribute("checkerboard") !== "false";
6071
+ const bg = this.src
6072
+ ? `url(${this.src})`
6073
+ : cb
6074
+ ? "url()"
6075
+ : "var(--figma-color-bg-secondary)";
6076
+ return `<fig-chit size="large" data-type="image" background="${bg}" disabled${cb ? " checkerboard" : ""}></fig-chit><div>${
5809
6077
  this.upload
5810
6078
  ? `<fig-button variant="overlay" type="upload">
5811
6079
  ${this.label}
@@ -5980,11 +6248,16 @@ class FigImage extends HTMLElement {
5980
6248
  if (name === "src") {
5981
6249
  this.#src = newValue;
5982
6250
  if (this.chit) {
5983
- const hasCb = this.hasAttribute("checkerboard") && this.getAttribute("checkerboard") !== "false";
6251
+ const hasCb =
6252
+ this.hasAttribute("checkerboard") &&
6253
+ this.getAttribute("checkerboard") !== "false";
5984
6254
  if (this.#src) {
5985
6255
  this.chit.setAttribute("background", `url(${this.#src})`);
5986
6256
  } else {
5987
- this.chit.setAttribute("background", hasCb ? "url()" : "var(--figma-color-bg-secondary)");
6257
+ this.chit.setAttribute(
6258
+ "background",
6259
+ hasCb ? "url()" : "var(--figma-color-bg-secondary)",
6260
+ );
5988
6261
  }
5989
6262
  }
5990
6263
  if (this.#src) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "3.5.0",
3
+ "version": "3.7.0",
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",