dsh-better-left-sidebar 0.1.0 → 0.1.2

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/lib/client.js CHANGED
@@ -39,9 +39,9 @@ html.dsh-better-left-sidebar {
39
39
  }
40
40
 
41
41
  html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] {
42
- --dsh-better-left-sidebar-rail: 56px;
42
+ --dsh-better-left-sidebar-rail: min(56px, 100%);
43
43
  --dsh-better-left-sidebar-open: 100%;
44
- --dsh-better-left-sidebar-conversation: calc(100% - var(--dsh-better-left-sidebar-rail));
44
+ --dsh-better-left-sidebar-conversation: max(0px, calc(100% - var(--dsh-better-left-sidebar-rail)));
45
45
  position: relative !important;
46
46
  display: block !important;
47
47
  min-height: 0;
@@ -88,7 +88,8 @@ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sideb
88
88
  html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="brand"],
89
89
  html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="new-session"],
90
90
  html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="global-navigation"],
91
- html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="tools"] {
91
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="tools"],
92
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="compact-widgets"] {
92
93
  box-sizing: border-box;
93
94
  min-width: 0;
94
95
  margin: 0;
@@ -277,6 +278,94 @@ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sideb
277
278
  scrollbar-width: none;
278
279
  }
279
280
 
281
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="compact-widgets"] {
282
+ position: absolute;
283
+ z-index: 2;
284
+ bottom: var(--dsh-better-left-sidebar-compact-widgets-bottom, 16px);
285
+ left: 0;
286
+ display: flex;
287
+ width: 56px;
288
+ max-height: var(--dsh-better-left-sidebar-compact-widgets-height, 0px);
289
+ flex-direction: column;
290
+ align-items: stretch;
291
+ gap: 4px;
292
+ box-sizing: border-box;
293
+ padding: 0;
294
+ overflow: hidden;
295
+ }
296
+
297
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-compact-widget-clipped] {
298
+ visibility: hidden;
299
+ }
300
+
301
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] [data-dsh-better-left-sidebar-region="compact-widgets"][hidden] {
302
+ display: none !important;
303
+ }
304
+
305
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] .dsh-better-left-sidebar-compact-widget-box {
306
+ width: 100%;
307
+ flex: none;
308
+ overflow: hidden;
309
+ pointer-events: none;
310
+ }
311
+
312
+ /* A widget that renders nothing must not take a flex gap. */
313
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] .dsh-better-left-sidebar-compact-widget-box:not(:has(> [data-slot] > *)) {
314
+ display: none;
315
+ }
316
+
317
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] .dsh-better-left-sidebar-clock,
318
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview .dsh-better-left-sidebar-clock {
319
+ display: flex;
320
+ width: 100%;
321
+ min-width: 0;
322
+ box-sizing: border-box;
323
+ flex-direction: column;
324
+ align-items: center;
325
+ justify-content: center;
326
+ gap: 1px;
327
+ overflow: hidden;
328
+ color: var(--dsw-alias-label-secondary);
329
+ font-variant-numeric: tabular-nums;
330
+ font-feature-settings: 'tnum' 1;
331
+ line-height: 16px;
332
+ white-space: nowrap;
333
+ }
334
+
335
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] .dsh-better-left-sidebar-clock-time,
336
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview .dsh-better-left-sidebar-clock-time {
337
+ display: inline-flex;
338
+ align-items: baseline;
339
+ gap: 3px;
340
+ color: var(--dsw-alias-label-primary);
341
+ font-size: 12px;
342
+ font-weight: 650;
343
+ }
344
+
345
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] .dsh-better-left-sidebar-clock-period,
346
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview .dsh-better-left-sidebar-clock-period {
347
+ font-size: 12px;
348
+ font-weight: 650;
349
+ line-height: 16px;
350
+ }
351
+
352
+ html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root] .dsh-better-left-sidebar-clock-date,
353
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview .dsh-better-left-sidebar-clock-date {
354
+ font-size: 11px;
355
+ }
356
+
357
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-visually-hidden {
358
+ position: absolute !important;
359
+ width: 1px;
360
+ height: 1px;
361
+ padding: 0;
362
+ margin: -1px;
363
+ overflow: hidden;
364
+ clip: rect(0, 0, 0, 0);
365
+ white-space: nowrap;
366
+ border: 0;
367
+ }
368
+
280
369
  html.dsh-better-left-sidebar [data-slot="sidebar"] > [data-dsh-better-left-sidebar-root][data-dsh-better-left-sidebar-tools-position="rail-bottom"] [data-dsh-better-left-sidebar-region="tools"] {
281
370
  right: auto;
282
371
  bottom: 8px;
@@ -540,12 +629,18 @@ html.dsh-better-left-sidebar[data-dsh-better-left-sidebar-animation="off"] {
540
629
  }
541
630
  }
542
631
 
543
- /* DSH-native profile settings: compact controls, a real location preview, and readable states. */
632
+ /* DSH-native staged settings. Keep field controls and previews within one reading width. */
633
+ html.dsh-better-left-sidebar div:has(> .dsh-better-left-sidebar-preferences) {
634
+ width: 100%;
635
+ max-width: 780px;
636
+ box-sizing: border-box;
637
+ }
544
638
  html.dsh-better-left-sidebar .dsh-better-left-sidebar-preferences {
545
639
  display: flex;
546
640
  min-width: 0;
547
641
  flex-direction: column;
548
- gap: 24px;
642
+ gap: 28px;
643
+ container-type: inline-size;
549
644
  color: var(--dsw-alias-label-primary);
550
645
  }
551
646
 
@@ -569,9 +664,228 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-settings-group h3 {
569
664
  line-height: 24px;
570
665
  }
571
666
 
572
- html.dsh-better-left-sidebar .dsh-better-left-sidebar-choice-row {
667
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widgets-intro {
668
+ display: grid;
669
+ gap: 6px;
670
+ }
671
+
672
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widgets-hint,
673
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widgets-empty {
674
+ margin: 0;
675
+ color: var(--dsw-alias-label-tertiary);
676
+ font-size: 12px;
677
+ line-height: 1.5;
678
+ }
679
+
680
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widgets-link {
681
+ color: var(--dsw-alias-link);
682
+ text-decoration: none;
683
+ }
684
+
685
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widgets-link:hover {
686
+ text-decoration: underline;
687
+ }
688
+
689
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widgets-list {
690
+ display: flex;
691
+ min-width: 0;
692
+ flex-direction: column;
693
+ }
694
+
695
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widget-setting {
696
+ display: grid;
697
+ min-width: 0;
698
+ grid-template-columns: minmax(0, 1fr) auto;
699
+ align-items: center;
700
+ gap: 12px;
701
+ padding: 14px 0;
702
+ border-top: 1px solid var(--dsw-alias-border-l3);
703
+ }
704
+
705
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-options {
706
+ grid-column: 1 / -1;
707
+ min-width: 0;
708
+ }
709
+
710
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-options > summary,
711
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-reference > summary {
712
+ width: fit-content;
713
+ color: var(--dsw-alias-label-secondary);
714
+ font-size: 13px;
715
+ cursor: pointer;
716
+ line-height: 24px;
717
+ }
718
+
719
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-reference {
720
+ color: var(--dsw-alias-label-secondary);
721
+ font-size: 12px;
722
+ }
723
+
724
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-reference dl {
725
+ display: grid;
726
+ gap: 4px;
727
+ margin: 12px 0;
728
+ }
729
+
730
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-reference dt {
731
+ margin-top: 6px;
732
+ }
733
+
734
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-reference dd {
735
+ margin: 0;
736
+ overflow-wrap: anywhere;
737
+ }
738
+
739
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-reference a {
740
+ color: var(--dsw-alias-link);
741
+ }
742
+
743
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-options {
744
+ display: grid;
745
+ grid-template-columns: minmax(0, 420px) 104px;
746
+ max-width: 548px;
747
+ align-items: start;
748
+ gap: 24px;
749
+ margin-top: 12px;
750
+ }
751
+
752
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-option-fields {
573
753
  display: grid;
574
- grid-template-columns: minmax(100px, 150px) minmax(0, 1fr);
754
+ min-width: 0;
755
+ gap: 10px;
756
+ }
757
+
758
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-modules {
759
+ display: flex;
760
+ min-height: 32px;
761
+ align-items: center;
762
+ flex-wrap: wrap;
763
+ gap: 12px 20px;
764
+ }
765
+
766
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-controls {
767
+ display: flex;
768
+ min-width: 0;
769
+ flex-wrap: wrap;
770
+ align-items: center;
771
+ gap: 8px;
772
+ }
773
+
774
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-picker {
775
+ position: relative;
776
+ width: 28px;
777
+ height: 28px;
778
+ flex: none;
779
+ cursor: pointer;
780
+ }
781
+
782
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-swatch {
783
+ display: block;
784
+ width: 28px;
785
+ height: 28px;
786
+ box-sizing: border-box;
787
+ border: 1px solid var(--dsw-alias-border-l3);
788
+ border-radius: 6px;
789
+ background: var(--dsw-alias-label-primary);
790
+ }
791
+
792
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-input {
793
+ position: absolute;
794
+ inset: 0;
795
+ width: 100%;
796
+ height: 100%;
797
+ margin: 0;
798
+ padding: 0;
799
+ border: 0;
800
+ opacity: 0;
801
+ cursor: pointer;
802
+ }
803
+
804
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-picker:focus-within {
805
+ outline: 2px solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
806
+ outline-offset: 2px;
807
+ }
808
+
809
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-value {
810
+ color: var(--dsw-alias-label-primary);
811
+ font-size: 13px;
812
+ }
813
+
814
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-hint {
815
+ margin: 6px 0 0;
816
+ color: var(--dsw-alias-label-tertiary);
817
+ font-size: 12px;
818
+ line-height: 1.5;
819
+ }
820
+
821
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview {
822
+ width: 104px;
823
+ min-width: 56px;
824
+ margin: 0;
825
+ }
826
+
827
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview figcaption {
828
+ margin: 0 0 8px;
829
+ color: var(--dsw-alias-label-primary);
830
+ font-size: 12px;
831
+ font-weight: 600;
832
+ white-space: nowrap;
833
+ text-align: center;
834
+ }
835
+
836
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview-rail {
837
+ display: flex;
838
+ box-sizing: border-box;
839
+ width: 56px;
840
+ min-height: 80px;
841
+ margin-inline: auto;
842
+ align-items: center;
843
+ justify-content: center;
844
+ padding: 0;
845
+ border: 0;
846
+ background: var(--dsw-specific-sidebar-fill, var(--dsw-alias-bg-base));
847
+ }
848
+
849
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widget-info {
850
+ display: grid;
851
+ min-width: 0;
852
+ gap: 4px;
853
+ }
854
+
855
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widget-name {
856
+ overflow-wrap: anywhere;
857
+ color: var(--dsw-alias-label-primary);
858
+ font-size: 14px;
859
+ font-weight: 600;
860
+ line-height: 19px;
861
+ }
862
+
863
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widget-source,
864
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widget-status {
865
+ overflow-wrap: anywhere;
866
+ color: var(--dsw-alias-label-secondary);
867
+ font-size: 12px;
868
+ line-height: 17px;
869
+ }
870
+
871
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widget-status.is-failed {
872
+ color: var(--dsw-alias-state-danger, #b54736);
873
+ }
874
+
875
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-compact-widget-controls {
876
+ display: flex;
877
+ align-items: center;
878
+ gap: 12px;
879
+ }
880
+
881
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-widget-order {
882
+ display: flex;
883
+ gap: 4px;
884
+ }
885
+
886
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-settings-row {
887
+ display: grid;
888
+ grid-template-columns: 108px minmax(0, 1fr);
575
889
  align-items: center;
576
890
  gap: 12px;
577
891
  min-width: 0;
@@ -580,7 +894,7 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-choice-row {
580
894
  border: 0;
581
895
  }
582
896
 
583
- html.dsh-better-left-sidebar .dsh-better-left-sidebar-choice-row legend {
897
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-settings-row legend {
584
898
  float: left;
585
899
  width: 100%;
586
900
  padding: 0;
@@ -596,10 +910,10 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-choice-options {
596
910
 
597
911
  html.dsh-better-left-sidebar .dsh-better-left-sidebar-choice-option {
598
912
  display: inline-flex;
599
- min-height: 36px;
913
+ min-height: 32px;
600
914
  align-items: center;
601
915
  gap: 7px;
602
- padding: 0 11px;
916
+ padding: 0 9px;
603
917
  border: 1px solid var(--dsw-alias-border-l3);
604
918
  border-radius: var(--dsw-radius-md);
605
919
  background: var(--dsw-alias-bg-base);
@@ -672,7 +986,7 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-position-controls {
672
986
 
673
987
  html.dsh-better-left-sidebar .dsh-better-left-sidebar-position {
674
988
  display: grid;
675
- grid-template-columns: minmax(130px, 1fr) minmax(180px, 1.2fr);
989
+ grid-template-columns: 108px minmax(0, 1fr);
676
990
  align-items: center;
677
991
  gap: 12px;
678
992
  min-width: 0;
@@ -693,10 +1007,18 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-position select {
693
1007
  font-size: 13px;
694
1008
  }
695
1009
 
696
- html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-previews {
1010
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-settings {
697
1011
  display: grid;
698
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
699
- gap: 12px;
1012
+ grid-template-columns: minmax(0, 1fr) 240px;
1013
+ gap: 16px 24px;
1014
+ align-items: start;
1015
+ }
1016
+
1017
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-settings > .dsh-better-left-sidebar-settings-heading {
1018
+ grid-column: 1 / -1;
1019
+ }
1020
+
1021
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-setting-control {
700
1022
  min-width: 0;
701
1023
  }
702
1024
 
@@ -809,18 +1131,77 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-preview-hidden {
809
1131
  font-style: italic;
810
1132
  }
811
1133
 
812
- html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-cancel {
813
- align-self: flex-start;
1134
+ /* Sit beside the settings form's save control. The form is the parent of the preferences block. */
1135
+ html.dsh-better-left-sidebar div:has(> .dsh-better-left-sidebar-preferences):has(> .dsh-better-left-sidebar-layout-cancel) {
1136
+ display: grid;
1137
+ grid-template-columns: auto minmax(0, 1fr);
1138
+ align-items: center;
1139
+ column-gap: 8px;
1140
+ row-gap: 16px;
1141
+ }
1142
+
1143
+ html.dsh-better-left-sidebar div:has(> .dsh-better-left-sidebar-layout-cancel) > .dsh-better-left-sidebar-preferences,
1144
+ html.dsh-better-left-sidebar div:has(> .dsh-better-left-sidebar-layout-cancel) > p {
1145
+ grid-column: 1 / -1;
1146
+ }
1147
+
1148
+ html.dsh-better-left-sidebar div:has(> .dsh-better-left-sidebar-layout-cancel) > :last-child {
1149
+ padding-top: 0;
1150
+ }
1151
+
1152
+ @container (max-width: 620px) {
1153
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-settings {
1154
+ grid-template-columns: 1fr;
1155
+ }
1156
+
1157
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-preview {
1158
+ max-width: 320px;
1159
+ }
1160
+ }
1161
+
1162
+ @container (max-width: 480px) {
1163
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-options {
1164
+ grid-template-columns: 1fr;
1165
+ gap: 16px;
1166
+ }
1167
+
1168
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview {
1169
+ display: flex;
1170
+ width: auto;
1171
+ align-items: center;
1172
+ gap: 16px;
1173
+ }
1174
+
1175
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview-rail {
1176
+ min-height: 64px;
1177
+ margin: 0;
1178
+ }
1179
+
1180
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-preview figcaption {
1181
+ margin: 0;
1182
+ }
1183
+
1184
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-settings-row,
1185
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-position {
1186
+ grid-template-columns: 1fr;
1187
+ gap: 6px;
1188
+ }
1189
+
1190
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-settings-row legend {
1191
+ float: none;
1192
+ width: auto;
1193
+ margin-bottom: 6px;
1194
+ }
814
1195
  }
815
1196
 
816
1197
  /* Keep sessions open behind a full-width editor, instead of squeezing a form into 287px. */
817
1198
  @media (max-width: 760px) {
818
- html.dsh-better-left-sidebar .dsh-better-left-sidebar-choice-row {
1199
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-settings-row {
819
1200
  grid-template-columns: 1fr;
820
1201
  gap: 7px;
821
1202
  }
822
1203
 
823
- html.dsh-better-left-sidebar .dsh-better-left-sidebar-choice-row legend {
1204
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-settings-row legend {
824
1205
  float: none;
825
1206
  width: auto;
826
1207
  }
@@ -835,6 +1216,15 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-cancel {
835
1216
  grid-template-columns: repeat(2, minmax(0, 1fr));
836
1217
  }
837
1218
 
1219
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-options,
1220
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color {
1221
+ grid-template-columns: 1fr;
1222
+ }
1223
+
1224
+ html.dsh-better-left-sidebar .dsh-better-left-sidebar-clock-color-hint {
1225
+ grid-column: 1;
1226
+ }
1227
+
838
1228
  html.dsh-better-left-sidebar [data-slot='main'] > section:has(> div > aside) {
839
1229
  position: fixed;
840
1230
  z-index: 100;
@@ -903,6 +1293,80 @@ html.dsh-better-left-sidebar .dsh-better-left-sidebar-layout-cancel {
903
1293
  }
904
1294
  `;
905
1295
 
1296
+ // src/clock-display.ts
1297
+ var CLOCK_WIDGET_ID = "dsh-better-left-sidebar.clock";
1298
+ var DEFAULT_CLOCK_DISPLAY = {
1299
+ hourCycle: "h23",
1300
+ monthDay: true,
1301
+ weekday: false,
1302
+ color: null
1303
+ };
1304
+ var HEX = /^#[0-9a-fA-F]{6}$/;
1305
+ function normalizeClockDisplay(value) {
1306
+ const input = value && typeof value === "object" ? value : {};
1307
+ const hourCycle = input.hourCycle === "h12" ? "h12" : "h23";
1308
+ const raw = typeof input.color === "string" ? input.color.trim() : "";
1309
+ return {
1310
+ hourCycle,
1311
+ ...clockModules(input),
1312
+ color: HEX.test(raw) ? raw.toLowerCase() : null
1313
+ };
1314
+ }
1315
+ function toClockConfig(value) {
1316
+ const display = normalizeClockDisplay(value);
1317
+ return {
1318
+ hourCycle: display.hourCycle,
1319
+ monthDay: display.monthDay,
1320
+ weekday: display.weekday,
1321
+ color: display.color ?? ""
1322
+ };
1323
+ }
1324
+ function clockLines(now, locale, display) {
1325
+ const tag = Intl.DateTimeFormat.supportedLocalesOf(locale).length > 0 ? locale : void 0;
1326
+ const hourCycle = display.hourCycle;
1327
+ const timeParts = new Intl.DateTimeFormat(tag, {
1328
+ hour: "2-digit",
1329
+ minute: "2-digit",
1330
+ hourCycle
1331
+ }).formatToParts(now);
1332
+ const hour = timeParts.find((part) => part.type === "hour")?.value ?? "00";
1333
+ const minute = timeParts.find((part) => part.type === "minute")?.value ?? "00";
1334
+ const period = hourCycle === "h12" ? now.getHours() >= 12 ? "P" : "A" : null;
1335
+ return {
1336
+ time: `${hour}:${minute}`,
1337
+ period,
1338
+ monthDay: display.monthDay ? monthDayLine(now, tag) : null,
1339
+ weekday: display.weekday ? new Intl.DateTimeFormat(tag, { weekday: "short" }).format(now) : null,
1340
+ accessible: new Intl.DateTimeFormat(tag, {
1341
+ weekday: "long",
1342
+ year: "numeric",
1343
+ month: "long",
1344
+ day: "numeric",
1345
+ hour: "2-digit",
1346
+ minute: "2-digit",
1347
+ hourCycle,
1348
+ timeZoneName: "long"
1349
+ }).format(now)
1350
+ };
1351
+ }
1352
+ function clockModules(input) {
1353
+ const legacy = input.detail === "weekday" || input.detail === "none";
1354
+ const explicit = input.monthDay === false || input.weekday === true;
1355
+ if (legacy && !explicit) {
1356
+ return input.detail === "weekday" ? { monthDay: false, weekday: true } : { monthDay: false, weekday: false };
1357
+ }
1358
+ return {
1359
+ monthDay: input.monthDay !== false,
1360
+ weekday: input.weekday === true
1361
+ };
1362
+ }
1363
+ function monthDayLine(now, tag) {
1364
+ const dateParts = new Intl.DateTimeFormat(tag, { month: "numeric", day: "numeric" }).formatToParts(now);
1365
+ const month = dateParts.find((part) => part.type === "month")?.value ?? String(now.getMonth() + 1);
1366
+ const day = dateParts.find((part) => part.type === "day")?.value ?? String(now.getDate());
1367
+ return `${month}/${day}`;
1368
+ }
1369
+
906
1370
  // src/preferences.ts
907
1371
  var DEFAULT_PREFERENCES = {
908
1372
  animation: "standard",
@@ -962,7 +1426,40 @@ function createNavigationToggle(onClick) {
962
1426
  var NAMESPACE = "dsh-better-left-sidebar";
963
1427
  var en = {
964
1428
  summary: "Set how navigation opens and where each built-in area lives.",
965
- navigationBehavior: "Navigation behavior",
1429
+ compactWidgets: "Compact widgets",
1430
+ compactWidgetsHint: "Order by priority; widgets at the top are shown first when space is limited. Changes apply when you save.",
1431
+ compactWidgetsDocs: "compact widget guide",
1432
+ compactWidgetsDocsUrl: "https://github.com/exoticknight/dsh-better-left-sidebar/blob/main/docs/compact-widgets.md",
1433
+ compactWidgetsEmpty: "No compact widgets are registered or saved.",
1434
+ widgetSettings: "Display settings",
1435
+ widgetInformation: "Widget information and developer guide",
1436
+ clockContents: "Show",
1437
+ clockColorCustom: "Custom color",
1438
+ effectPreview: "Effect preview",
1439
+ widgetClipped: "Hidden: not enough space",
1440
+ widgetInactive: "Hidden while navigation is expanded",
1441
+ widgetEmpty: "No content",
1442
+ widgetEnabled: "Enabled",
1443
+ widgetPendingEnable: "Enable on save",
1444
+ widgetPendingDisable: "Disable on save",
1445
+ clock: "Clock",
1446
+ clockHourCycle: "Hour cycle",
1447
+ clock24: "24-hour",
1448
+ clock12: "12-hour",
1449
+ clockMonthDay: "Date",
1450
+ clockWeekday: "Weekday",
1451
+ clockColor: "Time color",
1452
+ clockColorDefault: "Follow theme",
1453
+ clockColorReset: "Restore default",
1454
+ clockColorHint: "A custom color stays fixed when the theme changes.",
1455
+ widgetShown: "Shown",
1456
+ widgetDisabled: "Disabled",
1457
+ widgetNotLoaded: "Not loaded",
1458
+ widgetFailed: "Failed",
1459
+ widgetEnable: "Enable",
1460
+ moveUp: "Move up",
1461
+ moveDown: "Move down",
1462
+ navigationBehavior: "Expansion and animation",
966
1463
  contentLayout: "Content layout",
967
1464
  expansion: "Expansion",
968
1465
  expandNavigation: "Expand navigation",
@@ -1000,7 +1497,40 @@ var en = {
1000
1497
  };
1001
1498
  var zh = {
1002
1499
  summary: "\u8BBE\u7F6E\u5BFC\u822A\u5C55\u5F00\u65B9\u5F0F\uFF0C\u5E76\u4E3A\u5404\u4E2A\u533A\u57DF\u9009\u62E9\u4F4D\u7F6E\u3002",
1003
- navigationBehavior: "\u5BFC\u822A\u884C\u4E3A",
1500
+ compactWidgets: "\u6536\u8D77\u680F\u5C0F\u7EC4\u4EF6",
1501
+ compactWidgetsHint: "\u6309\u4F18\u5148\u7EA7\u6392\u5E8F\uFF0C\u7A7A\u95F4\u4E0D\u8DB3\u65F6\u4F18\u5148\u663E\u793A\u9760\u524D\u7684\u5C0F\u7EC4\u4EF6\u3002\u66F4\u6539\u5728\u4FDD\u5B58\u540E\u751F\u6548\u3002",
1502
+ compactWidgetsDocs: "\u6536\u8D77\u680F\u5C0F\u7EC4\u4EF6\u6307\u5357",
1503
+ compactWidgetsDocsUrl: "https://github.com/exoticknight/dsh-better-left-sidebar/blob/main/docs/compact-widgets.md",
1504
+ compactWidgetsEmpty: "\u76EE\u524D\u6CA1\u6709\u5DF2\u6CE8\u518C\u6216\u5DF2\u4FDD\u5B58\u7684\u5C0F\u7EC4\u4EF6\u3002",
1505
+ widgetSettings: "\u663E\u793A\u8BBE\u7F6E",
1506
+ widgetInformation: "\u7EC4\u4EF6\u4FE1\u606F\u4E0E\u63A5\u5165\u6307\u5357",
1507
+ clockContents: "\u663E\u793A\u5185\u5BB9",
1508
+ clockColorCustom: "\u81EA\u5B9A\u4E49\u989C\u8272",
1509
+ effectPreview: "\u6548\u679C\u9884\u89C8",
1510
+ widgetClipped: "\u7A7A\u95F4\u4E0D\u8DB3\uFF0C\u6682\u672A\u663E\u793A",
1511
+ widgetInactive: "\u5BFC\u822A\u5C55\u5F00\u65F6\u6682\u4E0D\u663E\u793A",
1512
+ widgetEmpty: "\u6682\u65E0\u663E\u793A\u5185\u5BB9",
1513
+ widgetEnabled: "\u5DF2\u542F\u7528",
1514
+ widgetPendingEnable: "\u4FDD\u5B58\u540E\u542F\u7528",
1515
+ widgetPendingDisable: "\u4FDD\u5B58\u540E\u505C\u7528",
1516
+ clock: "\u65F6\u949F",
1517
+ clockHourCycle: "\u5C0F\u65F6\u5236",
1518
+ clock24: "24 \u5C0F\u65F6",
1519
+ clock12: "12 \u5C0F\u65F6",
1520
+ clockMonthDay: "\u65E5\u671F",
1521
+ clockWeekday: "\u661F\u671F",
1522
+ clockColor: "\u65F6\u95F4\u989C\u8272",
1523
+ clockColorDefault: "\u8DDF\u968F\u4E3B\u9898",
1524
+ clockColorReset: "\u6062\u590D\u9ED8\u8BA4",
1525
+ clockColorHint: "\u81EA\u5B9A\u4E49\u989C\u8272\u5728\u5207\u6362\u4E3B\u9898\u65F6\u4FDD\u6301\u4E0D\u53D8\u3002",
1526
+ widgetShown: "\u663E\u793A\u4E2D",
1527
+ widgetDisabled: "\u5DF2\u7981\u7528",
1528
+ widgetNotLoaded: "\u672A\u52A0\u8F7D",
1529
+ widgetFailed: "\u5931\u8D25",
1530
+ widgetEnable: "\u542F\u7528",
1531
+ moveUp: "\u4E0A\u79FB",
1532
+ moveDown: "\u4E0B\u79FB",
1533
+ navigationBehavior: "\u5C55\u5F00\u4E0E\u52A8\u753B",
1004
1534
  contentLayout: "\u5185\u5BB9\u5E03\u5C40",
1005
1535
  expansion: "\u5C55\u5F00\u65B9\u5F0F",
1006
1536
  expandNavigation: "\u5C55\u5F00\u5BFC\u822A",
@@ -1043,8 +1573,17 @@ function fallbackTranslate(key) {
1043
1573
  return key;
1044
1574
  }
1045
1575
 
1576
+ // src/client/rail-region.ts
1577
+ function isRailRegionEligible(region, preferences) {
1578
+ if (region === "brand") return preferences?.brandPosition === "navigation-top";
1579
+ if (region === "global-navigation") return preferences?.globalNavigationPosition === "rail";
1580
+ if (region === "compact-widgets") return true;
1581
+ if (region === "tools") return preferences?.toolsPosition === "rail-bottom";
1582
+ return false;
1583
+ }
1584
+
1046
1585
  // src/client/interaction.ts
1047
- var NAV_REGION = '[data-dsh-better-left-sidebar-region="brand"], [data-dsh-better-left-sidebar-region="global-navigation"], [data-dsh-better-left-sidebar-region="tools"]';
1586
+ var NAV_REGION = '[data-dsh-better-left-sidebar-region="brand"], [data-dsh-better-left-sidebar-region="global-navigation"], [data-dsh-better-left-sidebar-region="tools"], [data-dsh-better-left-sidebar-region="compact-widgets"]';
1048
1587
  var POPUP_SELECTOR = '[role="menu"], [role="listbox"], [role="dialog"], [data-menu-material]';
1049
1588
  var ENTER_DELAY = 160;
1050
1589
  var CLOSE_DELAY = 210;
@@ -1067,6 +1606,7 @@ function connectInteraction() {
1067
1606
  let associatedPopups = /* @__PURE__ */ new Set();
1068
1607
  let popupPending = false;
1069
1608
  let toggle;
1609
+ let setCompactWidgetsActive;
1070
1610
  let resizeObserver;
1071
1611
  let rootIdOwned = false;
1072
1612
  let documentCleanup;
@@ -1075,9 +1615,7 @@ function connectInteraction() {
1075
1615
  if (!root || !(target instanceof Element) || !root.contains(target)) return false;
1076
1616
  const region = target.closest(NAV_REGION);
1077
1617
  if (!region) return false;
1078
- if (region.dataset.dshBetterLeftSidebarRegion === "brand") return preferences?.brandPosition === "navigation-top";
1079
- if (region.dataset.dshBetterLeftSidebarRegion === "global-navigation") return preferences?.globalNavigationPosition === "rail";
1080
- return preferences?.toolsPosition === "rail-bottom";
1618
+ return isRailRegionEligible(region.dataset.dshBetterLeftSidebarRegion ?? "", preferences);
1081
1619
  };
1082
1620
  const isPopupTarget = (target) => {
1083
1621
  if (!(target instanceof Node)) return false;
@@ -1092,6 +1630,7 @@ function connectInteraction() {
1092
1630
  mode = next;
1093
1631
  classRoot.classList.toggle("dsh-better-left-sidebar-expanded", next !== "compact");
1094
1632
  classRoot.classList.toggle("dsh-better-left-sidebar-pinned", next === "pinned");
1633
+ setCompactWidgetsActive?.(next === "compact");
1095
1634
  syncToggle();
1096
1635
  };
1097
1636
  const clearTimer = (timer) => {
@@ -1235,14 +1774,20 @@ function connectInteraction() {
1235
1774
  if (full) full.textContent = label;
1236
1775
  updateTogglePosition();
1237
1776
  };
1238
- const createToggle = () => {
1239
- if (!root || preferences?.expansion !== "click" || toggle) return;
1240
- toggle = createNavigationToggle(onToggleClick);
1241
- document.body.append(toggle);
1777
+ const refreshToggleObservation = () => {
1778
+ if (!root || !toggle) return;
1779
+ resizeObserver?.disconnect();
1242
1780
  resizeObserver = new ResizeObserver(updateTogglePosition);
1243
1781
  resizeObserver.observe(root);
1244
1782
  const tools = root.querySelector('[data-dsh-better-left-sidebar-region="tools"]');
1245
1783
  if (tools) resizeObserver.observe(tools);
1784
+ updateTogglePosition();
1785
+ };
1786
+ const createToggle = () => {
1787
+ if (!root || preferences?.expansion !== "click" || toggle) return;
1788
+ toggle = createNavigationToggle(onToggleClick);
1789
+ document.body.append(toggle);
1790
+ refreshToggleObservation();
1246
1791
  window.addEventListener("resize", updateTogglePosition);
1247
1792
  window.addEventListener("scroll", updateTogglePosition, true);
1248
1793
  syncToggle();
@@ -1357,8 +1902,11 @@ function connectInteraction() {
1357
1902
  };
1358
1903
  };
1359
1904
  documentCleanup = attachDocumentListeners();
1360
- const setRoot = (next) => {
1361
- if (next === root) return;
1905
+ const setRoot = (next, refreshAnchors = false) => {
1906
+ if (next === root) {
1907
+ if (refreshAnchors) refreshToggleObservation();
1908
+ return;
1909
+ }
1362
1910
  if (root && rootIdOwned && root.id === "dsh-better-left-sidebar-navigation") root.removeAttribute("id");
1363
1911
  rootIdOwned = false;
1364
1912
  removeToggle();
@@ -1395,6 +1943,10 @@ function connectInteraction() {
1395
1943
  else removeToggle();
1396
1944
  syncToggle();
1397
1945
  },
1946
+ setCompactWidgetsActive(setActive) {
1947
+ setCompactWidgetsActive = setActive;
1948
+ setActive(mode === "compact");
1949
+ },
1398
1950
  setTranslate(next) {
1399
1951
  translate = next;
1400
1952
  syncToggle();
@@ -1441,14 +1993,50 @@ var REGION_MARK = "data-dsh-better-left-sidebar-region";
1441
1993
  var ROW_MARK = "data-dsh-better-left-sidebar-panel-row";
1442
1994
  var GLYPH_MARK = "data-dsh-better-left-sidebar-panel-glyph";
1443
1995
  var TITLE_MARK = "data-dsh-better-left-sidebar-panel-title";
1444
- function connectLayoutAdapter(onRoot) {
1996
+ function sizeConversationNavigation(rootHeight, navigationTop, footerHeight, panelContentHeight) {
1997
+ const height = Math.max(0, rootHeight);
1998
+ const top = Math.max(0, navigationTop);
1999
+ const footer = Math.max(0, footerHeight);
2000
+ const content = Math.max(0, panelContentHeight);
2001
+ const availableContentHeight = Math.max(0, height - top - footer - 8);
2002
+ const contentReserve = Math.min(112, availableContentHeight);
2003
+ const panelHeight = Math.min(content, Math.max(0, availableContentHeight - contentReserve));
2004
+ return {
2005
+ panelHeight,
2006
+ conversationTop: top + (panelHeight > 0 ? panelHeight + 8 : 0)
2007
+ };
2008
+ }
2009
+ var COMPACT_WIDGETS_MAX_HEIGHT = 112;
2010
+ var COMPACT_WIDGET_GAP = 4;
2011
+ var CLIPPED_MARK = "data-dsh-better-left-sidebar-compact-widget-clipped";
2012
+ function fitCompactWidgets(heights, gap, limit) {
2013
+ let height = 0;
2014
+ let visible = 0;
2015
+ let rendered = 0;
2016
+ for (const boxHeight of heights) {
2017
+ if (boxHeight <= 0) {
2018
+ visible += 1;
2019
+ continue;
2020
+ }
2021
+ const next = height + (rendered > 0 ? gap : 0) + boxHeight;
2022
+ if (next > limit) break;
2023
+ height = next;
2024
+ visible += 1;
2025
+ rendered += 1;
2026
+ }
2027
+ return { visible, height };
2028
+ }
2029
+ function connectLayoutAdapter(onRoot, compactWidgets) {
1445
2030
  let preferences;
2031
+ let compactWidgetCount = 0;
2032
+ let compactWidgetsActive = true;
1446
2033
  let current;
1447
- let sidebarObserver;
2034
+ let candidateRoot;
2035
+ let observedSidebar;
1448
2036
  let childrenObserver;
1449
2037
  let measurementObserver;
1450
2038
  let collapseObserver;
1451
- let pendingObserver;
2039
+ let hostObserver;
1452
2040
  let resizeObserver;
1453
2041
  let measureFrame = 0;
1454
2042
  let warnedFor = /* @__PURE__ */ new WeakSet();
@@ -1465,7 +2053,7 @@ function connectLayoutAdapter(onRoot) {
1465
2053
  !conversation && "conversation/workspace region",
1466
2054
  !tools && "footer tools region"
1467
2055
  ].filter(Boolean);
1468
- if (missing.length) {
2056
+ if (missing.length > 0) {
1469
2057
  if (!warnedFor.has(root)) {
1470
2058
  console.warn(`[dsh-better-left-sidebar] DSH sidebar adapter is inactive: missing ${missing.join(", ")}.`);
1471
2059
  warnedFor.add(root);
@@ -1507,15 +2095,30 @@ function connectLayoutAdapter(onRoot) {
1507
2095
  const conversationGlobalTop = sessionTop + sessionHeight + 8;
1508
2096
  const panelRows = [...anchors.panelList.querySelectorAll(DSH.panelButton)];
1509
2097
  const rowHeight = panelRows.reduce((height, row) => height + Math.ceil(row.getBoundingClientRect().height), 0);
1510
- const rowGap = Number.parseFloat(getComputedStyle(anchors.panelList).rowGap) || 4;
1511
- const panelContentHeight = Math.max(36, rowHeight + Math.max(0, panelRows.length - 1) * rowGap);
1512
- const conversationHeight = anchors.root.getBoundingClientRect().height;
1513
- const availablePanelHeight = Math.max(36, conversationHeight - conversationGlobalTop - (preferences?.toolsPosition === "conversation-bottom" ? toolsHeight + 8 : 8) - 16);
1514
- const panelHeight = Math.min(panelContentHeight, availablePanelHeight);
1515
- const conversationTop = conversationGlobalTop + (preferences?.globalNavigationPosition === "conversation-top" ? panelHeight + 8 : 0);
2098
+ const rowGap = Math.max(0, Number.parseFloat(getComputedStyle(anchors.panelList).rowGap) || 4);
1516
2099
  const bottomPad = preferences?.toolsPosition === "conversation-bottom" ? toolsHeight + 8 : 0;
2100
+ const panelContentHeight = panelRows.length > 0 ? rowHeight + (panelRows.length - 1) * rowGap : 0;
2101
+ const conversationNavigation = preferences?.globalNavigationPosition === "conversation-top";
2102
+ const conversationSizing = sizeConversationNavigation(
2103
+ anchors.root.getBoundingClientRect().height,
2104
+ conversationGlobalTop,
2105
+ bottomPad,
2106
+ panelContentHeight
2107
+ );
2108
+ const panelHeight = conversationNavigation ? conversationSizing.panelHeight : panelContentHeight;
2109
+ const conversationTop = conversationNavigation ? conversationSizing.conversationTop : conversationGlobalTop;
1517
2110
  const navBottomReserve = preferences?.toolsPosition === "rail-bottom" ? toolsHeight + 8 : 8;
1518
2111
  const toggleReserve = preferences?.expansion === "click" ? 44 : 0;
2112
+ const footerReserve = preferences?.toolsPosition === "rail-bottom" ? toolsHeight + 8 : 8;
2113
+ const widgetBoxes = [...compactWidgets.children].filter((child) => child instanceof HTMLElement);
2114
+ for (const box of widgetBoxes) resizeObserver?.observe(box);
2115
+ const widgetBoxHeights = compactWidgetsActive && compactWidgetCount > 0 ? widgetBoxes.map((box) => Math.ceil(box.getBoundingClientRect().height)) : [];
2116
+ const widgetAvailable = Math.max(0, anchors.root.getBoundingClientRect().height - railGlobalTop - footerReserve - toggleReserve - 16);
2117
+ const widgetFit = fitCompactWidgets(widgetBoxHeights, COMPACT_WIDGET_GAP, Math.min(COMPACT_WIDGETS_MAX_HEIGHT, widgetAvailable));
2118
+ widgetBoxes.forEach((box, index) => box.toggleAttribute(CLIPPED_MARK, index >= widgetFit.visible));
2119
+ const widgetHeight = widgetFit.height;
2120
+ const widgetBottom = footerReserve + toggleReserve + 8;
2121
+ const railBottomReserve = widgetHeight > 0 ? footerReserve + toggleReserve + widgetHeight + 16 : navBottomReserve + toggleReserve;
1519
2122
  anchors.root.style.setProperty("--dsh-better-left-sidebar-chrome", `${chrome}px`);
1520
2123
  anchors.root.style.setProperty("--dsh-better-left-sidebar-brand-height", `${brandHeight}px`);
1521
2124
  anchors.root.style.setProperty("--dsh-better-left-sidebar-session-top", `${sessionTop}px`);
@@ -1525,12 +2128,40 @@ function connectLayoutAdapter(onRoot) {
1525
2128
  anchors.root.style.setProperty("--dsh-better-left-sidebar-tools-height", `${toolsHeight}px`);
1526
2129
  anchors.root.style.setProperty("--dsh-better-left-sidebar-conversation-top", `${conversationTop}px`);
1527
2130
  anchors.root.style.setProperty("--dsh-better-left-sidebar-conversation-bottom", `${bottomPad}px`);
1528
- anchors.root.style.setProperty("--dsh-better-left-sidebar-nav-bottom-reserve", `${navBottomReserve + toggleReserve}px`);
2131
+ anchors.root.style.setProperty("--dsh-better-left-sidebar-nav-bottom-reserve", `${railBottomReserve}px`);
2132
+ anchors.root.style.setProperty("--dsh-better-left-sidebar-compact-widgets-height", `${widgetHeight}px`);
2133
+ anchors.root.style.setProperty("--dsh-better-left-sidebar-compact-widgets-bottom", `${widgetBottom}px`);
1529
2134
  };
1530
2135
  const scheduleMeasure = () => {
1531
2136
  if (measureFrame) window.cancelAnimationFrame(measureFrame);
1532
2137
  measureFrame = window.requestAnimationFrame(measure);
1533
2138
  };
2139
+ const observeAnchors = (anchors) => {
2140
+ resizeObserver?.disconnect();
2141
+ measurementObserver?.disconnect();
2142
+ resizeObserver = new ResizeObserver(scheduleMeasure);
2143
+ for (const element of [anchors.root, anchors.brand, anchors.newSession, anchors.panelList, anchors.tools, compactWidgets]) resizeObserver.observe(element);
2144
+ measurementObserver = new MutationObserver(scheduleMeasure);
2145
+ measurementObserver.observe(anchors.panelList, { childList: true, subtree: true });
2146
+ measurementObserver.observe(anchors.tools, { childList: true, subtree: true });
2147
+ measurementObserver.observe(compactWidgets, { childList: true });
2148
+ };
2149
+ const sameAnchors = (a, b) => a.root === b.root && a.brand === b.brand && a.newSession === b.newSession && a.panelList === b.panelList && a.conversation === b.conversation && a.tools === b.tools;
2150
+ const releaseStaleAnchors = (previous, next) => {
2151
+ const retained = /* @__PURE__ */ new Set([next.brand, next.newSession, next.panelList, next.conversation, next.tools]);
2152
+ for (const element of [previous.brand, previous.newSession, previous.panelList, previous.conversation, previous.tools]) {
2153
+ if (retained.has(element)) continue;
2154
+ element.removeAttribute(REGION_MARK);
2155
+ element.querySelector(DSH.brandButton)?.removeAttribute("inert");
2156
+ element.querySelectorAll(`[${ROW_MARK}], [${GLYPH_MARK}], [${TITLE_MARK}], [data-dsh-better-left-sidebar-brand-mark], [data-dsh-better-left-sidebar-brand-name], [data-dsh-better-left-sidebar-session-label], [data-dsh-better-left-sidebar-settings-trigger-row]`).forEach((marked) => {
2157
+ for (const attribute of [ROW_MARK, GLYPH_MARK, TITLE_MARK, "data-dsh-better-left-sidebar-brand-mark", "data-dsh-better-left-sidebar-brand-name", "data-dsh-better-left-sidebar-session-label", "data-dsh-better-left-sidebar-settings-trigger-row"]) marked.removeAttribute(attribute);
2158
+ });
2159
+ element.querySelectorAll("[data-dsh-better-left-sidebar-owned-title]").forEach((ownedTitle) => {
2160
+ ownedTitle.removeAttribute("title");
2161
+ ownedTitle.removeAttribute("data-dsh-better-left-sidebar-owned-title");
2162
+ });
2163
+ }
2164
+ };
1534
2165
  const apply2 = (anchors, next) => {
1535
2166
  const { root, brand, newSession, panelList, conversation, tools } = anchors;
1536
2167
  root.setAttribute(ROOT_MARK, "");
@@ -1560,12 +2191,10 @@ function connectLayoutAdapter(onRoot) {
1560
2191
  scheduleMeasure();
1561
2192
  };
1562
2193
  const detach = () => {
1563
- sidebarObserver?.disconnect();
1564
2194
  childrenObserver?.disconnect();
1565
2195
  measurementObserver?.disconnect();
1566
2196
  collapseObserver?.disconnect();
1567
2197
  resizeObserver?.disconnect();
1568
- sidebarObserver = void 0;
1569
2198
  childrenObserver = void 0;
1570
2199
  measurementObserver = void 0;
1571
2200
  collapseObserver = void 0;
@@ -1573,6 +2202,7 @@ function connectLayoutAdapter(onRoot) {
1573
2202
  if (measureFrame) window.cancelAnimationFrame(measureFrame);
1574
2203
  measureFrame = 0;
1575
2204
  if (current) {
2205
+ if (compactWidgets.parentElement === current.root) compactWidgets.remove();
1576
2206
  current.root.removeAttribute(ROOT_MARK);
1577
2207
  current.root.removeAttribute("data-dsh-better-left-sidebar-brand-position");
1578
2208
  current.root.removeAttribute("data-dsh-better-left-sidebar-global-position");
@@ -1592,7 +2222,9 @@ function connectLayoutAdapter(onRoot) {
1592
2222
  "--dsh-better-left-sidebar-tools-height",
1593
2223
  "--dsh-better-left-sidebar-conversation-top",
1594
2224
  "--dsh-better-left-sidebar-conversation-bottom",
1595
- "--dsh-better-left-sidebar-nav-bottom-reserve"
2225
+ "--dsh-better-left-sidebar-nav-bottom-reserve",
2226
+ "--dsh-better-left-sidebar-compact-widgets-height",
2227
+ "--dsh-better-left-sidebar-compact-widgets-bottom"
1596
2228
  ]) current.root.style.removeProperty(custom);
1597
2229
  current.brand.querySelector(DSH.brandButton)?.removeAttribute("inert");
1598
2230
  current.root.querySelectorAll(`[${ROW_MARK}], [${GLYPH_MARK}], [${TITLE_MARK}], [data-dsh-better-left-sidebar-brand-mark], [data-dsh-better-left-sidebar-brand-name], [data-dsh-better-left-sidebar-session-label], [data-dsh-better-left-sidebar-obsolete-collapse]`).forEach((element) => {
@@ -1606,15 +2238,32 @@ function connectLayoutAdapter(onRoot) {
1606
2238
  }
1607
2239
  current = void 0;
1608
2240
  };
1609
- const watchPending = () => {
1610
- if (pendingObserver) return;
1611
- pendingObserver = new MutationObserver(() => {
1612
- if (attach()) {
1613
- pendingObserver?.disconnect();
1614
- pendingObserver = void 0;
1615
- }
2241
+ const subtreeHas = (node, selector) => {
2242
+ if (!(node instanceof Element)) return false;
2243
+ return node.matches(selector) || Boolean(node.querySelector(selector));
2244
+ };
2245
+ const watchHost = () => {
2246
+ if (hostObserver) return;
2247
+ hostObserver = new MutationObserver((records) => {
2248
+ const targetRoot = current?.root ?? candidateRoot;
2249
+ const relevant = records.some((record) => {
2250
+ const target = record.target;
2251
+ if (target === observedSidebar) return true;
2252
+ if (target === targetRoot) return !current || record.type !== "childList";
2253
+ if (target instanceof Element && (target.matches(DSH.sidebarSlot) || target.matches(DSH.root))) return true;
2254
+ if (target instanceof Element && target.parentElement === targetRoot) return !current || record.type !== "childList";
2255
+ return [...record.addedNodes, ...record.removedNodes].some(
2256
+ (node) => node === targetRoot || subtreeHas(node, DSH.sidebarSlot) || subtreeHas(node, DSH.root)
2257
+ );
2258
+ });
2259
+ if (relevant) attach();
2260
+ });
2261
+ hostObserver.observe(document.documentElement, {
2262
+ childList: true,
2263
+ subtree: true,
2264
+ attributes: true,
2265
+ attributeFilter: ["class", "data-slot"]
1616
2266
  });
1617
- pendingObserver.observe(document.body, { childList: true, subtree: true });
1618
2267
  };
1619
2268
  const attach = () => {
1620
2269
  const sidebar = document.querySelector(DSH.sidebarSlot);
@@ -1624,60 +2273,73 @@ function connectLayoutAdapter(onRoot) {
1624
2273
  detach();
1625
2274
  onRoot(void 0);
1626
2275
  }
1627
- watchPending();
2276
+ observedSidebar = sidebar ?? void 0;
2277
+ candidateRoot = void 0;
1628
2278
  return false;
1629
2279
  }
1630
- if (current?.root === root) return true;
2280
+ observedSidebar = sidebar;
2281
+ candidateRoot = root;
2282
+ if (current?.root === root) {
2283
+ const refreshed = readAnchors(root);
2284
+ if (!refreshed) {
2285
+ detach();
2286
+ onRoot(void 0);
2287
+ return false;
2288
+ }
2289
+ if (sameAnchors(current, refreshed)) {
2290
+ if (compactWidgets.parentElement !== root) root.append(compactWidgets);
2291
+ if (preferences) apply2(refreshed, preferences);
2292
+ return true;
2293
+ }
2294
+ releaseStaleAnchors(current, refreshed);
2295
+ current = refreshed;
2296
+ root.append(compactWidgets);
2297
+ observeAnchors(refreshed);
2298
+ if (preferences) apply2(refreshed, preferences);
2299
+ onRoot(root, true);
2300
+ return true;
2301
+ }
1631
2302
  detach();
1632
2303
  const anchors = readAnchors(root);
1633
2304
  if (!anchors) {
1634
2305
  onRoot(void 0);
1635
- watchPending();
1636
2306
  return false;
1637
2307
  }
1638
2308
  current = anchors;
2309
+ root.append(compactWidgets);
1639
2310
  if (preferences) apply2(anchors, preferences);
1640
2311
  onRoot(root);
1641
2312
  if (root.classList.contains("hHd-Xa_collapsed")) root.querySelector(DSH.collapse)?.click();
1642
2313
  childrenObserver = new MutationObserver(() => {
1643
2314
  if (!current || current.root !== root) return;
1644
- const reread = readAnchors(root);
1645
- if (!reread) {
1646
- detach();
1647
- onRoot(void 0);
1648
- watchPending();
1649
- return;
1650
- }
1651
- current = reread;
1652
- if (preferences) apply2(reread, preferences);
2315
+ attach();
1653
2316
  });
1654
2317
  childrenObserver.observe(root, { childList: true, subtree: true });
1655
2318
  collapseObserver = new MutationObserver(() => {
1656
2319
  if (root.classList.contains("hHd-Xa_collapsed")) root.querySelector(DSH.collapse)?.click();
1657
2320
  });
1658
2321
  collapseObserver.observe(root, { attributes: true, attributeFilter: ["class"] });
1659
- resizeObserver = new ResizeObserver(scheduleMeasure);
1660
- for (const element of [anchors.root, anchors.brand, anchors.newSession, anchors.panelList, anchors.tools]) resizeObserver.observe(element);
1661
- measurementObserver = new MutationObserver(scheduleMeasure);
1662
- measurementObserver.observe(anchors.panelList, { childList: true, subtree: true });
1663
- measurementObserver.observe(anchors.tools, { childList: true, subtree: true });
1664
- sidebarObserver = new MutationObserver(() => attach());
1665
- sidebarObserver.observe(sidebar, { childList: true, subtree: true });
2322
+ observeAnchors(anchors);
1666
2323
  return true;
1667
2324
  };
1668
- const sync = () => {
1669
- attach();
1670
- };
1671
- sync();
2325
+ watchHost();
2326
+ attach();
1672
2327
  return {
1673
2328
  update(next) {
1674
2329
  preferences = next;
1675
2330
  if (current) apply2(current, next);
1676
2331
  },
2332
+ setCompactWidgets(count, active) {
2333
+ compactWidgetCount = Math.max(0, Math.floor(count));
2334
+ compactWidgetsActive = active;
2335
+ if (current && preferences) scheduleMeasure();
2336
+ },
1677
2337
  dispose() {
1678
- pendingObserver?.disconnect();
1679
- pendingObserver = void 0;
2338
+ hostObserver?.disconnect();
2339
+ hostObserver = void 0;
1680
2340
  detach();
2341
+ candidateRoot = void 0;
2342
+ observedSidebar = void 0;
1681
2343
  onRoot(void 0);
1682
2344
  }
1683
2345
  };
@@ -1732,7 +2394,109 @@ function connectNavigation(ctx) {
1732
2394
 
1733
2395
  // src/client/settings.ts
1734
2396
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
2397
+ var import_react3 = require("react");
2398
+
2399
+ // src/client/clock.tsx
1735
2400
  var import_react2 = require("react");
2401
+ function createClockWidget(locale, clock) {
2402
+ const subscribeLocale = (listener) => locale.subscribe(listener);
2403
+ const activeLocale = () => locale.getSnapshot().active;
2404
+ return function ClockWidget({ active }) {
2405
+ const now = useMinuteClock(active);
2406
+ const localeId = (0, import_react2.useSyncExternalStore)(subscribeLocale, activeLocale, activeLocale);
2407
+ const display = (0, import_react2.useSyncExternalStore)(clock.subscribe, clock.getSnapshot, clock.getSnapshot);
2408
+ return (0, import_react2.createElement)(ClockFace, { now, lines: clockLines(now, localeId, display), color: display.color });
2409
+ };
2410
+ }
2411
+ function useMinuteClock(active) {
2412
+ const [now, setNow] = (0, import_react2.useState)(() => /* @__PURE__ */ new Date());
2413
+ (0, import_react2.useEffect)(() => {
2414
+ if (!active) return;
2415
+ let timer = 0;
2416
+ const clearTimer = () => {
2417
+ if (timer) window.clearTimeout(timer);
2418
+ timer = 0;
2419
+ };
2420
+ const refresh = () => {
2421
+ clearTimer();
2422
+ const current = /* @__PURE__ */ new Date();
2423
+ setNow(current);
2424
+ if (!document.hidden) timer = window.setTimeout(refresh, 6e4 - current.getTime() % 6e4);
2425
+ };
2426
+ const onVisibilityChange = () => {
2427
+ if (document.hidden) clearTimer();
2428
+ else refresh();
2429
+ };
2430
+ document.addEventListener("visibilitychange", onVisibilityChange);
2431
+ if (!document.hidden) refresh();
2432
+ return () => {
2433
+ clearTimer();
2434
+ document.removeEventListener("visibilitychange", onVisibilityChange);
2435
+ };
2436
+ }, [active]);
2437
+ return now;
2438
+ }
2439
+ function ClockFace(props) {
2440
+ const { now, lines, color } = props;
2441
+ return (0, import_react2.createElement)(
2442
+ "div",
2443
+ { className: "dsh-better-left-sidebar-clock" },
2444
+ (0, import_react2.createElement)(
2445
+ "time",
2446
+ {
2447
+ className: "dsh-better-left-sidebar-clock-time",
2448
+ dateTime: now.toISOString(),
2449
+ "aria-hidden": "true",
2450
+ style: color ? { color } : void 0
2451
+ },
2452
+ lines.time,
2453
+ lines.period ? (0, import_react2.createElement)("span", { className: "dsh-better-left-sidebar-clock-period" }, lines.period) : null
2454
+ ),
2455
+ lines.monthDay ? (0, import_react2.createElement)("span", { className: "dsh-better-left-sidebar-clock-date", "aria-hidden": "true" }, lines.monthDay) : null,
2456
+ lines.weekday ? (0, import_react2.createElement)("span", { className: "dsh-better-left-sidebar-clock-date", "aria-hidden": "true" }, lines.weekday) : null,
2457
+ (0, import_react2.createElement)("span", { className: "dsh-better-left-sidebar-visually-hidden" }, lines.accessible)
2458
+ );
2459
+ }
2460
+
2461
+ // src/client/compact-widget-preferences.ts
2462
+ function validateCompactWidgetPreferences(value) {
2463
+ if (!Array.isArray(value)) return [];
2464
+ const result = [];
2465
+ for (const item of value) {
2466
+ if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2467
+ const record = item;
2468
+ if (typeof record.id !== "string") continue;
2469
+ result.push({
2470
+ id: record.id,
2471
+ enabled: typeof record.enabled === "boolean" ? record.enabled : true
2472
+ });
2473
+ }
2474
+ return result;
2475
+ }
2476
+ function orderCompactWidgetEntries(entries, preferences) {
2477
+ const savedById = /* @__PURE__ */ new Map();
2478
+ preferences.forEach((preference, index) => {
2479
+ if (!savedById.has(preference.id)) savedById.set(preference.id, { index, enabled: preference.enabled });
2480
+ });
2481
+ return entries.map((entry, registrationIndex) => ({
2482
+ entry,
2483
+ registrationIndex,
2484
+ saved: savedById.get(entry.id)
2485
+ })).sort((a, b) => {
2486
+ if (a.saved && b.saved) return a.saved.index - b.saved.index || a.registrationIndex - b.registrationIndex;
2487
+ if (a.saved) return -1;
2488
+ if (b.saved) return 1;
2489
+ const orderA = typeof a.entry.order === "number" && Number.isFinite(a.entry.order) ? a.entry.order : 0;
2490
+ const orderB = typeof b.entry.order === "number" && Number.isFinite(b.entry.order) ? b.entry.order : 0;
2491
+ return orderA - orderB || a.registrationIndex - b.registrationIndex;
2492
+ }).map(({ entry, saved }) => ({
2493
+ entry,
2494
+ enabled: saved?.enabled ?? true,
2495
+ saved: saved !== void 0
2496
+ }));
2497
+ }
2498
+
2499
+ // src/client/settings.ts
1736
2500
  var PACKAGE = "dsh-better-left-sidebar";
1737
2501
  var animationValues = ["standard", "fast", "off"];
1738
2502
  var expansionValues = ["hover", "click"];
@@ -1745,12 +2509,47 @@ var fields = [
1745
2509
  choiceField("brandPosition", brandValues),
1746
2510
  choiceField("globalNavigationPosition", globalValues),
1747
2511
  choiceField("toolsPosition", toolsValues),
2512
+ compactWidgetField(),
2513
+ clockField(),
1748
2514
  {
1749
2515
  field: "layoutVersion",
1750
2516
  format: (value) => value === 1 ? "1" : "",
1751
2517
  parse: (text) => text === "1" ? { kind: "set", value: 1 } : text === "" ? { kind: "clear" } : void 0
1752
2518
  }
1753
2519
  ];
2520
+ function compactWidgetField() {
2521
+ return {
2522
+ field: "compactWidgets",
2523
+ format: (value) => JSON.stringify(validateCompactWidgetPreferences(value), null, 2),
2524
+ parse: (text) => {
2525
+ let value;
2526
+ try {
2527
+ value = JSON.parse(text);
2528
+ } catch {
2529
+ return void 0;
2530
+ }
2531
+ if (!Array.isArray(value) || value.some((item) => {
2532
+ if (!item || typeof item !== "object" || Array.isArray(item)) return true;
2533
+ const record = item;
2534
+ return typeof record.id !== "string" || typeof record.enabled !== "boolean";
2535
+ })) return void 0;
2536
+ return { kind: "set", value: validateCompactWidgetPreferences(value) };
2537
+ }
2538
+ };
2539
+ }
2540
+ function clockField() {
2541
+ return {
2542
+ field: "clock",
2543
+ format: (value) => JSON.stringify(toClockConfig(value)),
2544
+ parse: (text) => {
2545
+ try {
2546
+ return { kind: "set", value: toClockConfig(JSON.parse(text)) };
2547
+ } catch {
2548
+ return void 0;
2549
+ }
2550
+ }
2551
+ };
2552
+ }
1754
2553
  function choiceField(field, values) {
1755
2554
  return {
1756
2555
  field,
@@ -1758,7 +2557,7 @@ function choiceField(field, values) {
1758
2557
  parse: (text) => values.includes(text) ? { kind: "set", value: text } : void 0
1759
2558
  };
1760
2559
  }
1761
- function connectSettings(ctx, onPreferences, onTranslate) {
2560
+ function connectSettings(ctx, compactWidgets, onPreferences, onTranslate) {
1762
2561
  ctx.inject(["locale", "configForms"], (settingsCtx) => {
1763
2562
  const form = settingsCtx.configForms.get(PACKAGE);
1764
2563
  settingsCtx.effect(() => {
@@ -1778,7 +2577,7 @@ function connectSettings(ctx, onPreferences, onTranslate) {
1778
2577
  settingsCtx.effect(
1779
2578
  () => settingsCtx.slots.inject("plugins.bundle.config", () => settingsCtx.slots.register(
1780
2579
  { name: "plugins.bundle.config", key: PACKAGE, locale: NAMESPACE },
1781
- (props) => (0, import_react2.createElement)(SettingsPage, { ...props, ctx: settingsCtx })
2580
+ (props) => (0, import_react3.createElement)(SettingsPage, { ...props, ctx: settingsCtx, compactWidgets, locale: settingsCtx.locale })
1782
2581
  )),
1783
2582
  "better-left-sidebar: settings page"
1784
2583
  );
@@ -1786,23 +2585,23 @@ function connectSettings(ctx, onPreferences, onTranslate) {
1786
2585
  }
1787
2586
  function SettingsPage(props) {
1788
2587
  const { ctx, t } = props;
1789
- const scope = (0, import_react2.useMemo)(
2588
+ const scope = (0, import_react3.useMemo)(
1790
2589
  () => ctx.configForms.get(PACKAGE),
1791
2590
  [ctx]
1792
2591
  );
1793
- const model = (0, import_react2.useMemo)(() => new import_dsh_client_ui_primitives.SettingsFormModel(scope, fields), [scope]);
1794
- (0, import_react2.useEffect)(() => () => model.dispose(), [model]);
1795
- const store = (0, import_react2.useMemo)(() => model.bind(() => ({
2592
+ const model = (0, import_react3.useMemo)(() => new import_dsh_client_ui_primitives.SettingsFormModel(scope, fields), [scope]);
2593
+ (0, import_react3.useEffect)(() => () => model.dispose(), [model]);
2594
+ const store = (0, import_react3.useMemo)(() => model.bind(() => ({
1796
2595
  shell: model.shell(),
1797
2596
  fields: Object.fromEntries(fields.map((spec) => [spec.field, model.field(spec.field)]))
1798
2597
  })), [model]);
1799
- const subscribe = (0, import_react2.useCallback)((listener) => store.subscribe(listener), [store]);
1800
- const getSnapshot = (0, import_react2.useCallback)(() => store.getSnapshot(), [store]);
1801
- const view = (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
1802
- const actions = (0, import_react2.useMemo)(() => model.actions(), [model]);
2598
+ const subscribe = (0, import_react3.useCallback)((listener) => store.subscribe(listener), [store]);
2599
+ const getSnapshot = (0, import_react3.useCallback)(() => store.getSnapshot(), [store]);
2600
+ const view = (0, import_react3.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
2601
+ (0, import_react3.useSyncExternalStore)(props.compactWidgets.subscribeCatalog, props.compactWidgets.getCatalogVersion, props.compactWidgets.getCatalogVersion);
2602
+ const actions = (0, import_react3.useMemo)(() => model.actions(), [model]);
1803
2603
  const disabled = !view.shell.writable || view.shell.saving;
1804
2604
  const savedSnapshot = scope.getSnapshot();
1805
- const savedPreferences = normalizePreferences(savedSnapshot.value);
1806
2605
  const draft = normalizePreferences({
1807
2606
  ...savedSnapshot.value,
1808
2607
  animation: view.fields.animation?.text,
@@ -1818,8 +2617,102 @@ function SettingsPage(props) {
1818
2617
  actions.edit(field, value);
1819
2618
  actions.edit("layoutVersion", "1");
1820
2619
  };
1821
- if (props.view === "summary") return (0, import_react2.createElement)("p", null, t("summary"));
1822
- return (0, import_react2.createElement)(import_dsh_client_ui_primitives.SettingsForm, {
2620
+ const savedWidgets = readWidgetDraft(view.fields.compactWidgets?.text);
2621
+ const widgetRows = buildWidgetRows(savedWidgets, props.compactWidgets.getCatalog(), validateCompactWidgetPreferences(savedSnapshot.value?.compactWidgets));
2622
+ const writeWidgetRows = (rows) => {
2623
+ const next = rows.map(({ id, enabled }) => ({ id, enabled }));
2624
+ actions.edit("compactWidgets", JSON.stringify(next, null, 2));
2625
+ };
2626
+ const clockDraft = readClockDraft(view.fields.clock?.text);
2627
+ const writeClock = (next) => {
2628
+ actions.edit("clock", JSON.stringify(toClockConfig(next)));
2629
+ };
2630
+ if (props.view === "summary") return (0, import_react3.createElement)("p", null, t("summary"));
2631
+ const widgetSection = (0, import_react3.createElement)(
2632
+ "section",
2633
+ { className: "dsh-better-left-sidebar-settings-group dsh-better-left-sidebar-compact-widgets-settings" },
2634
+ (0, import_react3.createElement)(
2635
+ "div",
2636
+ { className: "dsh-better-left-sidebar-compact-widgets-intro" },
2637
+ (0, import_react3.createElement)("h3", null, t("compactWidgets")),
2638
+ (0, import_react3.createElement)("p", { className: "dsh-better-left-sidebar-compact-widgets-hint" }, t("compactWidgetsHint"))
2639
+ ),
2640
+ widgetRows.length === 0 ? (0, import_react3.createElement)("p", { className: "dsh-better-left-sidebar-compact-widgets-empty" }, t("compactWidgetsEmpty")) : (0, import_react3.createElement)(
2641
+ "div",
2642
+ { className: "dsh-better-left-sidebar-compact-widgets-list" },
2643
+ ...widgetRows.map((row, index) => (0, import_react3.createElement)(
2644
+ "div",
2645
+ {
2646
+ key: row.id,
2647
+ className: "dsh-better-left-sidebar-compact-widget-setting",
2648
+ "data-dsh-better-left-sidebar-widget-setting": row.id
2649
+ },
2650
+ (0, import_react3.createElement)(
2651
+ "div",
2652
+ { className: "dsh-better-left-sidebar-compact-widget-info" },
2653
+ (0, import_react3.createElement)("div", { className: "dsh-better-left-sidebar-compact-widget-name" }, row.label),
2654
+ (0, import_react3.createElement)("div", {
2655
+ className: `dsh-better-left-sidebar-compact-widget-status is-${row.status}`,
2656
+ "aria-live": "polite"
2657
+ }, row.status === "failed" ? `${t("widgetFailed")}: ${row.error}` : t(widgetStatusKey(row.status)))
2658
+ ),
2659
+ (0, import_react3.createElement)(
2660
+ "div",
2661
+ { className: "dsh-better-left-sidebar-compact-widget-controls" },
2662
+ (0, import_react3.createElement)(import_dsh_client_ui_primitives.Switch, {
2663
+ checked: row.enabled,
2664
+ disabled,
2665
+ label: `${t("widgetEnable")} ${row.label}`,
2666
+ onChange: (checked) => writeWidgetRows(widgetRows.map((item) => item.id === row.id ? { ...item, enabled: checked } : item))
2667
+ }),
2668
+ widgetRows.length > 1 ? (0, import_react3.createElement)(
2669
+ "div",
2670
+ { className: "dsh-better-left-sidebar-widget-order" },
2671
+ (0, import_react3.createElement)(import_dsh_client_ui_primitives.Button, {
2672
+ type: "button",
2673
+ variant: "outline",
2674
+ size: "sm",
2675
+ disabled: disabled || index === 0,
2676
+ "aria-label": `${t("moveUp")} ${row.label}`,
2677
+ onClick: () => writeWidgetRows(moveWidgetRow(widgetRows, index, -1))
2678
+ }, "\u2191"),
2679
+ (0, import_react3.createElement)(import_dsh_client_ui_primitives.Button, {
2680
+ type: "button",
2681
+ variant: "outline",
2682
+ size: "sm",
2683
+ disabled: disabled || index === widgetRows.length - 1,
2684
+ "aria-label": `${t("moveDown")} ${row.label}`,
2685
+ onClick: () => writeWidgetRows(moveWidgetRow(widgetRows, index, 1))
2686
+ }, "\u2193")
2687
+ ) : null
2688
+ ),
2689
+ row.id === CLOCK_WIDGET_ID ? (0, import_react3.createElement)(
2690
+ "details",
2691
+ { className: "dsh-better-left-sidebar-widget-options", open: true },
2692
+ (0, import_react3.createElement)("summary", null, t("widgetSettings")),
2693
+ (0, import_react3.createElement)(ClockOptions, {
2694
+ t,
2695
+ disabled,
2696
+ display: clockDraft,
2697
+ caption: t("effectPreview"),
2698
+ locale: props.locale,
2699
+ onChange: writeClock
2700
+ })
2701
+ ) : null
2702
+ ))
2703
+ ),
2704
+ (0, import_react3.createElement)(
2705
+ "details",
2706
+ { className: "dsh-better-left-sidebar-widget-reference" },
2707
+ (0, import_react3.createElement)("summary", null, t("widgetInformation")),
2708
+ (0, import_react3.createElement)("dl", null, ...widgetRows.flatMap((row) => [
2709
+ (0, import_react3.createElement)("dt", { key: `${row.id}-label` }, row.label),
2710
+ (0, import_react3.createElement)("dd", { key: row.id }, (0, import_react3.createElement)("code", null, row.id))
2711
+ ])),
2712
+ (0, import_react3.createElement)("a", { href: t("compactWidgetsDocsUrl"), target: "_blank", rel: "noopener noreferrer" }, t("compactWidgetsDocs"))
2713
+ )
2714
+ );
2715
+ return (0, import_react3.createElement)(import_dsh_client_ui_primitives.SettingsForm, {
1823
2716
  labels: {
1824
2717
  unavailable: t("unavailable"),
1825
2718
  readOnly: t("readOnly"),
@@ -1830,128 +2723,305 @@ function SettingsPage(props) {
1830
2723
  state: view.shell,
1831
2724
  onSave: actions.save,
1832
2725
  onDiscard: actions.discard,
1833
- children: (0, import_react2.createElement)(
1834
- "div",
1835
- { className: "dsh-better-left-sidebar-preferences" },
1836
- (0, import_react2.createElement)(
1837
- "section",
1838
- { className: "dsh-better-left-sidebar-settings-group" },
1839
- (0, import_react2.createElement)("h3", null, t("navigationBehavior")),
1840
- (0, import_react2.createElement)(ChoiceRow, {
1841
- name: "dsh-better-left-sidebar-expansion",
1842
- label: t("expansion"),
1843
- value: view.fields.expansion.text,
1844
- disabled,
1845
- options: expansionValues.map((value) => ({ value, label: t(value) })),
1846
- onChange: (value) => actions.edit("expansion", value)
1847
- }),
1848
- (0, import_react2.createElement)(ChoiceRow, {
1849
- name: "dsh-better-left-sidebar-animation",
1850
- label: t("animation"),
1851
- value: view.fields.animation.text,
1852
- disabled,
1853
- options: animationValues.map((value) => ({ value, label: t(value) })),
1854
- onChange: (value) => actions.edit("animation", value)
1855
- })
1856
- ),
1857
- (0, import_react2.createElement)(
1858
- "section",
1859
- { className: "dsh-better-left-sidebar-settings-group" },
1860
- (0, import_react2.createElement)(
1861
- "div",
1862
- { className: "dsh-better-left-sidebar-settings-heading" },
1863
- (0, import_react2.createElement)("h3", null, t("contentLayout")),
1864
- (0, import_react2.createElement)("button", {
1865
- type: "button",
1866
- className: "dsh-better-left-sidebar-layout-reset",
1867
- disabled: disabled || !canRestore,
1868
- onClick: () => {
1869
- actions.edit("brandPosition", DEFAULT_PREFERENCES.brandPosition);
1870
- actions.edit("globalNavigationPosition", DEFAULT_PREFERENCES.globalNavigationPosition);
1871
- actions.edit("toolsPosition", DEFAULT_PREFERENCES.toolsPosition);
1872
- actions.edit("layoutVersion", "1");
1873
- }
1874
- }, t("resetLayout"))
2726
+ children: [
2727
+ (0, import_react3.createElement)(
2728
+ "div",
2729
+ { className: "dsh-better-left-sidebar-preferences" },
2730
+ (0, import_react3.createElement)(
2731
+ "section",
2732
+ { className: "dsh-better-left-sidebar-settings-group dsh-better-left-sidebar-layout-settings" },
2733
+ (0, import_react3.createElement)(
2734
+ "div",
2735
+ { className: "dsh-better-left-sidebar-settings-heading" },
2736
+ (0, import_react3.createElement)("h3", null, t("contentLayout")),
2737
+ (0, import_react3.createElement)("button", {
2738
+ type: "button",
2739
+ className: "dsh-better-left-sidebar-layout-reset",
2740
+ disabled: disabled || !canRestore,
2741
+ onClick: () => {
2742
+ actions.edit("brandPosition", DEFAULT_PREFERENCES.brandPosition);
2743
+ actions.edit("globalNavigationPosition", DEFAULT_PREFERENCES.globalNavigationPosition);
2744
+ actions.edit("toolsPosition", DEFAULT_PREFERENCES.toolsPosition);
2745
+ actions.edit("layoutVersion", "1");
2746
+ }
2747
+ }, t("resetLayout"))
2748
+ ),
2749
+ (0, import_react3.createElement)(
2750
+ "div",
2751
+ { className: "dsh-better-left-sidebar-position-controls" },
2752
+ (0, import_react3.createElement)(PositionSelect, {
2753
+ field: "brandPosition",
2754
+ label: t("brand"),
2755
+ value: view.fields.brandPosition.text,
2756
+ disabled,
2757
+ options: brandValues.map((value) => ({ value, label: brandLabel(t, value) })),
2758
+ onChange: (value) => setChoice("brandPosition", value)
2759
+ }),
2760
+ (0, import_react3.createElement)(PositionSelect, {
2761
+ field: "globalNavigationPosition",
2762
+ label: t("globalNavigation"),
2763
+ value: view.fields.globalNavigationPosition.text,
2764
+ disabled,
2765
+ options: globalValues.map((value) => ({ value, label: globalLabel(t, value) })),
2766
+ onChange: (value) => setChoice("globalNavigationPosition", value)
2767
+ }),
2768
+ (0, import_react3.createElement)(PositionSelect, {
2769
+ field: "toolsPosition",
2770
+ label: t("tools"),
2771
+ value: view.fields.toolsPosition.text,
2772
+ disabled,
2773
+ options: toolsValues.map((value) => ({ value, label: toolsLabel(t, value) })),
2774
+ onChange: (value) => setChoice("toolsPosition", value)
2775
+ })
2776
+ ),
2777
+ (0, import_react3.createElement)(LayoutPreview, { title: t("effectPreview"), preferences: draft, t })
1875
2778
  ),
1876
- (0, import_react2.createElement)(
1877
- "div",
1878
- { className: "dsh-better-left-sidebar-position-controls" },
1879
- (0, import_react2.createElement)(PositionSelect, {
1880
- field: "brandPosition",
1881
- label: t("brand"),
1882
- value: view.fields.brandPosition.text,
2779
+ (0, import_react3.createElement)(
2780
+ "section",
2781
+ { className: "dsh-better-left-sidebar-settings-group" },
2782
+ (0, import_react3.createElement)("h3", null, t("navigationBehavior")),
2783
+ (0, import_react3.createElement)(ChoiceRow, {
2784
+ name: "dsh-better-left-sidebar-expansion",
2785
+ label: t("expansion"),
2786
+ value: view.fields.expansion.text,
1883
2787
  disabled,
1884
- options: brandValues.map((value) => ({ value, label: brandLabel(t, value) })),
1885
- onChange: (value) => setChoice("brandPosition", value)
2788
+ options: expansionValues.map((value) => ({ value, label: t(value) })),
2789
+ onChange: (value) => actions.edit("expansion", value)
1886
2790
  }),
1887
- (0, import_react2.createElement)(PositionSelect, {
1888
- field: "globalNavigationPosition",
1889
- label: t("globalNavigation"),
1890
- value: view.fields.globalNavigationPosition.text,
2791
+ (0, import_react3.createElement)(ChoiceRow, {
2792
+ name: "dsh-better-left-sidebar-animation",
2793
+ label: t("animation"),
2794
+ value: view.fields.animation.text,
1891
2795
  disabled,
1892
- options: globalValues.map((value) => ({ value, label: globalLabel(t, value) })),
1893
- onChange: (value) => setChoice("globalNavigationPosition", value)
2796
+ options: animationValues.map((value) => ({ value, label: t(value) })),
2797
+ onChange: (value) => actions.edit("animation", value)
2798
+ })
2799
+ ),
2800
+ widgetSection
2801
+ ),
2802
+ view.shell.dirty ? (0, import_react3.createElement)("button", {
2803
+ type: "button",
2804
+ className: "dsh-better-left-sidebar-layout-cancel",
2805
+ disabled: view.shell.saving,
2806
+ onClick: actions.discard
2807
+ }, t("cancel")) : null
2808
+ ]
2809
+ });
2810
+ }
2811
+ function sameLayout(a, b) {
2812
+ return a.brandPosition === b.brandPosition && a.globalNavigationPosition === b.globalNavigationPosition && a.toolsPosition === b.toolsPosition;
2813
+ }
2814
+ function readWidgetDraft(text) {
2815
+ if (text === void 0) return [];
2816
+ try {
2817
+ return validateCompactWidgetPreferences(JSON.parse(text));
2818
+ } catch {
2819
+ return [];
2820
+ }
2821
+ }
2822
+ function buildWidgetRows(preferences, catalog, applied) {
2823
+ const preferred = /* @__PURE__ */ new Map();
2824
+ for (const preference of preferences) if (!preferred.has(preference.id)) preferred.set(preference.id, preference);
2825
+ const byId = new Map(catalog.map((entry) => [entry.id, entry]));
2826
+ const ordered = orderCompactWidgetEntries(catalog, preferences);
2827
+ const ids = [];
2828
+ const seen = /* @__PURE__ */ new Set();
2829
+ for (const preference of preferences) {
2830
+ if (seen.has(preference.id)) continue;
2831
+ seen.add(preference.id);
2832
+ ids.push(preference.id);
2833
+ }
2834
+ for (const item of ordered) {
2835
+ if (seen.has(item.entry.id)) continue;
2836
+ seen.add(item.entry.id);
2837
+ ids.push(item.entry.id);
2838
+ }
2839
+ return ids.map((id) => {
2840
+ const registration = byId.get(id);
2841
+ const enabled = preferred.get(id)?.enabled ?? true;
2842
+ const status = registration?.error !== void 0 ? "failed" : !registration ? "notLoaded" : enabled !== (applied.find((item) => item.id === id)?.enabled ?? true) ? enabled ? "pendingEnable" : "pendingDisable" : registration.presentation;
2843
+ const row = {
2844
+ id,
2845
+ label: registration?.label ?? id,
2846
+ enabled,
2847
+ status
2848
+ };
2849
+ if (registration?.error !== void 0) row.error = registration.error;
2850
+ return row;
2851
+ });
2852
+ }
2853
+ function widgetStatusKey(status) {
2854
+ switch (status) {
2855
+ case "shown":
2856
+ return "widgetShown";
2857
+ case "enabled":
2858
+ return "widgetEnabled";
2859
+ case "clipped":
2860
+ return "widgetClipped";
2861
+ case "inactive":
2862
+ return "widgetInactive";
2863
+ case "empty":
2864
+ return "widgetEmpty";
2865
+ case "pendingEnable":
2866
+ return "widgetPendingEnable";
2867
+ case "pendingDisable":
2868
+ return "widgetPendingDisable";
2869
+ case "disabled":
2870
+ return "widgetDisabled";
2871
+ case "notLoaded":
2872
+ return "widgetNotLoaded";
2873
+ case "failed":
2874
+ return "widgetFailed";
2875
+ }
2876
+ }
2877
+ function moveWidgetRow(rows, index, delta) {
2878
+ const next = [...rows];
2879
+ const target = index + delta;
2880
+ if (target < 0 || target >= next.length) return next;
2881
+ [next[index], next[target]] = [next[target], next[index]];
2882
+ return next;
2883
+ }
2884
+ var clockHourCycles = ["h23", "h12"];
2885
+ function ClockOptions(props) {
2886
+ const { t, disabled, display, onChange } = props;
2887
+ return (0, import_react3.createElement)(
2888
+ "div",
2889
+ { className: "dsh-better-left-sidebar-clock-options" },
2890
+ (0, import_react3.createElement)(
2891
+ "div",
2892
+ { className: "dsh-better-left-sidebar-clock-option-fields" },
2893
+ (0, import_react3.createElement)(ChoiceRow, {
2894
+ name: "dsh-better-left-sidebar-clock-hour",
2895
+ label: t("clockHourCycle"),
2896
+ value: display.hourCycle,
2897
+ disabled,
2898
+ options: clockHourCycles.map((value) => ({ value, label: t(value === "h23" ? "clock24" : "clock12") })),
2899
+ onChange: (value) => onChange({ ...display, hourCycle: value === "h12" ? "h12" : "h23" })
2900
+ }),
2901
+ (0, import_react3.createElement)(
2902
+ SettingsRow,
2903
+ { label: t("clockContents"), disabled },
2904
+ (0, import_react3.createElement)(
2905
+ "div",
2906
+ { className: "dsh-better-left-sidebar-clock-modules" },
2907
+ (0, import_react3.createElement)(import_dsh_client_ui_primitives.Checkbox, {
2908
+ checked: display.monthDay,
2909
+ disabled,
2910
+ label: t("clockMonthDay"),
2911
+ onChange: (checked) => onChange({ ...display, monthDay: checked })
1894
2912
  }),
1895
- (0, import_react2.createElement)(PositionSelect, {
1896
- field: "toolsPosition",
1897
- label: t("tools"),
1898
- value: view.fields.toolsPosition.text,
2913
+ (0, import_react3.createElement)(import_dsh_client_ui_primitives.Checkbox, {
2914
+ checked: display.weekday,
1899
2915
  disabled,
1900
- options: toolsValues.map((value) => ({ value, label: toolsLabel(t, value) })),
1901
- onChange: (value) => setChoice("toolsPosition", value)
2916
+ label: t("clockWeekday"),
2917
+ onChange: (checked) => onChange({ ...display, weekday: checked })
1902
2918
  })
1903
- ),
1904
- (0, import_react2.createElement)(
2919
+ )
2920
+ ),
2921
+ (0, import_react3.createElement)(ChoiceRow, {
2922
+ name: "dsh-better-left-sidebar-clock-color-mode",
2923
+ label: t("clockColor"),
2924
+ value: display.color ? "custom" : "theme",
2925
+ disabled,
2926
+ options: [{ value: "theme", label: t("clockColorDefault") }, { value: "custom", label: t("clockColorCustom") }],
2927
+ onChange: (value) => onChange({ ...display, color: value === "theme" ? null : "#767676" })
2928
+ }),
2929
+ display.color ? (0, import_react3.createElement)(
2930
+ SettingsRow,
2931
+ { label: t("clockColorCustom"), disabled },
2932
+ (0, import_react3.createElement)(
1905
2933
  "div",
1906
- { className: "dsh-better-left-sidebar-layout-previews", "aria-live": "polite" },
1907
- view.shell.dirty ? (0, import_react2.createElement)(LayoutPreview, { title: t("applied"), preferences: savedPreferences, t }) : null,
1908
- (0, import_react2.createElement)(LayoutPreview, { title: view.shell.dirty ? t("preview") : t("applied"), preferences: draft, t })
2934
+ { className: "dsh-better-left-sidebar-clock-color-controls" },
2935
+ (0, import_react3.createElement)(
2936
+ "label",
2937
+ { className: "dsh-better-left-sidebar-clock-color-picker" },
2938
+ (0, import_react3.createElement)("span", {
2939
+ className: "dsh-better-left-sidebar-clock-color-swatch",
2940
+ style: { backgroundColor: display.color },
2941
+ "aria-hidden": "true"
2942
+ }),
2943
+ (0, import_react3.createElement)("input", {
2944
+ type: "color",
2945
+ className: "dsh-better-left-sidebar-clock-color-input",
2946
+ value: display.color,
2947
+ disabled,
2948
+ "aria-label": t("clockColorCustom"),
2949
+ onChange: (event) => onChange({ ...display, color: event.currentTarget.value })
2950
+ })
2951
+ ),
2952
+ (0, import_react3.createElement)("code", null, display.color)
1909
2953
  ),
1910
- view.shell.dirty ? (0, import_react2.createElement)("button", {
1911
- type: "button",
1912
- className: "dsh-better-left-sidebar-layout-cancel",
1913
- disabled: view.shell.saving,
1914
- onClick: actions.discard
1915
- }, t("cancel")) : null
1916
- )
2954
+ (0, import_react3.createElement)("p", { className: "dsh-better-left-sidebar-clock-color-hint" }, t("clockColorHint"))
2955
+ ) : null
2956
+ ),
2957
+ (0, import_react3.createElement)(ClockPreview, { display, caption: props.caption, locale: props.locale })
2958
+ );
2959
+ }
2960
+ function ClockPreview(props) {
2961
+ const now = useMinuteClock(true);
2962
+ const localeId = (0, import_react3.useSyncExternalStore)(
2963
+ (listener) => props.locale.subscribe(listener),
2964
+ () => props.locale.getSnapshot().active,
2965
+ () => props.locale.getSnapshot().active
2966
+ );
2967
+ return (0, import_react3.createElement)(
2968
+ "figure",
2969
+ { className: "dsh-better-left-sidebar-clock-preview" },
2970
+ (0, import_react3.createElement)("figcaption", null, props.caption),
2971
+ (0, import_react3.createElement)(
2972
+ "div",
2973
+ { className: "dsh-better-left-sidebar-clock-preview-rail" },
2974
+ (0, import_react3.createElement)(ClockFace, { now, lines: clockLines(now, localeId, props.display), color: props.display.color })
1917
2975
  )
1918
- });
2976
+ );
1919
2977
  }
1920
- function sameLayout(a, b) {
1921
- return a.brandPosition === b.brandPosition && a.globalNavigationPosition === b.globalNavigationPosition && a.toolsPosition === b.toolsPosition;
2978
+ function readClockDraft(text) {
2979
+ if (!text) return normalizeClockDisplay(void 0);
2980
+ try {
2981
+ return normalizeClockDisplay(JSON.parse(text));
2982
+ } catch {
2983
+ return normalizeClockDisplay(void 0);
2984
+ }
1922
2985
  }
1923
- function ChoiceRow(props) {
1924
- return (0, import_react2.createElement)(
2986
+ function SettingsRow(props) {
2987
+ return (0, import_react3.createElement)(
1925
2988
  "fieldset",
1926
- { className: "dsh-better-left-sidebar-choice-row", disabled: props.disabled },
1927
- (0, import_react2.createElement)("legend", null, props.label),
1928
- (0, import_react2.createElement)(
2989
+ { className: "dsh-better-left-sidebar-settings-row", disabled: props.disabled },
2990
+ (0, import_react3.createElement)("legend", null, props.label),
2991
+ (0, import_react3.createElement)("div", { className: "dsh-better-left-sidebar-setting-control" }, props.children)
2992
+ );
2993
+ }
2994
+ function ChoiceRow(props) {
2995
+ return (0, import_react3.createElement)(
2996
+ SettingsRow,
2997
+ { label: props.label, disabled: props.disabled },
2998
+ (0, import_react3.createElement)(
1929
2999
  "div",
1930
3000
  { className: "dsh-better-left-sidebar-choice-options" },
1931
- ...props.options.map((option) => (0, import_react2.createElement)(
3001
+ ...props.options.map((option) => (0, import_react3.createElement)(
1932
3002
  "label",
1933
3003
  {
1934
3004
  key: option.value,
1935
3005
  className: "dsh-better-left-sidebar-choice-option"
1936
3006
  },
1937
- (0, import_react2.createElement)("input", {
3007
+ (0, import_react3.createElement)("input", {
1938
3008
  type: "radio",
1939
3009
  name: props.name,
1940
3010
  value: option.value,
1941
3011
  checked: props.value === option.value,
1942
3012
  onChange: () => props.onChange(option.value)
1943
3013
  }),
1944
- (0, import_react2.createElement)("span", null, option.label)
3014
+ (0, import_react3.createElement)("span", null, option.label)
1945
3015
  ))
1946
3016
  )
1947
3017
  );
1948
3018
  }
1949
3019
  function PositionSelect(props) {
1950
- return (0, import_react2.createElement)(
3020
+ return (0, import_react3.createElement)(
1951
3021
  "label",
1952
3022
  { className: "dsh-better-left-sidebar-position" },
1953
- (0, import_react2.createElement)("span", null, props.label),
1954
- (0, import_react2.createElement)(
3023
+ (0, import_react3.createElement)("span", null, props.label),
3024
+ (0, import_react3.createElement)(
1955
3025
  "select",
1956
3026
  {
1957
3027
  name: props.field,
@@ -1960,40 +3030,40 @@ function PositionSelect(props) {
1960
3030
  disabled: props.disabled,
1961
3031
  onChange: (event) => props.onChange(event.currentTarget.value)
1962
3032
  },
1963
- ...props.options.map((option) => (0, import_react2.createElement)("option", { key: option.value, value: option.value }, option.label))
3033
+ ...props.options.map((option) => (0, import_react3.createElement)("option", { key: option.value, value: option.value }, option.label))
1964
3034
  )
1965
3035
  );
1966
3036
  }
1967
3037
  function LayoutPreview(props) {
1968
3038
  const { preferences, t } = props;
1969
- return (0, import_react2.createElement)(
3039
+ return (0, import_react3.createElement)(
1970
3040
  "figure",
1971
3041
  { className: "dsh-better-left-sidebar-layout-preview" },
1972
- (0, import_react2.createElement)("figcaption", null, props.title),
1973
- (0, import_react2.createElement)(
3042
+ (0, import_react3.createElement)("figcaption", null, props.title),
3043
+ (0, import_react3.createElement)(
1974
3044
  "div",
1975
3045
  {
1976
3046
  className: "dsh-better-left-sidebar-preview-canvas",
1977
3047
  role: "img",
1978
3048
  "aria-label": `${t("brand")}: ${brandLabel(t, preferences.brandPosition)}; ${t("globalNavigation")}: ${globalLabel(t, preferences.globalNavigationPosition)}; ${t("tools")}: ${toolsLabel(t, preferences.toolsPosition)}`
1979
3049
  },
1980
- (0, import_react2.createElement)(
3050
+ (0, import_react3.createElement)(
1981
3051
  "div",
1982
3052
  { className: "dsh-better-left-sidebar-preview-rail" },
1983
- preferences.brandPosition === "navigation-top" ? (0, import_react2.createElement)(PreviewBlock, { kind: "brand", label: t("previewBrand") }) : null,
1984
- preferences.globalNavigationPosition === "rail" ? (0, import_react2.createElement)(PreviewBlock, { kind: "navigation", label: t("previewNavigation") }) : null,
1985
- preferences.toolsPosition === "rail-bottom" ? (0, import_react2.createElement)(PreviewBlock, { kind: "tools", label: t("previewTools") }) : null
3053
+ preferences.brandPosition === "navigation-top" ? (0, import_react3.createElement)(PreviewBlock, { kind: "brand", label: t("previewBrand") }) : null,
3054
+ preferences.globalNavigationPosition === "rail" ? (0, import_react3.createElement)(PreviewBlock, { kind: "navigation", label: t("previewNavigation") }) : null,
3055
+ preferences.toolsPosition === "rail-bottom" ? (0, import_react3.createElement)(PreviewBlock, { kind: "tools", label: t("previewTools") }) : null
1986
3056
  ),
1987
- (0, import_react2.createElement)(
3057
+ (0, import_react3.createElement)(
1988
3058
  "div",
1989
3059
  { className: "dsh-better-left-sidebar-preview-conversation" },
1990
- preferences.brandPosition === "sidebar-top" ? (0, import_react2.createElement)(PreviewBlock, { kind: "brand", label: t("previewBrand") }) : null,
1991
- (0, import_react2.createElement)("div", { className: "dsh-better-left-sidebar-preview-session" }, t("previewNewSession")),
1992
- preferences.globalNavigationPosition === "conversation-top" ? (0, import_react2.createElement)(PreviewBlock, { kind: "navigation", label: t("previewNavigation") }) : null,
1993
- (0, import_react2.createElement)("div", { className: "dsh-better-left-sidebar-preview-list" }, t("previewConversations")),
1994
- preferences.toolsPosition === "conversation-bottom" ? (0, import_react2.createElement)(PreviewBlock, { kind: "tools", label: t("previewTools") }) : null
3060
+ preferences.brandPosition === "sidebar-top" ? (0, import_react3.createElement)(PreviewBlock, { kind: "brand", label: t("previewBrand") }) : null,
3061
+ (0, import_react3.createElement)("div", { className: "dsh-better-left-sidebar-preview-session" }, t("previewNewSession")),
3062
+ preferences.globalNavigationPosition === "conversation-top" ? (0, import_react3.createElement)(PreviewBlock, { kind: "navigation", label: t("previewNavigation") }) : null,
3063
+ (0, import_react3.createElement)("div", { className: "dsh-better-left-sidebar-preview-list" }, t("previewConversations")),
3064
+ preferences.toolsPosition === "conversation-bottom" ? (0, import_react3.createElement)(PreviewBlock, { kind: "tools", label: t("previewTools") }) : null
1995
3065
  ),
1996
- preferences.brandPosition === "hidden" ? (0, import_react2.createElement)("div", { className: "dsh-better-left-sidebar-preview-hidden" }, `${t("previewBrand")} \xB7 ${t("hidden")}`) : null
3066
+ preferences.brandPosition === "hidden" ? (0, import_react3.createElement)("div", { className: "dsh-better-left-sidebar-preview-hidden" }, `${t("previewBrand")} \xB7 ${t("hidden")}`) : null
1997
3067
  )
1998
3068
  );
1999
3069
  }
@@ -2007,7 +3077,207 @@ function toolsLabel(t, value) {
2007
3077
  return t(value === "rail-bottom" ? "railBottom" : "conversationBottom");
2008
3078
  }
2009
3079
  function PreviewBlock(props) {
2010
- return (0, import_react2.createElement)("div", { className: `dsh-better-left-sidebar-preview-block is-${props.kind}` }, props.label);
3080
+ return (0, import_react3.createElement)("div", { className: `dsh-better-left-sidebar-preview-block is-${props.kind}` }, props.label);
3081
+ }
3082
+
3083
+ // src/client/compact-widgets.tsx
3084
+ var import_react4 = require("react");
3085
+ var import_react_dom = require("react-dom");
3086
+ var SLOT = "dsh-better-left-sidebar.footer.compact";
3087
+ var HOST_ID = "dsh-better-left-sidebar.compact-host";
3088
+ function connectCompactWidgets(ctx, onChange) {
3089
+ const container = document.createElement("div");
3090
+ container.dataset.dshBetterLeftSidebarRegion = "compact-widgets";
3091
+ container.dataset.dshBetterLeftSidebarCompactWidgets = "";
3092
+ let active = true;
3093
+ let count = 0;
3094
+ let preferences = [];
3095
+ let clockDisplay = DEFAULT_CLOCK_DISPLAY;
3096
+ let surfaceVersion = 0;
3097
+ let disposed = false;
3098
+ const surfaceListeners = /* @__PURE__ */ new Set();
3099
+ const subscribeSurface = (listener) => {
3100
+ surfaceListeners.add(listener);
3101
+ return () => surfaceListeners.delete(listener);
3102
+ };
3103
+ const getSurfaceVersion = () => surfaceVersion;
3104
+ const publishSurface = () => {
3105
+ surfaceVersion += 1;
3106
+ for (const listener of surfaceListeners) listener();
3107
+ };
3108
+ const setActive = (next) => {
3109
+ if (disposed || active === next) return;
3110
+ active = next;
3111
+ container.hidden = !next;
3112
+ container.inert = !next;
3113
+ publishSurface();
3114
+ onChange(count, active);
3115
+ };
3116
+ container.hidden = false;
3117
+ container.inert = false;
3118
+ const entries = () => ctx.slots.entriesOfSlot(SLOT);
3119
+ const failures = /* @__PURE__ */ new Map();
3120
+ const metadata = (entry, registrationIndex, error) => {
3121
+ const id = entry.options.id;
3122
+ if (typeof id !== "string") return void 0;
3123
+ let label = id;
3124
+ try {
3125
+ const value = entry.options.label;
3126
+ const resolved = typeof value === "function" ? value() : value;
3127
+ if (typeof resolved === "string" && resolved.length > 0) label = resolved;
3128
+ } catch {
3129
+ }
3130
+ const box = [...container.children].find((child) => child.getAttribute("data-dsh-better-left-sidebar-compact-widget") === id);
3131
+ const enabled = preferences.find((item) => item.id === id)?.enabled ?? true;
3132
+ const presentation = !enabled ? "disabled" : !active ? "inactive" : !box ? "enabled" : box.getBoundingClientRect().height === 0 ? "empty" : box.hasAttribute("data-dsh-better-left-sidebar-compact-widget-clipped") ? "clipped" : "shown";
3133
+ const result = {
3134
+ presentation,
3135
+ id,
3136
+ label,
3137
+ order: typeof entry.options.order === "number" && Number.isFinite(entry.options.order) ? entry.options.order : 0,
3138
+ registrationIndex
3139
+ };
3140
+ if (error !== void 0) result.error = error;
3141
+ return result;
3142
+ };
3143
+ const refreshFailures = () => {
3144
+ const registered = new Set(ctx.slots.entries(SLOT));
3145
+ const live = new Map(entries().flatMap((entry) => typeof entry.options.id === "string" ? [[entry.options.id, entry]] : []));
3146
+ for (const [id, failure] of failures) {
3147
+ const replacement = live.get(id);
3148
+ if (!registered.has(failure.entry) || replacement && replacement !== failure.entry) failures.delete(id);
3149
+ }
3150
+ };
3151
+ const getCatalog = () => {
3152
+ const all = ctx.slots.entries(SLOT);
3153
+ const live = entries();
3154
+ const visible = live.flatMap((entry) => {
3155
+ const index = all.indexOf(entry);
3156
+ const item = metadata(entry, index < 0 ? all.length : index);
3157
+ return item ? [item] : [];
3158
+ });
3159
+ const liveIds = new Set(visible.map((entry) => entry.id));
3160
+ const failed = [...failures].flatMap(([id, failure]) => {
3161
+ if (liveIds.has(id)) return [];
3162
+ const index = all.indexOf(failure.entry);
3163
+ const item = metadata(failure.entry, index < 0 ? all.length : index, failure.message);
3164
+ return item ? [item] : [];
3165
+ });
3166
+ return [...visible, ...failed].sort((a, b) => a.registrationIndex - b.registrationIndex);
3167
+ };
3168
+ let presentationSnapshot = "";
3169
+ const publishPresentation = () => {
3170
+ const next = JSON.stringify(getCatalog().map(({ id, presentation }) => [id, presentation]));
3171
+ if (next === presentationSnapshot) return;
3172
+ presentationSnapshot = next;
3173
+ publishSurface();
3174
+ };
3175
+ const presentationObserver = new MutationObserver(publishPresentation);
3176
+ const presentationResize = new ResizeObserver(publishPresentation);
3177
+ presentationResize.observe(container);
3178
+ presentationObserver.observe(container, {
3179
+ childList: true,
3180
+ subtree: true,
3181
+ attributes: true,
3182
+ attributeFilter: ["data-dsh-better-left-sidebar-compact-widget-clipped"]
3183
+ });
3184
+ const getCatalogVersion = () => surfaceVersion;
3185
+ const orderedEntries = () => orderCompactWidgetEntries(
3186
+ entries().flatMap((entry) => typeof entry.options.id === "string" ? [{ id: entry.options.id, order: entry.options.order, entry }] : []),
3187
+ preferences
3188
+ );
3189
+ const readCount = () => orderedEntries().filter(({ enabled }) => enabled).length;
3190
+ const publishCount = () => {
3191
+ count = readCount();
3192
+ onChange(count, active);
3193
+ };
3194
+ const publishEntries = () => {
3195
+ refreshFailures();
3196
+ publishSurface();
3197
+ publishCount();
3198
+ };
3199
+ const unsubscribeEntries = ctx.slots.subscribe(SLOT, publishEntries);
3200
+ const unsubscribeErrors = ctx.slots.onEntryError((key, registration, error) => {
3201
+ if (key !== SLOT || !("options" in registration) || typeof registration.options.id !== "string") return;
3202
+ const entry = registration;
3203
+ failures.set(entry.options.id, {
3204
+ entry,
3205
+ message: error instanceof Error ? error.message : String(error)
3206
+ });
3207
+ publishSurface();
3208
+ });
3209
+ publishEntries();
3210
+ const subscribe = (listener) => ctx.slots.subscribe(SLOT, listener);
3211
+ const getVersion = () => ctx.slots.getVersion(SLOT);
3212
+ function CompactHost(props) {
3213
+ (0, import_react4.useSyncExternalStore)(subscribe, getVersion, getVersion);
3214
+ (0, import_react4.useSyncExternalStore)(subscribeSurface, getSurfaceVersion, getSurfaceVersion);
3215
+ const ordered = orderedEntries().filter(({ enabled }) => enabled);
3216
+ return (0, import_react_dom.createPortal)((0, import_react4.createElement)(import_react4.Fragment, null, ...ordered.map(({ entry, enabled: _enabled }) => {
3217
+ const id = entry.id;
3218
+ return (0, import_react4.createElement)("div", {
3219
+ key: id,
3220
+ className: "dsh-better-left-sidebar-compact-widget-box",
3221
+ "data-dsh-better-left-sidebar-compact-widget": id
3222
+ }, props.renderSlot(SLOT, { active }, { only: id }));
3223
+ })), container);
3224
+ }
3225
+ const stopHost = ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
3226
+ name: "sidebar.footer.action",
3227
+ id: HOST_ID,
3228
+ label: "Compact footer widgets",
3229
+ order: 0,
3230
+ children: {
3231
+ [SLOT]: { kind: "list", scope: "root" }
3232
+ }
3233
+ }, CompactHost));
3234
+ const stopClock = ctx.slots.inject(SLOT, () => ctx.slots.register({
3235
+ name: SLOT,
3236
+ id: CLOCK_WIDGET_ID,
3237
+ label: () => ctx.locale.bind(NAMESPACE)("clock"),
3238
+ order: 100
3239
+ }, createClockWidget(ctx.locale, {
3240
+ subscribe: subscribeSurface,
3241
+ getSnapshot: () => clockDisplay
3242
+ })));
3243
+ return {
3244
+ container,
3245
+ get count() {
3246
+ return count;
3247
+ },
3248
+ get active() {
3249
+ return active;
3250
+ },
3251
+ setActive,
3252
+ setPreferences(next) {
3253
+ if (disposed) return;
3254
+ preferences = next;
3255
+ publishSurface();
3256
+ publishCount();
3257
+ },
3258
+ setClock(next) {
3259
+ if (disposed) return;
3260
+ const normalized = normalizeClockDisplay(next);
3261
+ if (normalized.hourCycle === clockDisplay.hourCycle && normalized.monthDay === clockDisplay.monthDay && normalized.weekday === clockDisplay.weekday && normalized.color === clockDisplay.color) return;
3262
+ clockDisplay = normalized;
3263
+ publishSurface();
3264
+ },
3265
+ subscribeCatalog: subscribeSurface,
3266
+ getCatalogVersion,
3267
+ getCatalog,
3268
+ dispose() {
3269
+ if (disposed) return;
3270
+ disposed = true;
3271
+ presentationObserver.disconnect();
3272
+ presentationResize.disconnect();
3273
+ stopClock();
3274
+ stopHost();
3275
+ unsubscribeEntries();
3276
+ unsubscribeErrors();
3277
+ surfaceListeners.clear();
3278
+ container.remove();
3279
+ }
3280
+ };
2011
3281
  }
2012
3282
 
2013
3283
  // src/client/index.ts
@@ -2023,9 +3293,16 @@ function apply(ctx) {
2023
3293
  document.head.append(style);
2024
3294
  html.classList.add("dsh-better-left-sidebar");
2025
3295
  const interaction = connectInteraction();
2026
- const layout = connectLayoutAdapter(interaction.setRoot);
3296
+ let layout;
3297
+ const compactWidgets = connectCompactWidgets(ctx, (count, active) => layout?.setCompactWidgets(count, active));
3298
+ interaction.setCompactWidgetsActive(compactWidgets.setActive);
3299
+ layout = connectLayoutAdapter((root, anchorsChanged) => interaction.setRoot(root, anchorsChanged), compactWidgets.container);
3300
+ layout.setCompactWidgets(compactWidgets.count, compactWidgets.active);
2027
3301
  const applyPreferences = (value) => {
2028
3302
  preferences = normalizePreferences(value);
3303
+ const config = value && typeof value === "object" ? value : {};
3304
+ compactWidgets.setPreferences(validateCompactWidgetPreferences(config.compactWidgets));
3305
+ compactWidgets.setClock(normalizeClockDisplay(config.clock));
2029
3306
  layout.update(preferences);
2030
3307
  interaction.setPreferences(preferences);
2031
3308
  };
@@ -2036,10 +3313,11 @@ function apply(ctx) {
2036
3313
  applyPreferences(DEFAULT_PREFERENCES);
2037
3314
  interaction.setTranslate(translate);
2038
3315
  const disposeNavigation = connectNavigation(ctx);
2039
- connectSettings(ctx, applyPreferences, applyTranslate);
3316
+ connectSettings(ctx, compactWidgets, applyPreferences, applyTranslate);
2040
3317
  return () => {
2041
3318
  disposeNavigation();
2042
3319
  layout.dispose();
3320
+ compactWidgets.dispose();
2043
3321
  interaction.dispose();
2044
3322
  html.classList.remove("dsh-better-left-sidebar");
2045
3323
  style.remove();