@rogieking/figui3 1.9.6 → 1.9.7

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/components.css CHANGED
@@ -2079,38 +2079,8 @@ dialog,
2079
2079
  }
2080
2080
  }
2081
2081
  dialog[is="fig-dialog"] {
2082
- --offset: 1rem;
2083
- --translate-x: -50%;
2084
- --translate-y: -50%;
2085
2082
  --z-index: 999999;
2086
-
2087
- position: fixed;
2088
- top: 50%;
2089
- left: 50%;
2090
- margin: 0;
2091
2083
  z-index: var(--z-index);
2092
- transform: translate(var(--translate-x), var(--translate-y));
2093
-
2094
- &[position*="bottom"] {
2095
- bottom: var(--offset);
2096
- top: auto;
2097
- --translate-y: 0;
2098
- }
2099
- &[position*="top"] {
2100
- top: var(--offset);
2101
- bottom: auto;
2102
- --translate-y: 0;
2103
- }
2104
- &[position*="right"] {
2105
- right: var(--offset);
2106
- left: auto;
2107
- --translate-x: 0;
2108
- }
2109
- &[position*="left"] {
2110
- left: var(--offset);
2111
- right: auto;
2112
- --translate-x: 0;
2113
- }
2114
2084
  }
2115
2085
 
2116
2086
  /* Tooltip */
package/example.html CHANGED
@@ -594,6 +594,7 @@
594
594
  </fig-field>
595
595
 
596
596
  <dialog open="true"
597
+ drag="true"
597
598
  position="bottom right"
598
599
  is="fig-dialog">
599
600
  <fig-header>
package/fig.js CHANGED
@@ -592,20 +592,214 @@ customElements.define("fig-popover", FigPopover);
592
592
  * @attr {boolean} modal - Whether the dialog should be modal
593
593
  */
594
594
  class FigDialog extends HTMLDialogElement {
595
+ #isDragging = false;
596
+ #dragOffset = { x: 0, y: 0 };
597
+ #boundPointerDown;
598
+ #boundPointerMove;
599
+ #boundPointerUp;
600
+ #offset = 16; // 1rem in pixels
601
+ #positionInitialized = false;
602
+
603
+ constructor() {
604
+ super();
605
+ this.#boundPointerDown = this.#handlePointerDown.bind(this);
606
+ this.#boundPointerMove = this.#handlePointerMove.bind(this);
607
+ this.#boundPointerUp = this.#handlePointerUp.bind(this);
608
+ }
609
+
595
610
  connectedCallback() {
596
611
  this.modal =
597
612
  this.hasAttribute("modal") && this.getAttribute("modal") !== "false";
613
+
614
+ // Set up drag functionality
615
+ this.drag =
616
+ this.hasAttribute("drag") && this.getAttribute("drag") !== "false";
617
+
598
618
  requestAnimationFrame(() => {
599
619
  this.#addCloseListeners();
620
+ this.#setupDragListeners();
621
+ this.#applyPosition();
600
622
  });
601
623
  }
602
624
 
625
+ disconnectedCallback() {
626
+ this.#removeDragListeners();
627
+ }
628
+
603
629
  #addCloseListeners() {
604
630
  this.querySelectorAll("fig-button[close-dialog]").forEach((button) => {
605
631
  button.removeEventListener("click", this.close);
606
632
  button.addEventListener("click", this.close.bind(this));
607
633
  });
608
634
  }
635
+
636
+ #applyPosition() {
637
+ const position = this.getAttribute("position") || "";
638
+ const rect = this.getBoundingClientRect();
639
+ const viewportWidth = window.innerWidth;
640
+ const viewportHeight = window.innerHeight;
641
+
642
+ // Default to centered
643
+ let top = (viewportHeight - rect.height) / 2;
644
+ let left = (viewportWidth - rect.width) / 2;
645
+
646
+ // Parse position attribute
647
+ const hasTop = position.includes("top");
648
+ const hasBottom = position.includes("bottom");
649
+ const hasLeft = position.includes("left");
650
+ const hasRight = position.includes("right");
651
+
652
+ // Vertical positioning
653
+ if (hasTop) {
654
+ top = this.#offset;
655
+ } else if (hasBottom) {
656
+ top = viewportHeight - rect.height - this.#offset;
657
+ }
658
+
659
+ // Horizontal positioning
660
+ if (hasLeft) {
661
+ left = this.#offset;
662
+ } else if (hasRight) {
663
+ left = viewportWidth - rect.width - this.#offset;
664
+ }
665
+
666
+ // Apply position using fixed positioning with pixels
667
+ this.style.position = "fixed";
668
+ this.style.top = `${top}px`;
669
+ this.style.left = `${left}px`;
670
+ this.style.transform = "none";
671
+ this.style.margin = "0";
672
+
673
+ this.#positionInitialized = true;
674
+ }
675
+
676
+ #setupDragListeners() {
677
+ if (this.drag) {
678
+ this.addEventListener("pointerdown", this.#boundPointerDown);
679
+ // Set move cursor only on fig-header elements
680
+ const header = this.querySelector("fig-header, header");
681
+ if (header) {
682
+ header.style.cursor = "move";
683
+ }
684
+ }
685
+ }
686
+
687
+ #removeDragListeners() {
688
+ this.removeEventListener("pointerdown", this.#boundPointerDown);
689
+ document.removeEventListener("pointermove", this.#boundPointerMove);
690
+ document.removeEventListener("pointerup", this.#boundPointerUp);
691
+ }
692
+
693
+ #isInteractiveElement(element) {
694
+ // List of interactive element types and attributes to avoid dragging on
695
+ const interactiveSelectors = [
696
+ "input",
697
+ "button",
698
+ "select",
699
+ "textarea",
700
+ "a",
701
+ "label",
702
+ '[contenteditable="true"]',
703
+ "[tabindex]",
704
+ "fig-button",
705
+ "fig-input-text",
706
+ "fig-input-number",
707
+ "fig-slider",
708
+ "fig-checkbox",
709
+ "fig-radio",
710
+ "fig-tab",
711
+ "fig-dropdown",
712
+ "fig-chit",
713
+ ];
714
+
715
+ // Check if the element itself is interactive
716
+ if (interactiveSelectors.some((selector) => element.matches?.(selector))) {
717
+ return true;
718
+ }
719
+
720
+ // Check if any parent element up to the dialog is interactive
721
+ let parent = element.parentElement;
722
+ while (parent && parent !== this) {
723
+ if (interactiveSelectors.some((selector) => parent.matches?.(selector))) {
724
+ return true;
725
+ }
726
+ parent = parent.parentElement;
727
+ }
728
+
729
+ return false;
730
+ }
731
+
732
+ #handlePointerDown(e) {
733
+ if (!this.drag || this.#isInteractiveElement(e.target)) {
734
+ return;
735
+ }
736
+
737
+ this.#isDragging = true;
738
+ this.setPointerCapture(e.pointerId);
739
+
740
+ // Get current position from computed style
741
+ const rect = this.getBoundingClientRect();
742
+
743
+ // Store offset from pointer to dialog top-left corner
744
+ this.#dragOffset.x = e.clientX - rect.left;
745
+ this.#dragOffset.y = e.clientY - rect.top;
746
+
747
+ document.addEventListener("pointermove", this.#boundPointerMove);
748
+ document.addEventListener("pointerup", this.#boundPointerUp);
749
+
750
+ e.preventDefault();
751
+ }
752
+
753
+ #handlePointerMove(e) {
754
+ if (!this.#isDragging) return;
755
+
756
+ // Calculate new position based on pointer position minus offset
757
+ const newLeft = e.clientX - this.#dragOffset.x;
758
+ const newTop = e.clientY - this.#dragOffset.y;
759
+
760
+ // Apply position directly with pixels
761
+ this.style.left = `${newLeft}px`;
762
+ this.style.top = `${newTop}px`;
763
+
764
+ e.preventDefault();
765
+ }
766
+
767
+ #handlePointerUp(e) {
768
+ if (!this.#isDragging) return;
769
+
770
+ this.#isDragging = false;
771
+ this.releasePointerCapture(e.pointerId);
772
+
773
+ document.removeEventListener("pointermove", this.#boundPointerMove);
774
+ document.removeEventListener("pointerup", this.#boundPointerUp);
775
+
776
+ e.preventDefault();
777
+ }
778
+
779
+ static get observedAttributes() {
780
+ return ["modal", "drag", "position"];
781
+ }
782
+
783
+ attributeChangedCallback(name, oldValue, newValue) {
784
+ if (name === "drag") {
785
+ this.drag = newValue !== null && newValue !== "false";
786
+
787
+ if (this.drag) {
788
+ this.#setupDragListeners();
789
+ } else {
790
+ this.#removeDragListeners();
791
+ // Remove move cursor from header
792
+ const header = this.querySelector("fig-header, header");
793
+ if (header) {
794
+ header.style.cursor = "";
795
+ }
796
+ }
797
+ }
798
+
799
+ if (name === "position" && this.#positionInitialized) {
800
+ this.#applyPosition();
801
+ }
802
+ }
609
803
  }
610
804
  customElements.define("fig-dialog", FigDialog, { extends: "dialog" });
611
805
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rogieking/figui3",
3
- "version": "1.9.6",
3
+ "version": "1.9.7",
4
4
  "module": "index.ts",
5
5
  "type": "module",
6
6
  "devDependencies": {