@mushi-mushi/web 0.5.1 → 0.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.
package/dist/index.d.cts CHANGED
@@ -44,6 +44,12 @@ declare class MushiWidget {
44
44
  private screenshotAttached;
45
45
  private elementSelected;
46
46
  private submitting;
47
+ private triggerVisible;
48
+ private triggerShrunk;
49
+ private triggerHiddenByScroll;
50
+ private attachedLaunchers;
51
+ private smartHideCleanup;
52
+ private smartHideTimer;
47
53
  /** Captured at the moment of submit so the success ledger metadata
48
54
  * ("REPORT · 14:23:07 JST") doesn't drift while the success step
49
55
  * is on screen. */
@@ -62,11 +68,23 @@ declare class MushiWidget {
62
68
  }): void;
63
69
  close(): void;
64
70
  getIsOpen(): boolean;
71
+ showTrigger(): void;
72
+ hideTrigger(): void;
73
+ setTrigger(trigger: NonNullable<MushiWidgetConfig['trigger']>): void;
74
+ attachTo(selectorOrElement: string | Element, options?: MushiWidgetConfig): () => void;
65
75
  setScreenshotAttached(attached: boolean): void;
66
76
  setElementSelected(selected: boolean): void;
67
77
  destroy(): void;
78
+ private syncAttachedLaunchers;
79
+ private syncSmartHide;
80
+ private shouldRenderTrigger;
81
+ private effectiveTrigger;
82
+ private isMobileSmartHidden;
83
+ private detectEnvironment;
84
+ private isRouteHidden;
68
85
  private getTheme;
69
86
  private render;
87
+ private applyInsetVars;
70
88
  private renderStep;
71
89
  /**
72
90
  * Editorial masthead. Always carries:
package/dist/index.d.ts CHANGED
@@ -44,6 +44,12 @@ declare class MushiWidget {
44
44
  private screenshotAttached;
45
45
  private elementSelected;
46
46
  private submitting;
47
+ private triggerVisible;
48
+ private triggerShrunk;
49
+ private triggerHiddenByScroll;
50
+ private attachedLaunchers;
51
+ private smartHideCleanup;
52
+ private smartHideTimer;
47
53
  /** Captured at the moment of submit so the success ledger metadata
48
54
  * ("REPORT · 14:23:07 JST") doesn't drift while the success step
49
55
  * is on screen. */
@@ -62,11 +68,23 @@ declare class MushiWidget {
62
68
  }): void;
63
69
  close(): void;
64
70
  getIsOpen(): boolean;
71
+ showTrigger(): void;
72
+ hideTrigger(): void;
73
+ setTrigger(trigger: NonNullable<MushiWidgetConfig['trigger']>): void;
74
+ attachTo(selectorOrElement: string | Element, options?: MushiWidgetConfig): () => void;
65
75
  setScreenshotAttached(attached: boolean): void;
66
76
  setElementSelected(selected: boolean): void;
67
77
  destroy(): void;
78
+ private syncAttachedLaunchers;
79
+ private syncSmartHide;
80
+ private shouldRenderTrigger;
81
+ private effectiveTrigger;
82
+ private isMobileSmartHidden;
83
+ private detectEnvironment;
84
+ private isRouteHidden;
68
85
  private getTheme;
69
86
  private render;
87
+ private applyInsetVars;
70
88
  private renderStep;
71
89
  /**
72
90
  * Editorial masthead. Always carries:
package/dist/index.js CHANGED
@@ -243,11 +243,6 @@ function getWidgetStyles(theme) {
243
243
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
244
244
  button { font-family: inherit; }
245
245
 
246
- /* \u2500\u2500 Trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
247
- A small "stamp card" \u2014 soft rounded square (4px radius), paper
248
- background, vermillion bottom edge that reads as the inked face
249
- of a real \u5370\u9451. A pulsing dot in the top-right hints there's a
250
- channel here without needing a notification badge. */
251
246
  .mushi-trigger {
252
247
  position: fixed;
253
248
  width: 52px;
@@ -301,10 +296,53 @@ function getWidgetStyles(theme) {
301
296
  outline: 2px solid ${vermillion};
302
297
  outline-offset: 3px;
303
298
  }
304
- .mushi-trigger.bottom-right { bottom: 24px; right: 24px; }
305
- .mushi-trigger.bottom-left { bottom: 24px; left: 24px; }
306
- .mushi-trigger.top-right { top: 24px; right: 24px; }
307
- .mushi-trigger.top-left { top: 24px; left: 24px; }
299
+ .mushi-trigger.bottom-right {
300
+ bottom: var(--mushi-bottom, calc(24px + env(safe-area-inset-bottom, 0px)));
301
+ right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
302
+ }
303
+ .mushi-trigger.bottom-left {
304
+ bottom: var(--mushi-bottom, calc(24px + env(safe-area-inset-bottom, 0px)));
305
+ left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
306
+ }
307
+ .mushi-trigger.top-right {
308
+ top: var(--mushi-top, calc(24px + env(safe-area-inset-top, 0px)));
309
+ right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
310
+ }
311
+ .mushi-trigger.top-left {
312
+ top: var(--mushi-top, calc(24px + env(safe-area-inset-top, 0px)));
313
+ left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
314
+ }
315
+ .mushi-trigger.edge-tab {
316
+ width: 32px;
317
+ height: 88px;
318
+ border-radius: 4px 0 0 4px;
319
+ writing-mode: vertical-rl;
320
+ text-orientation: upright;
321
+ font-size: 16px;
322
+ box-shadow:
323
+ 0 1px 0 ${rule},
324
+ 0 10px 24px -14px rgba(14,13,11,0.45),
325
+ inset -3px 0 0 ${vermillion};
326
+ }
327
+ .mushi-trigger.edge-tab.bottom-right,
328
+ .mushi-trigger.edge-tab.top-right {
329
+ right: var(--mushi-right, 0);
330
+ }
331
+ .mushi-trigger.edge-tab.bottom-left,
332
+ .mushi-trigger.edge-tab.top-left {
333
+ left: var(--mushi-left, 0);
334
+ border-radius: 0 4px 4px 0;
335
+ box-shadow:
336
+ 0 1px 0 ${rule},
337
+ 0 10px 24px -14px rgba(14,13,11,0.45),
338
+ inset 3px 0 0 ${vermillion};
339
+ }
340
+ .mushi-trigger.shrunk {
341
+ width: 36px;
342
+ height: 36px;
343
+ opacity: 0.82;
344
+ transform: scale(0.92);
345
+ }
308
346
 
309
347
  @keyframes mushi-pulse {
310
348
  0% { box-shadow: 0 0 0 0 ${vermillion}; opacity: 1; }
@@ -312,12 +350,6 @@ function getWidgetStyles(theme) {
312
350
  100% { box-shadow: 0 0 0 0 rgba(224,60,44,0); opacity: 1; }
313
351
  }
314
352
 
315
- /* \u2500\u2500 Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
316
- Paper-card. Sharper corners (6px) than typical SaaS modals
317
- (which default to 12-16px and read as plastic). Two-layer shadow:
318
- one hairline that sells the paper edge, one diffuse that lifts
319
- the panel off the underlying app. No backdrop-filter \u2014 we want
320
- the widget to feel like it sits ON the page, not blur INTO it. */
321
353
  .mushi-panel {
322
354
  position: fixed;
323
355
  width: 384px;
@@ -337,10 +369,26 @@ function getWidgetStyles(theme) {
337
369
  }
338
370
  .mushi-panel.open { animation: mushi-stamp-in 320ms ${easeStamp} both; }
339
371
  .mushi-panel.closed { display: none; }
340
- .mushi-panel.bottom-right { bottom: 88px; right: 24px; --mushi-origin: bottom right; }
341
- .mushi-panel.bottom-left { bottom: 88px; left: 24px; --mushi-origin: bottom left; }
342
- .mushi-panel.top-right { top: 88px; right: 24px; --mushi-origin: top right; }
343
- .mushi-panel.top-left { top: 88px; left: 24px; --mushi-origin: top left; }
372
+ .mushi-panel.bottom-right {
373
+ bottom: var(--mushi-panel-bottom, calc(var(--mushi-bottom, 24px) + 64px));
374
+ right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
375
+ --mushi-origin: bottom right;
376
+ }
377
+ .mushi-panel.bottom-left {
378
+ bottom: var(--mushi-panel-bottom, calc(var(--mushi-bottom, 24px) + 64px));
379
+ left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
380
+ --mushi-origin: bottom left;
381
+ }
382
+ .mushi-panel.top-right {
383
+ top: var(--mushi-panel-top, calc(var(--mushi-top, 24px) + 64px));
384
+ right: var(--mushi-right, calc(24px + env(safe-area-inset-right, 0px)));
385
+ --mushi-origin: top right;
386
+ }
387
+ .mushi-panel.top-left {
388
+ top: var(--mushi-panel-top, calc(var(--mushi-top, 24px) + 64px));
389
+ left: var(--mushi-left, calc(24px + env(safe-area-inset-left, 0px)));
390
+ --mushi-origin: top left;
391
+ }
344
392
 
345
393
  @keyframes mushi-stamp-in {
346
394
  0% { opacity: 0; transform: scale(0.94) translateY(6px); }
@@ -348,10 +396,6 @@ function getWidgetStyles(theme) {
348
396
  100% { opacity: 1; transform: scale(1) translateY(0); }
349
397
  }
350
398
 
351
- /* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
352
- Editorial masthead: small mono eyebrow ("MUSHI / REPORT") on top,
353
- serif display headline below, mono step counter on the far right.
354
- A single hairline separates header from body \u2014 no card stacking. */
355
399
  .mushi-header {
356
400
  padding: 18px 20px 14px;
357
401
  border-bottom: 1px solid ${rule};
@@ -448,10 +492,6 @@ function getWidgetStyles(theme) {
448
492
  .mushi-body::-webkit-scrollbar { width: 6px; }
449
493
  .mushi-body::-webkit-scrollbar-thumb { background: ${inkFaint}; border-radius: 3px; }
450
494
 
451
- /* \u2500\u2500 Step 1: Categories as a contents-page list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
452
- No boxes. Hairline rules between rows. Hovering a row pulls a
453
- vermillion arrow in from the right and tints the row label \u2014
454
- reads like flipping through an index card. */
455
495
  .mushi-option-btn {
456
496
  display: grid;
457
497
  grid-template-columns: auto 1fr auto;
@@ -504,11 +544,6 @@ function getWidgetStyles(theme) {
504
544
  transition: opacity 220ms ${easeStamp}, transform 220ms ${easeStamp}, color 220ms ${easeStamp};
505
545
  }
506
546
 
507
- /* \u2500\u2500 Step 2: Selected-category breadcrumb + intent text-buttons \u2500
508
- Breadcrumb is a thin chip with the kanji-stamp aesthetic carried
509
- over (vermillion left rule). Intents are inline TEXT buttons
510
- with vermillion underlines on hover \u2014 not pill-shaped chips,
511
- which is the SaaS default and not what we are. */
512
547
  .mushi-selected-category {
513
548
  display: inline-flex;
514
549
  align-items: center;
@@ -564,10 +599,6 @@ function getWidgetStyles(theme) {
564
599
  box-shadow: inset 2px 0 0 ${vermillion};
565
600
  }
566
601
 
567
- /* \u2500\u2500 Step 3: Borderless textarea + minimal attach pills \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
568
- The textarea has no box around it \u2014 just a hairline underline
569
- that turns vermillion on focus. Encourages writing rather than
570
- form-filling. */
571
602
  .mushi-textarea {
572
603
  width: 100%;
573
604
  min-height: 96px;
@@ -625,10 +656,6 @@ function getWidgetStyles(theme) {
625
656
  outline-offset: 2px;
626
657
  }
627
658
 
628
- /* \u2500\u2500 Footer + submit (vermillion stamp) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
629
- Submit button is the heaviest visual moment in the widget \u2014
630
- vermillion fill, mono-caps label, send arrow. Holds an ink-
631
- bloom pseudo-element that animates outward when pressed. */
632
659
  .mushi-footer {
633
660
  padding: 14px 22px 16px;
634
661
  border-top: 1px solid ${rule};
@@ -693,10 +720,6 @@ function getWidgetStyles(theme) {
693
720
  }
694
721
  .mushi-submit:hover .mushi-submit-arrow { transform: translateX(3px); }
695
722
 
696
- /* \u2500\u2500 Step indicator (numeral ledger) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
697
- Replaces the generic three-dots with a typographic series:
698
- "01 \u2014 02 \u2014 03". The active step uses serif numerals, the
699
- others use mono so the active one literally reads heavier. */
700
723
  .mushi-step-indicator {
701
724
  display: flex;
702
725
  align-items: center;
@@ -724,10 +747,6 @@ function getWidgetStyles(theme) {
724
747
  }
725
748
  .mushi-step-sep { width: 14px; height: 1px; background: ${rule}; }
726
749
 
727
- /* \u2500\u2500 Success: \u6731\u5370 stamp animation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
728
- The success state is the signature moment. A vermillion ring
729
- scribes itself, then a "RECEIVED" mono-caps label fades in at
730
- the centre, evoking a hanko being pressed onto the form. */
731
750
  .mushi-success {
732
751
  text-align: center;
733
752
  padding: 28px 16px 20px;
@@ -791,9 +810,6 @@ function getWidgetStyles(theme) {
791
810
  100% { opacity: 1; transform: rotate(-6deg) scale(1); }
792
811
  }
793
812
 
794
- /* \u2500\u2500 Error \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
795
- Inline editorial note rather than a red box. Vermillion left
796
- rule keeps the same accent language. */
797
813
  .mushi-error {
798
814
  margin-top: 10px;
799
815
  padding: 8px 0 8px 10px;
@@ -804,9 +820,6 @@ function getWidgetStyles(theme) {
804
820
  letter-spacing: 0.02em;
805
821
  }
806
822
 
807
- /* \u2500\u2500 Reduced motion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
808
- Honour the OS preference: kill every transition + animation
809
- except the focus underline (which is critical feedback). */
810
823
  @media (prefers-reduced-motion: reduce) {
811
824
  *,
812
825
  *::before,
@@ -854,6 +867,12 @@ var MushiWidget = class {
854
867
  screenshotAttached = false;
855
868
  elementSelected = false;
856
869
  submitting = false;
870
+ triggerVisible = true;
871
+ triggerShrunk = false;
872
+ triggerHiddenByScroll = false;
873
+ attachedLaunchers = [];
874
+ smartHideCleanup = null;
875
+ smartHideTimer = null;
857
876
  /** Captured at the moment of submit so the success ledger metadata
858
877
  * ("REPORT · 14:23:07 JST") doesn't drift while the success step
859
878
  * is on screen. */
@@ -879,7 +898,16 @@ var MushiWidget = class {
879
898
  expandedTitle: config.expandedTitle ?? "",
880
899
  mode: config.mode ?? "conversational",
881
900
  locale: config.locale ?? "auto",
882
- zIndex: config.zIndex ?? 99999
901
+ zIndex: config.zIndex ?? 99999,
902
+ trigger: config.trigger ?? "auto",
903
+ attachToSelector: config.attachToSelector ?? "",
904
+ inset: config.inset ?? {},
905
+ respectSafeArea: config.respectSafeArea ?? true,
906
+ hideOnSelector: config.hideOnSelector ?? "",
907
+ hideOnRoutes: config.hideOnRoutes ?? [],
908
+ environments: config.environments ?? {},
909
+ smartHide: config.smartHide ?? false,
910
+ draggable: config.draggable ?? false
883
911
  };
884
912
  this.callbacks = callbacks;
885
913
  this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
@@ -889,6 +917,8 @@ var MushiWidget = class {
889
917
  }
890
918
  mount() {
891
919
  document.body.appendChild(this.host);
920
+ this.syncAttachedLaunchers();
921
+ this.syncSmartHide();
892
922
  this.render();
893
923
  }
894
924
  updateConfig(config = {}) {
@@ -900,9 +930,20 @@ var MushiWidget = class {
900
930
  ...config.expandedTitle !== void 0 ? { expandedTitle: config.expandedTitle } : {},
901
931
  ...config.mode ? { mode: config.mode } : {},
902
932
  ...config.locale ? { locale: config.locale } : {},
903
- ...config.zIndex !== void 0 ? { zIndex: config.zIndex } : {}
933
+ ...config.zIndex !== void 0 ? { zIndex: config.zIndex } : {},
934
+ ...config.trigger ? { trigger: config.trigger } : {},
935
+ ...config.attachToSelector !== void 0 ? { attachToSelector: config.attachToSelector } : {},
936
+ ...config.inset !== void 0 ? { inset: config.inset } : {},
937
+ ...config.respectSafeArea !== void 0 ? { respectSafeArea: config.respectSafeArea } : {},
938
+ ...config.hideOnSelector !== void 0 ? { hideOnSelector: config.hideOnSelector } : {},
939
+ ...config.hideOnRoutes !== void 0 ? { hideOnRoutes: config.hideOnRoutes } : {},
940
+ ...config.environments !== void 0 ? { environments: config.environments } : {},
941
+ ...config.smartHide !== void 0 ? { smartHide: config.smartHide } : {},
942
+ ...config.draggable !== void 0 ? { draggable: config.draggable } : {}
904
943
  };
905
944
  this.locale = getLocale(this.config.locale === "auto" ? void 0 : this.config.locale);
945
+ this.syncAttachedLaunchers();
946
+ this.syncSmartHide();
906
947
  this.render();
907
948
  }
908
949
  open(options) {
@@ -933,6 +974,30 @@ var MushiWidget = class {
933
974
  getIsOpen() {
934
975
  return this.isOpen;
935
976
  }
977
+ showTrigger() {
978
+ this.triggerVisible = true;
979
+ this.render();
980
+ }
981
+ hideTrigger() {
982
+ this.triggerVisible = false;
983
+ this.render();
984
+ }
985
+ setTrigger(trigger) {
986
+ this.updateConfig({ trigger });
987
+ }
988
+ attachTo(selectorOrElement, options = {}) {
989
+ const elements = typeof selectorOrElement === "string" ? Array.from(document.querySelectorAll(selectorOrElement)) : [selectorOrElement];
990
+ const cleanups = elements.map((el) => {
991
+ const onClick = (event) => {
992
+ event.preventDefault();
993
+ this.updateConfig(options);
994
+ this.open();
995
+ };
996
+ el.addEventListener("click", onClick);
997
+ return () => el.removeEventListener("click", onClick);
998
+ });
999
+ return () => cleanups.forEach((cleanup) => cleanup());
1000
+ }
936
1001
  setScreenshotAttached(attached) {
937
1002
  this.screenshotAttached = attached;
938
1003
  if (this.isOpen) this.render();
@@ -950,8 +1015,83 @@ var MushiWidget = class {
950
1015
  clearTimeout(this.autoCloseTimer);
951
1016
  this.autoCloseTimer = null;
952
1017
  }
1018
+ if (this.smartHideTimer !== null) {
1019
+ clearTimeout(this.smartHideTimer);
1020
+ this.smartHideTimer = null;
1021
+ }
1022
+ this.smartHideCleanup?.();
1023
+ this.smartHideCleanup = null;
1024
+ this.attachedLaunchers.forEach((cleanup) => cleanup());
1025
+ this.attachedLaunchers = [];
953
1026
  this.host.remove();
954
1027
  }
1028
+ syncAttachedLaunchers() {
1029
+ this.attachedLaunchers.forEach((cleanup) => cleanup());
1030
+ this.attachedLaunchers = [];
1031
+ if (this.config.trigger !== "attach" || !this.config.attachToSelector) return;
1032
+ if (typeof document === "undefined") return;
1033
+ this.attachedLaunchers.push(this.attachTo(this.config.attachToSelector));
1034
+ }
1035
+ syncSmartHide() {
1036
+ this.smartHideCleanup?.();
1037
+ this.smartHideCleanup = null;
1038
+ this.triggerShrunk = false;
1039
+ this.triggerHiddenByScroll = false;
1040
+ if (!this.config.smartHide || typeof window === "undefined") return;
1041
+ const smart = this.config.smartHide === true ? { onScroll: "shrink", onIdleMs: 900 } : this.config.smartHide;
1042
+ if (!smart.onScroll) return;
1043
+ const onScroll = () => {
1044
+ if (smart.onScroll === "hide") {
1045
+ this.triggerHiddenByScroll = true;
1046
+ } else {
1047
+ this.triggerShrunk = true;
1048
+ }
1049
+ this.render();
1050
+ if (this.smartHideTimer !== null) clearTimeout(this.smartHideTimer);
1051
+ this.smartHideTimer = setTimeout(() => {
1052
+ this.triggerHiddenByScroll = false;
1053
+ this.triggerShrunk = false;
1054
+ this.render();
1055
+ }, smart.onIdleMs ?? 900);
1056
+ };
1057
+ window.addEventListener("scroll", onScroll, { passive: true });
1058
+ this.smartHideCleanup = () => window.removeEventListener("scroll", onScroll);
1059
+ }
1060
+ shouldRenderTrigger() {
1061
+ if (!this.triggerVisible) return false;
1062
+ if (this.triggerHiddenByScroll) return false;
1063
+ if (this.config.trigger === "manual" || this.config.trigger === "hidden" || this.config.trigger === "attach") {
1064
+ return false;
1065
+ }
1066
+ if (this.isMobileSmartHidden()) return false;
1067
+ if (this.isRouteHidden()) return false;
1068
+ if (this.config.hideOnSelector && document.querySelector(this.config.hideOnSelector)) return false;
1069
+ const action = this.config.environments[this.detectEnvironment()];
1070
+ return action !== "never" && action !== "manual";
1071
+ }
1072
+ effectiveTrigger() {
1073
+ if (!this.config.smartHide || typeof window === "undefined") return this.config.trigger;
1074
+ const smart = this.config.smartHide === true ? { onMobile: "edge-tab" } : this.config.smartHide;
1075
+ if (window.matchMedia("(max-width: 768px)").matches && smart.onMobile === "edge-tab") {
1076
+ return "edge-tab";
1077
+ }
1078
+ return this.config.trigger;
1079
+ }
1080
+ isMobileSmartHidden() {
1081
+ if (!this.config.smartHide || typeof window === "undefined") return false;
1082
+ const smart = this.config.smartHide === true ? { onMobile: "edge-tab" } : this.config.smartHide;
1083
+ return window.matchMedia("(max-width: 768px)").matches && smart.onMobile === "hide";
1084
+ }
1085
+ detectEnvironment() {
1086
+ const host = typeof location !== "undefined" ? location.hostname : "";
1087
+ if (host === "localhost" || host === "127.0.0.1" || host.endsWith(".local")) return "development";
1088
+ if (/\b(staging|stage|preview|dev)\b/i.test(host)) return "staging";
1089
+ return "production";
1090
+ }
1091
+ isRouteHidden() {
1092
+ if (!this.config.hideOnRoutes.length || typeof location === "undefined") return false;
1093
+ return this.config.hideOnRoutes.some((route) => location.pathname.includes(route));
1094
+ }
955
1095
  getTheme() {
956
1096
  if (this.config.theme !== "auto") return this.config.theme;
957
1097
  if (typeof window !== "undefined" && window.matchMedia("(prefers-color-scheme: dark)").matches) {
@@ -967,24 +1107,29 @@ var MushiWidget = class {
967
1107
  const style = document.createElement("style");
968
1108
  style.textContent = getWidgetStyles(theme);
969
1109
  this.shadow.appendChild(style);
970
- const trigger = document.createElement("button");
971
- trigger.className = `mushi-trigger ${pos}`;
972
- trigger.textContent = this.config.triggerText;
973
- trigger.setAttribute("aria-label", t.widget.trigger);
974
- trigger.setAttribute("aria-haspopup", "dialog");
975
- trigger.setAttribute("aria-expanded", String(this.isOpen));
976
- trigger.style.zIndex = String(this.config.zIndex);
977
- trigger.addEventListener("click", () => {
978
- if (this.isOpen) this.close();
979
- else this.open();
980
- });
981
- this.shadow.appendChild(trigger);
1110
+ if (this.shouldRenderTrigger()) {
1111
+ const effectiveTrigger = this.effectiveTrigger();
1112
+ const trigger = document.createElement("button");
1113
+ trigger.className = `mushi-trigger ${pos}${effectiveTrigger === "edge-tab" ? " edge-tab" : ""}${this.triggerShrunk ? " shrunk" : ""}`;
1114
+ trigger.textContent = this.config.triggerText;
1115
+ trigger.setAttribute("aria-label", t.widget.trigger);
1116
+ trigger.setAttribute("aria-haspopup", "dialog");
1117
+ trigger.setAttribute("aria-expanded", String(this.isOpen));
1118
+ trigger.style.zIndex = String(this.config.zIndex);
1119
+ this.applyInsetVars(trigger);
1120
+ trigger.addEventListener("click", () => {
1121
+ if (this.isOpen) this.close();
1122
+ else this.open();
1123
+ });
1124
+ this.shadow.appendChild(trigger);
1125
+ }
982
1126
  const panel = document.createElement("div");
983
1127
  panel.className = `mushi-panel ${pos}${this.isOpen ? " open" : " closed"}`;
984
1128
  panel.setAttribute("role", "dialog");
985
1129
  panel.setAttribute("aria-modal", "true");
986
1130
  panel.setAttribute("aria-label", t.widget.title);
987
1131
  panel.style.zIndex = String(this.config.zIndex + 1);
1132
+ this.applyInsetVars(panel);
988
1133
  if (this.isOpen) {
989
1134
  panel.innerHTML = this.renderStep();
990
1135
  this.shadow.appendChild(panel);
@@ -992,6 +1137,20 @@ var MushiWidget = class {
992
1137
  this.trapFocus(panel);
993
1138
  }
994
1139
  }
1140
+ applyInsetVars(el) {
1141
+ const { inset } = this.config;
1142
+ if (!this.config.respectSafeArea) {
1143
+ ["top", "right", "bottom", "left"].forEach((edge) => {
1144
+ if (inset[edge] === void 0) el.style.setProperty(`--mushi-${edge}`, "24px");
1145
+ });
1146
+ }
1147
+ ["top", "right", "bottom", "left"].forEach((edge) => {
1148
+ const value = inset[edge];
1149
+ if (value === void 0) return;
1150
+ el.style.setProperty(`--mushi-${edge}`, value === "auto" ? "auto" : `${value}px`);
1151
+ });
1152
+ el.style.setProperty("--mushi-safe-area", this.config.respectSafeArea ? "1" : "0");
1153
+ }
995
1154
  renderStep() {
996
1155
  switch (this.step) {
997
1156
  case "category":
@@ -2107,6 +2266,21 @@ function createInstance(config) {
2107
2266
  open() {
2108
2267
  widget.open();
2109
2268
  },
2269
+ openWith(category) {
2270
+ widget.open({ category });
2271
+ },
2272
+ show() {
2273
+ widget.showTrigger();
2274
+ },
2275
+ hide() {
2276
+ widget.hideTrigger();
2277
+ },
2278
+ attachTo(selectorOrElement, options) {
2279
+ return widget.attachTo(selectorOrElement, options);
2280
+ },
2281
+ setTrigger(trigger) {
2282
+ widget.setTrigger(trigger);
2283
+ },
2110
2284
  close() {
2111
2285
  widget.close();
2112
2286
  },
@@ -2182,11 +2356,14 @@ function createInstance(config) {
2182
2356
  return sdk;
2183
2357
  }
2184
2358
  function mergeRuntimeConfig(config, runtime) {
2359
+ const nativeTrigger = runtime.native?.triggerMode;
2360
+ const widgetTrigger = runtime.widget?.trigger ?? (nativeTrigger === "none" || nativeTrigger === "shake" ? "manual" : void 0);
2185
2361
  return {
2186
2362
  ...config,
2187
2363
  widget: {
2188
2364
  ...config.widget,
2189
- ...runtime.widget
2365
+ ...runtime.widget,
2366
+ ...widgetTrigger ? { trigger: widgetTrigger } : {}
2190
2367
  },
2191
2368
  capture: {
2192
2369
  ...config.capture,
@@ -2242,6 +2419,16 @@ function createNoopInstance() {
2242
2419
  },
2243
2420
  updateConfig: () => {
2244
2421
  },
2422
+ openWith: () => {
2423
+ },
2424
+ show: () => {
2425
+ },
2426
+ hide: () => {
2427
+ },
2428
+ attachTo: () => () => {
2429
+ },
2430
+ setTrigger: () => {
2431
+ },
2245
2432
  destroy: () => {
2246
2433
  instance = null;
2247
2434
  },