@matdata/yasqe 5.14.0 → 5.16.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@matdata/yasqe",
3
3
  "description": "Yet Another SPARQL Query Editor",
4
- "version": "5.14.0",
4
+ "version": "5.16.0",
5
5
  "main": "build/yasqe.min.js",
6
6
  "types": "build/ts/src/index.d.ts",
7
7
  "license": "MIT",
@@ -105,7 +105,7 @@ describe("Share Functionality", () => {
105
105
  " }",
106
106
  ' ContentType = "application/x-www-form-urlencoded"',
107
107
  ' Body = "query=SELECT"',
108
- ' OutFile = "result.json"',
108
+ ' OutFile = "sparql-generated.json"',
109
109
  "}",
110
110
  "",
111
111
  "Invoke-WebRequest @params",
@@ -117,6 +117,37 @@ describe("Share Functionality", () => {
117
117
  expect(psString).to.include("Headers");
118
118
  expect(psString).to.include("OutFile");
119
119
  expect(psString).to.include("Accept");
120
+ expect(psString).to.include("sparql-generated");
121
+ });
122
+
123
+ it("should format PowerShell commands with here-string for query", () => {
124
+ const query = "SELECT * WHERE { ?s ?p ?o }";
125
+ const lines = [
126
+ '$query = @"',
127
+ query,
128
+ '"@',
129
+ "",
130
+ "$params = @{",
131
+ ' Uri = "https://example.com/sparql"',
132
+ ' Method = "Post"',
133
+ " Headers = @{",
134
+ ' "Accept" = "application/sparql-results+json"',
135
+ " }",
136
+ ' ContentType = "application/x-www-form-urlencoded"',
137
+ ' Body = "query=$([System.Net.WebUtility]::UrlEncode($query))"',
138
+ ' OutFile = "sparql-generated.json"',
139
+ "}",
140
+ "",
141
+ "Invoke-WebRequest @params",
142
+ ];
143
+ const psString = lines.join("\n");
144
+
145
+ expect(psString).to.include('$query = @"');
146
+ expect(psString).to.include('"@');
147
+ expect(psString).to.include(query);
148
+ expect(psString).to.include('Body = "query=$([System.Net.WebUtility]::UrlEncode($query))"');
149
+ expect(psString).to.not.include('Body = "query=`$query"'); // Should NOT escape the variable
150
+ expect(psString).to.include("sparql-generated");
120
151
  });
121
152
 
122
153
  it("should format wget commands with proper line breaks", () => {
package/src/index.ts CHANGED
@@ -46,6 +46,10 @@ export interface Yasqe {
46
46
  off(eventName: "autocompletionClose", handler: (instance: Yasqe) => void): void;
47
47
  on(eventName: "resize", handler: (instance: Yasqe, newSize: string) => void): void;
48
48
  off(eventName: "resize", handler: (instance: Yasqe, newSize: string) => void): void;
49
+ on(eventName: "saveManagedQuery", handler: () => void): void;
50
+ off(eventName: "saveManagedQuery", handler: () => void): void;
51
+ on(eventName: "downloadRqFile", handler: () => void): void;
52
+ off(eventName: "downloadRqFile", handler: () => void): void;
49
53
  on(eventName: string, handler: () => void): void;
50
54
  }
51
55
 
@@ -59,8 +63,11 @@ export class Yasqe extends CodeMirror {
59
63
  private abortController: AbortController | undefined;
60
64
  private queryStatus: "valid" | "error" | undefined;
61
65
  private queryBtn: HTMLButtonElement | undefined;
62
- private saveBtn: HTMLButtonElement | undefined;
66
+ private saveBtnWrapper: HTMLDivElement | undefined;
63
67
  private fullscreenBtn: HTMLButtonElement | undefined;
68
+ private hamburgerBtn: HTMLButtonElement | undefined;
69
+ private hamburgerMenu: HTMLDivElement | undefined;
70
+ private shareBtn: HTMLButtonElement | undefined;
64
71
  private isFullscreen: boolean = false;
65
72
  private horizontalResizeWrapper?: HTMLDivElement;
66
73
  private snippetsBar?: HTMLDivElement;
@@ -318,6 +325,7 @@ export class Yasqe extends CodeMirror {
318
325
  shareLinkWrapper.setAttribute("aria-label", "Share query");
319
326
  shareLinkWrapper.appendChild(shareIcon);
320
327
  buttons.appendChild(shareLinkWrapper);
328
+ this.shareBtn = shareLinkWrapper;
321
329
  shareLinkWrapper.addEventListener("click", (event: MouseEvent) => showSharePopup(event));
322
330
  shareLinkWrapper.addEventListener("keydown", (event: KeyboardEvent) => {
323
331
  if (event.code === "Enter") {
@@ -463,6 +471,7 @@ export class Yasqe extends CodeMirror {
463
471
  urlBtn.className = "yasqe_btn yasqe_btn-sm yasqe_shareBtn";
464
472
  buttonContainer.appendChild(urlBtn);
465
473
  urlBtn.onclick = async () => {
474
+ if (!this.config.createShareableLink) return;
466
475
  const url = this.config.createShareableLink(this);
467
476
  await copyToClipboard(url, "URL");
468
477
  };
@@ -478,6 +487,7 @@ export class Yasqe extends CodeMirror {
478
487
  shortenBtn.disabled = true;
479
488
  shortenBtn.innerText = "Shortening...";
480
489
  try {
490
+ if (!this.config.createShareableLink) return;
481
491
  const longUrl = this.config.createShareableLink(this);
482
492
  const shortUrl = await createShortLink(this, longUrl);
483
493
  await copyToClipboard(shortUrl, "Shortened URL");
@@ -533,9 +543,25 @@ export class Yasqe extends CodeMirror {
533
543
  // Position popup after layout is complete
534
544
  const positionPopup = () => {
535
545
  if (!popup) return;
536
- const sharePos = shareLinkWrapper.getBoundingClientRect();
537
- popup.style.top = shareLinkWrapper.offsetTop + sharePos.height + "px";
538
- popup.style.left = shareLinkWrapper.offsetLeft + shareLinkWrapper.clientWidth - popup.clientWidth + "px";
546
+ const buttonsRect = buttons.getBoundingClientRect();
547
+ const popupHeight = popup.offsetHeight || 300; // Estimated height
548
+ const viewportHeight = window.innerHeight;
549
+ const spaceAbove = buttonsRect.top;
550
+ const spaceBelow = viewportHeight - buttonsRect.bottom;
551
+
552
+ // If there's enough space above (with some padding), position above
553
+ // Otherwise position below (will use scrolling if needed)
554
+ if (spaceAbove >= popupHeight + 20) {
555
+ popup.style.bottom = (buttons.clientHeight || 46) + "px";
556
+ popup.style.top = "auto";
557
+ popup.style.right = "0px";
558
+ } else {
559
+ // Not enough space above, use fixed positioning at top of viewport
560
+ popup.style.position = "fixed";
561
+ popup.style.bottom = "auto";
562
+ popup.style.top = "20px";
563
+ popup.style.right = "20px";
564
+ }
539
565
  };
540
566
 
541
567
  if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
@@ -548,24 +574,42 @@ export class Yasqe extends CodeMirror {
548
574
  }
549
575
 
550
576
  /**
551
- * Draw save button (THIRD)
577
+ * Draw save buttons (THIRD)
552
578
  */
553
- const saveBtn = document.createElement("button");
554
- addClass(saveBtn, "yasqe_saveButton");
555
- const saveIcon = document.createElement("i");
556
- addClass(saveIcon, "fas");
557
- addClass(saveIcon, "fa-save");
558
- saveIcon.setAttribute("aria-hidden", "true");
559
- saveBtn.appendChild(saveIcon);
560
- saveBtn.onclick = () => {
561
- // Call the managed query save function if available
579
+ const saveBtnWrapper = document.createElement("div");
580
+ addClass(saveBtnWrapper, "yasqe_saveWrapper");
581
+ saveBtnWrapper.style.display = "none"; // Hidden by default, shown when workspace is configured
582
+ this.saveBtnWrapper = saveBtnWrapper;
583
+
584
+ const saveManagedBtn = document.createElement("button");
585
+ addClass(saveManagedBtn, "yasqe_saveManagedButton");
586
+ const saveManagedIcon = document.createElement("i");
587
+ addClass(saveManagedIcon, "fas");
588
+ addClass(saveManagedIcon, "fa-database");
589
+ saveManagedIcon.setAttribute("aria-hidden", "true");
590
+ saveManagedBtn.appendChild(saveManagedIcon);
591
+ saveManagedBtn.title = "Save as managed query";
592
+ saveManagedBtn.setAttribute("aria-label", "Save as managed query");
593
+ saveManagedBtn.onclick = () => {
562
594
  this.emit("saveManagedQuery");
563
595
  };
564
- saveBtn.title = "Save managed query (Ctrl+S)";
565
- saveBtn.setAttribute("aria-label", "Save managed query");
566
- saveBtn.style.display = "none"; // Hidden by default, shown when workspace is configured
567
- this.saveBtn = saveBtn;
568
- buttons.appendChild(saveBtn);
596
+ saveBtnWrapper.appendChild(saveManagedBtn);
597
+
598
+ const saveRqBtn = document.createElement("button");
599
+ addClass(saveRqBtn, "yasqe_saveRqButton");
600
+ const saveRqIcon = document.createElement("i");
601
+ addClass(saveRqIcon, "fas");
602
+ addClass(saveRqIcon, "fa-file-download");
603
+ saveRqIcon.setAttribute("aria-hidden", "true");
604
+ saveRqBtn.appendChild(saveRqIcon);
605
+ saveRqBtn.title = "Save as .rq file";
606
+ saveRqBtn.setAttribute("aria-label", "Save as .rq file");
607
+ saveRqBtn.onclick = () => {
608
+ this.emit("downloadRqFile");
609
+ };
610
+ saveBtnWrapper.appendChild(saveRqBtn);
611
+
612
+ buttons.appendChild(saveBtnWrapper);
569
613
 
570
614
  /**
571
615
  * Draw format btn (FOURTH)
@@ -609,6 +653,146 @@ export class Yasqe extends CodeMirror {
609
653
  this.fullscreenBtn.title = "Toggle fullscreen (F11)";
610
654
  this.fullscreenBtn.setAttribute("aria-label", "Toggle fullscreen");
611
655
  buttons.appendChild(this.fullscreenBtn);
656
+
657
+ /**
658
+ * Draw hamburger menu button and dropdown (for mobile)
659
+ */
660
+ this.hamburgerBtn = document.createElement("button");
661
+ addClass(this.hamburgerBtn, "yasqe_hamburgerButton");
662
+ const hamburgerIcon = document.createElement("i");
663
+ addClass(hamburgerIcon, "fas");
664
+ addClass(hamburgerIcon, "fa-bars");
665
+ hamburgerIcon.setAttribute("aria-hidden", "true");
666
+ this.hamburgerBtn.appendChild(hamburgerIcon);
667
+ this.hamburgerBtn.title = "More options";
668
+ this.hamburgerBtn.setAttribute("aria-label", "More options");
669
+ this.hamburgerBtn.setAttribute("aria-expanded", "false");
670
+ buttons.appendChild(this.hamburgerBtn);
671
+
672
+ // Create hamburger menu
673
+ this.hamburgerMenu = document.createElement("div");
674
+ this.hamburgerMenu.className = "yasqe_hamburgerMenu";
675
+ buttons.appendChild(this.hamburgerMenu);
676
+
677
+ // Add menu items
678
+ if (this.config.createShareableLink) {
679
+ const shareItem = document.createElement("button");
680
+ shareItem.className = "yasqe_hamburgerMenuItem";
681
+ const shareIconMenu = document.createElement("i");
682
+ addClass(shareIconMenu, "fas");
683
+ addClass(shareIconMenu, "fa-share-nodes");
684
+ shareIconMenu.setAttribute("aria-hidden", "true");
685
+ shareItem.appendChild(shareIconMenu);
686
+ const shareLabel = document.createElement("span");
687
+ shareLabel.textContent = "Share";
688
+ shareItem.appendChild(shareLabel);
689
+ shareItem.onclick = () => {
690
+ this.closeHamburgerMenu();
691
+ this.shareBtn?.click();
692
+ };
693
+ this.hamburgerMenu.appendChild(shareItem);
694
+ }
695
+
696
+ const saveManagedItem = document.createElement("button");
697
+ saveManagedItem.className = "yasqe_hamburgerMenuItem";
698
+ const saveManagedIconMenu = document.createElement("i");
699
+ addClass(saveManagedIconMenu, "fas");
700
+ addClass(saveManagedIconMenu, "fa-database");
701
+ saveManagedIconMenu.setAttribute("aria-hidden", "true");
702
+ saveManagedItem.appendChild(saveManagedIconMenu);
703
+ const saveManagedLabel = document.createElement("span");
704
+ saveManagedLabel.textContent = "Save as managed query";
705
+ saveManagedItem.appendChild(saveManagedLabel);
706
+ saveManagedItem.onclick = () => {
707
+ this.closeHamburgerMenu();
708
+ this.emit("saveManagedQuery");
709
+ };
710
+ this.hamburgerMenu.appendChild(saveManagedItem);
711
+
712
+ const saveRqItem = document.createElement("button");
713
+ saveRqItem.className = "yasqe_hamburgerMenuItem";
714
+ const saveRqIconMenu = document.createElement("i");
715
+ addClass(saveRqIconMenu, "fas");
716
+ addClass(saveRqIconMenu, "fa-file-download");
717
+ saveRqIconMenu.setAttribute("aria-hidden", "true");
718
+ saveRqItem.appendChild(saveRqIconMenu);
719
+ const saveRqLabel = document.createElement("span");
720
+ saveRqLabel.textContent = "Save as .rq file";
721
+ saveRqItem.appendChild(saveRqLabel);
722
+ saveRqItem.onclick = () => {
723
+ this.closeHamburgerMenu();
724
+ this.emit("downloadRqFile");
725
+ };
726
+ this.hamburgerMenu.appendChild(saveRqItem);
727
+
728
+ if (this.config.showFormatButton) {
729
+ const formatItem = document.createElement("button");
730
+ formatItem.className = "yasqe_hamburgerMenuItem";
731
+ const formatIconMenu = document.createElement("i");
732
+ addClass(formatIconMenu, "fas");
733
+ addClass(formatIconMenu, "fa-align-left");
734
+ formatIconMenu.setAttribute("aria-hidden", "true");
735
+ formatItem.appendChild(formatIconMenu);
736
+ const formatLabel = document.createElement("span");
737
+ formatLabel.textContent = "Format";
738
+ formatItem.appendChild(formatLabel);
739
+ formatItem.onclick = () => {
740
+ this.closeHamburgerMenu();
741
+ this.format();
742
+ };
743
+ this.hamburgerMenu.appendChild(formatItem);
744
+ }
745
+
746
+ const fullscreenItem = document.createElement("button");
747
+ fullscreenItem.className = "yasqe_hamburgerMenuItem";
748
+ const fullscreenIconMenu = document.createElement("i");
749
+ addClass(fullscreenIconMenu, "fas");
750
+ addClass(fullscreenIconMenu, "fa-expand");
751
+ fullscreenIconMenu.setAttribute("aria-hidden", "true");
752
+ fullscreenItem.appendChild(fullscreenIconMenu);
753
+ const fullscreenLabel = document.createElement("span");
754
+ fullscreenLabel.textContent = "Fullscreen";
755
+ fullscreenItem.appendChild(fullscreenLabel);
756
+ fullscreenItem.onclick = () => {
757
+ this.closeHamburgerMenu();
758
+ this.toggleFullscreen();
759
+ };
760
+ this.hamburgerMenu.appendChild(fullscreenItem);
761
+
762
+ // Toggle hamburger menu
763
+ this.hamburgerBtn.onclick = (e) => {
764
+ e.stopPropagation();
765
+ this.toggleHamburgerMenu();
766
+ };
767
+
768
+ // Close hamburger menu when clicking outside
769
+ document.addEventListener("click", (e) => {
770
+ if (
771
+ this.hamburgerMenu &&
772
+ this.hamburgerBtn &&
773
+ !this.hamburgerMenu.contains(e.target as Node) &&
774
+ !this.hamburgerBtn.contains(e.target as Node)
775
+ ) {
776
+ this.closeHamburgerMenu();
777
+ }
778
+ });
779
+ }
780
+
781
+ private toggleHamburgerMenu() {
782
+ if (!this.hamburgerMenu || !this.hamburgerBtn) return;
783
+ const isActive = this.hamburgerMenu.classList.contains("active");
784
+ if (isActive) {
785
+ this.closeHamburgerMenu();
786
+ } else {
787
+ addClass(this.hamburgerMenu, "active");
788
+ this.hamburgerBtn.setAttribute("aria-expanded", "true");
789
+ }
790
+ }
791
+
792
+ private closeHamburgerMenu() {
793
+ if (!this.hamburgerMenu || !this.hamburgerBtn) return;
794
+ removeClass(this.hamburgerMenu, "active");
795
+ this.hamburgerBtn.setAttribute("aria-expanded", "false");
612
796
  }
613
797
  public toggleFullscreen() {
614
798
  this.isFullscreen = !this.isFullscreen;
@@ -1407,8 +1591,8 @@ export class Yasqe extends CodeMirror {
1407
1591
  }
1408
1592
 
1409
1593
  public setSaveButtonVisible(visible: boolean) {
1410
- if (this.saveBtn) {
1411
- this.saveBtn.style.display = visible ? "inline-flex" : "none";
1594
+ if (this.saveBtnWrapper) {
1595
+ this.saveBtnWrapper.style.display = visible ? "inline-flex" : "none";
1412
1596
  }
1413
1597
  }
1414
1598
 
@@ -1830,7 +2014,7 @@ export interface Config extends Partial<CodeMirror.EditorConfiguration> {
1830
2014
  * By default, this feature is enabled, and the only the query value is appended to the link.
1831
2015
  * ps. This function should return an object which is parseable by jQuery.param (http://api.jquery.com/jQuery.param/)
1832
2016
  */
1833
- createShareableLink: (yasqe: Yasqe) => string;
2017
+ createShareableLink: ((yasqe: Yasqe) => string) | undefined | null;
1834
2018
  createShortLink: ((yasqe: Yasqe, longLink: string) => Promise<string>) | undefined;
1835
2019
  consumeShareLink: ((yasqe: Yasqe) => void) | undefined | null;
1836
2020
  /**
@@ -3,10 +3,10 @@
3
3
  $queryButtonHeight: 40px;
4
4
 
5
5
  .yasqe_btn {
6
- color: #333;
6
+ color: var(--yasgui-text-primary, #333);
7
7
  border: 1px solid transparent;
8
- background-color: #fff;
9
- border-color: #ccc;
8
+ background-color: var(--yasgui-bg-primary, #fff);
9
+ border-color: var(--yasgui-border-color, #ccc);
10
10
  border-width: 1px;
11
11
  display: inline-block;
12
12
  text-align: center;
@@ -33,8 +33,8 @@
33
33
 
34
34
  &:hover {
35
35
  outline: 0;
36
- background-color: #ebebeb;
37
- border-color: #adadad;
36
+ background-color: var(--yasgui-bg-secondary, #ebebeb);
37
+ border-color: var(--yasgui-text-muted, #adadad);
38
38
  }
39
39
 
40
40
  &:focus,
@@ -46,10 +46,10 @@
46
46
  }
47
47
 
48
48
  &.btn_icon:focus {
49
- color: #333;
49
+ color: var(--yasgui-text-primary, #333);
50
50
  border: 1px solid transparent;
51
- background-color: #fff;
52
- border-color: #ccc;
51
+ background-color: var(--yasgui-bg-primary, #fff);
52
+ border-color: var(--yasgui-border-color, #ccc);
53
53
  }
54
54
 
55
55
  &.yasqe_btn-sm {
@@ -62,7 +62,7 @@
62
62
 
63
63
  .yasqe_buttons {
64
64
  position: absolute;
65
- top: 10px;
65
+ bottom: 10px;
66
66
  right: 20px;
67
67
  z-index: 5;
68
68
 
@@ -77,7 +77,7 @@
77
77
  margin-left: 8px;
78
78
  height: 36px;
79
79
  width: 36px;
80
- color: #505050;
80
+ color: var(--yasgui-text-secondary, #505050);
81
81
 
82
82
  i {
83
83
  font-size: 20px;
@@ -96,22 +96,25 @@
96
96
  position: absolute;
97
97
  padding: 12px;
98
98
  margin-left: 0px;
99
- background-color: #fff;
100
- border: 1px solid #e3e3e3;
99
+ background-color: var(--yasgui-bg-primary, #fff);
100
+ border: 1px solid var(--yasgui-border-color, #e3e3e3);
101
101
  border-radius: 4px;
102
102
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
103
103
  width: auto;
104
104
  min-width: 300px;
105
105
  max-width: 400px;
106
+ max-height: calc(100vh - 100px);
107
+ overflow-y: auto;
106
108
  height: auto;
107
109
  display: flex;
108
110
  flex-direction: column;
109
111
  gap: 8px;
112
+ z-index: 10001;
110
113
 
111
114
  .yasqe_sharePopup_title {
112
115
  font-weight: 600;
113
116
  font-size: 14px;
114
- color: #333;
117
+ color: var(--yasgui-text-primary, #333);
115
118
  margin-bottom: 4px;
116
119
  }
117
120
 
@@ -290,7 +293,37 @@
290
293
  }
291
294
  }
292
295
 
293
- .yasqe_saveButton {
296
+ .yasqe_saveWrapper {
297
+ position: relative;
298
+ display: inline-flex;
299
+ align-items: center;
300
+ vertical-align: middle;
301
+ margin-left: 8px;
302
+
303
+ .yasqe_saveManagedButton,
304
+ .yasqe_saveRqButton {
305
+ display: inline-flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ border: none;
309
+ background: none;
310
+ cursor: pointer;
311
+ padding: 6px;
312
+ height: 36px;
313
+ width: 36px;
314
+ color: var(--yasgui-text-secondary, #505050);
315
+
316
+ i {
317
+ font-size: 20px;
318
+ }
319
+
320
+ &:hover {
321
+ color: #337ab7;
322
+ }
323
+ }
324
+ }
325
+
326
+ .yasqe_formatButton {
294
327
  display: inline-flex;
295
328
  align-items: center;
296
329
  justify-content: center;
@@ -301,7 +334,7 @@
301
334
  margin-left: 8px;
302
335
  height: 36px;
303
336
  width: 36px;
304
- color: #505050;
337
+ color: var(--yasgui-text-secondary, #505050);
305
338
 
306
339
  i {
307
340
  font-size: 20px;
@@ -312,7 +345,7 @@
312
345
  }
313
346
  }
314
347
 
315
- .yasqe_formatButton {
348
+ .yasqe_fullscreenButton {
316
349
  display: inline-flex;
317
350
  align-items: center;
318
351
  justify-content: center;
@@ -323,19 +356,23 @@
323
356
  margin-left: 8px;
324
357
  height: 36px;
325
358
  width: 36px;
326
- color: #505050;
359
+ color: var(--yasgui-text-secondary, #505050);
327
360
 
328
361
  i {
329
362
  font-size: 20px;
330
363
  }
331
364
 
365
+ .fullscreenExitIcon {
366
+ display: none;
367
+ }
368
+
332
369
  &:hover {
333
370
  color: #337ab7;
334
371
  }
335
372
  }
336
373
 
337
- .yasqe_fullscreenButton {
338
- display: inline-flex;
374
+ .yasqe_hamburgerButton {
375
+ display: none;
339
376
  align-items: center;
340
377
  justify-content: center;
341
378
  border: none;
@@ -345,20 +382,83 @@
345
382
  margin-left: 8px;
346
383
  height: 36px;
347
384
  width: 36px;
348
- color: #505050;
385
+ color: var(--yasgui-text-secondary, #505050);
349
386
 
350
387
  i {
351
388
  font-size: 20px;
352
389
  }
353
390
 
354
- .fullscreenExitIcon {
355
- display: none;
391
+ &:hover {
392
+ color: #337ab7;
356
393
  }
357
394
 
358
- &:hover {
395
+ &[aria-expanded="true"] {
359
396
  color: #337ab7;
360
397
  }
361
398
  }
399
+
400
+ .yasqe_hamburgerMenu {
401
+ display: none;
402
+ position: absolute;
403
+ bottom: 46px;
404
+ right: 0;
405
+ background-color: var(--yasgui-bg-primary, #fff);
406
+ border: 1px solid var(--yasgui-border-color, #e3e3e3);
407
+ border-radius: 4px;
408
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
409
+ padding: 4px;
410
+ min-width: 180px;
411
+ z-index: 10000;
412
+
413
+ &.active {
414
+ display: block;
415
+ }
416
+
417
+ .yasqe_hamburgerMenuItem {
418
+ display: flex;
419
+ align-items: center;
420
+ gap: 10px;
421
+ width: 100%;
422
+ padding: 10px 12px;
423
+ border: none;
424
+ background: none;
425
+ text-align: left;
426
+ cursor: pointer;
427
+ font-size: 14px;
428
+ color: var(--yasgui-text-primary, #333);
429
+ border-radius: 2px;
430
+
431
+ i {
432
+ font-size: 16px;
433
+ width: 20px;
434
+ color: var(--yasgui-text-secondary, #505050);
435
+ }
436
+
437
+ &:hover {
438
+ background-color: var(--yasgui-bg-secondary, #f0f0f0);
439
+ }
440
+
441
+ &:active {
442
+ background-color: var(--yasgui-bg-tertiary, #e0e0e0);
443
+ }
444
+ }
445
+ }
446
+ }
447
+
448
+ // Responsive: Show hamburger menu on smaller screens
449
+ @media (max-width: 768px) {
450
+ .yasqe_buttons {
451
+ .yasqe_share,
452
+ .yasqe_saveWrapper,
453
+ .yasqe_formatButton,
454
+ .yasqe_fullscreenButton {
455
+ display: none !important;
456
+ }
457
+
458
+ .yasqe_hamburgerButton {
459
+ display: inline-flex;
460
+ }
461
+ }
362
462
  }
363
463
 
364
464
  &.fullscreen {
@@ -368,7 +468,7 @@
368
468
  right: 0;
369
469
  bottom: 0;
370
470
  z-index: 9998;
371
- background: white;
471
+ background: var(--yasgui-bg-primary, white);
372
472
  margin: 0;
373
473
  padding-top: 100px;
374
474
 
@@ -378,7 +478,7 @@
378
478
 
379
479
  .yasqe_buttons {
380
480
  position: fixed;
381
- top: 110px;
481
+ bottom: 40px;
382
482
  right: 20px;
383
483
  z-index: 10000;
384
484
  }