@jackuait/blok 0.7.3-beta.3 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/blok.mjs +2 -2
- package/dist/chunks/{blok-CdxHhr5i.mjs → blok-BmlbETK7.mjs} +2119 -2013
- package/dist/chunks/{constants-C_H9o9Ao.mjs → constants-WhLyFkza.mjs} +260 -223
- package/dist/chunks/{i18next-loader-D5HxE5ZQ.mjs → i18next-loader-CZARkla1.mjs} +1 -1
- package/dist/chunks/{lightweight-i18n-Safdy0ua.mjs → lightweight-i18n-BQa0F2X6.mjs} +9 -0
- package/dist/chunks/{tools-B0YXCZFW.mjs → tools-BCb5bMO3.mjs} +973 -843
- package/dist/full.mjs +3 -3
- package/dist/locales.mjs +9 -0
- package/dist/react.mjs +2 -2
- package/dist/tools.mjs +2 -2
- package/package.json +1 -1
- package/src/components/block/style-manager.ts +1 -1
- package/src/components/blocks.ts +26 -54
- package/src/components/constants/data-attributes.ts +0 -2
- package/src/components/i18n/locales/en/messages.json +9 -0
- package/src/components/icons/index.ts +34 -6
- package/src/components/inline-tools/inline-tool-link.ts +202 -5
- package/src/components/inline-tools/inline-tool-marker.ts +166 -23
- package/src/components/inline-tools/utils/formatting-range-utils.ts +10 -1
- package/src/components/modules/blockManager/blockManager.ts +2 -2
- package/src/components/modules/blockManager/operations.ts +2 -2
- package/src/components/modules/blockManager/repository.ts +1 -9
- package/src/components/modules/blockManager/types.ts +1 -1
- package/src/components/modules/drag/operations/DragOperations.ts +45 -6
- package/src/components/modules/paste/google-docs-preprocessor.ts +69 -2
- package/src/components/modules/paste/handlers/blok-data-handler.ts +96 -19
- package/src/components/modules/renderer.ts +2 -0
- package/src/components/modules/toolbar/blockSettings.ts +1 -1
- package/src/components/modules/toolbar/index.ts +21 -0
- package/src/components/modules/toolbar/plus-button.ts +15 -5
- package/src/components/selection/fake-background/index.ts +9 -10
- package/src/components/shared/color-picker.ts +108 -95
- package/src/components/shared/color-presets.ts +30 -2
- package/src/components/ui/toolbox.ts +36 -7
- package/src/components/utils/color-mapping.ts +43 -1
- package/src/components/utils/color-migration.ts +37 -0
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +4 -3
- package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +5 -39
- package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +2 -2
- package/src/components/utils/popover/components/popover-item/popover-item.ts +11 -0
- package/src/components/utils/popover/components/search-input/search-input.const.ts +2 -3
- package/src/components/utils/popover/components/search-input/search-input.ts +1 -32
- package/src/components/utils/popover/popover-abstract.ts +2 -4
- package/src/components/utils/popover/popover-desktop.ts +1 -16
- package/src/components/utils/popover/popover-inline.ts +1 -2
- package/src/components/utils/popover/popover-mobile.ts +2 -2
- package/src/components/utils/popover/popover.const.ts +1 -1
- package/src/stories/Table.stories.ts +15 -9
- package/src/styles/main.css +312 -14
- package/src/tools/header/index.ts +5 -5
- package/src/tools/list/constants.ts +11 -4
- package/src/tools/list/depth-validator.ts +13 -1
- package/src/tools/list/dom-builder.ts +5 -3
- package/src/tools/list/index.ts +3 -2
- package/src/tools/paragraph/index.ts +2 -2
- package/src/tools/table/table-cell-color-picker.ts +1 -1
- package/src/tools/table/table-cell-selection.ts +1 -2
- package/src/tools/table/table-core.ts +2 -2
- package/src/tools/table/table-grip-visuals.ts +13 -5
- package/src/tools/table/table-heading-toggle.ts +15 -9
- package/src/tools/table/table-row-col-controls.ts +17 -11
- package/src/tools/table/table-row-col-drag.ts +26 -3
- package/src/tools/toggle/constants.ts +5 -5
- package/src/tools/toggle/index.ts +1 -1
- package/types/tools/hook-events.d.ts +6 -0
- package/types/tools/toggle.d.ts +23 -0
- package/types/tools-entry.d.ts +3 -0
- package/types/utils/popover/popover-item.d.ts +6 -0
|
@@ -167,6 +167,13 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
167
167
|
*/
|
|
168
168
|
private isInsideTableCell = false;
|
|
169
169
|
|
|
170
|
+
/**
|
|
171
|
+
* Whether the toolbox was opened in slash-search mode (via "/" key or existing slash paragraph).
|
|
172
|
+
* When false (opened via plus button), the input filter uses the full block text as the query
|
|
173
|
+
* instead of requiring a leading "/" and does not close on missing slash.
|
|
174
|
+
*/
|
|
175
|
+
private openedWithSlash = true;
|
|
176
|
+
|
|
170
177
|
/**
|
|
171
178
|
* Toolbox constructor
|
|
172
179
|
* @param options - available parameters
|
|
@@ -280,8 +287,10 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
280
287
|
|
|
281
288
|
/**
|
|
282
289
|
* Open Toolbox with Tools
|
|
290
|
+
* @param withSlash - When true (default), inline search requires "/" and closes on its removal.
|
|
291
|
+
* When false (plus button), the full block text is used as the filter query.
|
|
283
292
|
*/
|
|
284
|
-
public open(): void {
|
|
293
|
+
public open(withSlash = true): void {
|
|
285
294
|
if (this.isEmpty) {
|
|
286
295
|
return;
|
|
287
296
|
}
|
|
@@ -318,8 +327,20 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
318
327
|
const caretRect = SelectionUtils.rect;
|
|
319
328
|
|
|
320
329
|
this.popover.updatePosition(caretRect);
|
|
330
|
+
} else if (!withSlash && this.popover instanceof PopoverDesktop) {
|
|
331
|
+
/**
|
|
332
|
+
* When opened without slash (via plus button), the trigger element (plus button)
|
|
333
|
+
* is at the top of the block. Position the popover below the block's bottom edge
|
|
334
|
+
* instead, so it doesn't overlap the block's placeholder text.
|
|
335
|
+
*/
|
|
336
|
+
const currentBlock = this.api.blocks.getBlockByIndex(currentBlockIndex);
|
|
337
|
+
|
|
338
|
+
if (currentBlock) {
|
|
339
|
+
this.popover.updatePosition(currentBlock.holder.getBoundingClientRect());
|
|
340
|
+
}
|
|
321
341
|
}
|
|
322
342
|
|
|
343
|
+
this.openedWithSlash = withSlash;
|
|
323
344
|
this.opened = true;
|
|
324
345
|
this.emit(ToolboxEvent.Opened);
|
|
325
346
|
this.startListeningToBlockInput();
|
|
@@ -725,7 +746,12 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
725
746
|
|
|
726
747
|
/**
|
|
727
748
|
* Handles input events on the block to filter the toolbox.
|
|
728
|
-
*
|
|
749
|
+
*
|
|
750
|
+
* In slash mode (default): extracts text after "/" and filters by it.
|
|
751
|
+
* Closes if "/" is removed.
|
|
752
|
+
*
|
|
753
|
+
* In no-slash mode (opened via plus button): uses full block text as the
|
|
754
|
+
* filter query and does not close on missing "/".
|
|
729
755
|
*/
|
|
730
756
|
private handleBlockInput = (): void => {
|
|
731
757
|
if (this.currentContentEditable === null) {
|
|
@@ -733,15 +759,18 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
|
|
733
759
|
}
|
|
734
760
|
|
|
735
761
|
const text = this.currentContentEditable.textContent || '';
|
|
736
|
-
const slashIndex = text.lastIndexOf('/');
|
|
737
762
|
|
|
738
|
-
if (
|
|
739
|
-
|
|
763
|
+
if (this.openedWithSlash) {
|
|
764
|
+
const slashIndex = text.lastIndexOf('/');
|
|
740
765
|
|
|
741
|
-
|
|
766
|
+
if (slashIndex === -1) {
|
|
767
|
+
this.close();
|
|
768
|
+
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
742
771
|
}
|
|
743
772
|
|
|
744
|
-
const query = text.slice(
|
|
773
|
+
const query = this.openedWithSlash ? text.slice(text.lastIndexOf('/') + 1) : text;
|
|
745
774
|
|
|
746
775
|
if (this.currentContentEditable instanceof HTMLElement) {
|
|
747
776
|
this.currentContentEditable.setAttribute(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { COLOR_PRESETS } from '../shared/color-presets';
|
|
1
|
+
import { COLOR_PRESETS, COLOR_PRESETS_DARK } from '../shared/color-presets';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Convert an HSL color (H in degrees, S and L as 0-100 percentages) to an RGB tuple.
|
|
@@ -239,3 +239,45 @@ export function mapToNearestPresetColor(cssColor: string, mode: 'text' | 'bg'):
|
|
|
239
239
|
|
|
240
240
|
return best.color;
|
|
241
241
|
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Map an arbitrary CSS color to the name of the nearest Blok preset color.
|
|
245
|
+
*
|
|
246
|
+
* Searches both light and dark presets to correctly recover the semantic name
|
|
247
|
+
* from hex values that originated in either theme. First match wins on ties.
|
|
248
|
+
*
|
|
249
|
+
* @param cssColor - CSS color string (hex or rgb)
|
|
250
|
+
* @param mode - 'text' for text color presets, 'bg' for background presets
|
|
251
|
+
* @returns the nearest preset name (e.g. 'red'), or null if unparseable
|
|
252
|
+
*/
|
|
253
|
+
export function mapToNearestPresetName(cssColor: string, mode: 'text' | 'bg'): string | null {
|
|
254
|
+
const rgb = parseColor(cssColor);
|
|
255
|
+
|
|
256
|
+
if (rgb === null) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const hsl = rgbToHsl(rgb);
|
|
261
|
+
const allPresets = [...COLOR_PRESETS, ...COLOR_PRESETS_DARK];
|
|
262
|
+
|
|
263
|
+
const best = allPresets.reduce<{ name: string; distance: number } | null>(
|
|
264
|
+
(acc, preset) => {
|
|
265
|
+
const presetRgb = parseColor(preset[mode]);
|
|
266
|
+
|
|
267
|
+
if (presetRgb === null) {
|
|
268
|
+
return acc;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const distance = hslDistance(hsl, rgbToHsl(presetRgb));
|
|
272
|
+
|
|
273
|
+
if (acc === null || distance < acc.distance) {
|
|
274
|
+
return { name: preset.name, distance };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return acc;
|
|
278
|
+
},
|
|
279
|
+
null
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
return best?.name ?? null;
|
|
283
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { colorVarName } from '../shared/color-presets';
|
|
2
|
+
import { mapToNearestPresetName } from './color-mapping';
|
|
3
|
+
|
|
4
|
+
const PROPS = ['color', 'background-color'] as const;
|
|
5
|
+
type Prop = typeof PROPS[number];
|
|
6
|
+
|
|
7
|
+
const PROP_MODE: Record<Prop, 'text' | 'bg'> = {
|
|
8
|
+
'color': 'text',
|
|
9
|
+
'background-color': 'bg',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Scan all <mark> elements inside container and replace raw hex color/background-color
|
|
14
|
+
* inline style values with their corresponding CSS custom property references.
|
|
15
|
+
*
|
|
16
|
+
* Safe to call multiple times — already-migrated var() values and 'transparent' are
|
|
17
|
+
* left unchanged.
|
|
18
|
+
*
|
|
19
|
+
* @param container - Root element to search within (e.g. the editor redactor node)
|
|
20
|
+
*/
|
|
21
|
+
export function migrateMarkColors(container: Element): void {
|
|
22
|
+
container.querySelectorAll<HTMLElement>('mark').forEach((el) => {
|
|
23
|
+
for (const prop of PROPS) {
|
|
24
|
+
const value = el.style.getPropertyValue(prop);
|
|
25
|
+
|
|
26
|
+
if (!value || value === 'transparent' || value.startsWith('var(')) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const name = mapToNearestPresetName(value, PROP_MODE[prop]);
|
|
31
|
+
|
|
32
|
+
if (name !== null) {
|
|
33
|
+
el.style.setProperty(prop, colorVarName(name, PROP_MODE[prop]));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -10,7 +10,7 @@ export const css = {
|
|
|
10
10
|
* Note: noHover state is handled via [data-blok-popover-item-no-hover] which disables hover
|
|
11
11
|
* Priority order: active < hover < focus (focus wins when navigating with keyboard)
|
|
12
12
|
*/
|
|
13
|
-
item: 'flex items-center select-none border-none bg-transparent rounded-lg px-2 py-1 text-text-primary mb-px outline-hidden
|
|
13
|
+
item: 'flex items-center select-none border-none bg-transparent rounded-lg px-2 py-1 text-text-primary mb-px outline-hidden max-h-9 overflow-hidden data-blok-popover-item-active:bg-icon-active-bg data-blok-popover-item-active:text-icon-active-text can-hover:hover:cursor-pointer can-hover:hover:bg-item-hover-bg data-blok-force-hover:cursor-pointer data-blok-force-hover:bg-item-hover-bg data-[blok-focused="true"]:bg-item-focus-bg data-blok-popover-item-no-hover:hover:bg-transparent data-blok-popover-item-no-hover:cursor-default can-hover:data-blok-popover-item-destructive:hover:text-item-destructive-text can-hover:data-blok-popover-item-destructive:hover:bg-item-destructive-hover-bg [&[data-blok-popover-item-destructive][data-blok-force-hover]]:text-item-destructive-text [&[data-blok-popover-item-destructive][data-blok-force-hover]]:bg-item-destructive-hover-bg [&[data-blok-popover-item-destructive][data-blok-focused="true"]]:text-item-destructive-text [&[data-blok-popover-item-destructive][data-blok-focused="true"]]:bg-item-destructive-hover-bg',
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Item disabled state
|
|
@@ -21,7 +21,7 @@ export const css = {
|
|
|
21
21
|
/**
|
|
22
22
|
* Icon container styles
|
|
23
23
|
*/
|
|
24
|
-
icon: 'flex items-center justify-center w-7 h-7 shrink-0 rounded-lg bg-popover-icon-bg
|
|
24
|
+
icon: 'flex items-center justify-center w-7 h-7 shrink-0 rounded-lg bg-popover-icon-bg [&_svg]:w-icon [&_svg]:h-icon',
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Focused state class for DomIterator/Flipper keyboard navigation.
|
|
@@ -37,7 +37,8 @@ export const cssInline = {
|
|
|
37
37
|
/**
|
|
38
38
|
* Item in inline context - more compact styling
|
|
39
39
|
*/
|
|
40
|
-
item: 'rounded p-1',
|
|
40
|
+
item: 'rounded-lg p-1',
|
|
41
|
+
itemIconOnly: 'aspect-square justify-center',
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -305,7 +305,7 @@ export class PopoverItemDefault extends PopoverItem {
|
|
|
305
305
|
private createIconElement(icon: string, iconWithGap: boolean, isInline: boolean, isNestedInline: boolean): HTMLElement {
|
|
306
306
|
const iconEl = document.createElement('div');
|
|
307
307
|
|
|
308
|
-
iconEl.className = this.getIconClass(iconWithGap, isInline, isNestedInline
|
|
308
|
+
iconEl.className = this.getIconClass(iconWithGap, isInline, isNestedInline);
|
|
309
309
|
iconEl.setAttribute(DATA_ATTR.popoverItemIcon, '');
|
|
310
310
|
iconEl.setAttribute('data-blok-testid', 'popover-item-icon');
|
|
311
311
|
iconEl.innerHTML = icon;
|
|
@@ -328,6 +328,7 @@ export class PopoverItemDefault extends PopoverItem {
|
|
|
328
328
|
css.item,
|
|
329
329
|
!isInline && !isNestedInline && 'pl-2 pr-3',
|
|
330
330
|
isInline && cssInline.item,
|
|
331
|
+
isInline && this.params.icon && cssInline.itemIconOnly,
|
|
331
332
|
isNestedInline && cssNestedInline.item,
|
|
332
333
|
this.params.isDisabled && css.itemDisabled
|
|
333
334
|
);
|
|
@@ -336,15 +337,14 @@ export class PopoverItemDefault extends PopoverItem {
|
|
|
336
337
|
/**
|
|
337
338
|
* Gets the icon class based on context
|
|
338
339
|
*/
|
|
339
|
-
private getIconClass(iconWithGap: boolean, isInline: boolean, isNestedInline: boolean
|
|
340
|
+
private getIconClass(iconWithGap: boolean, isInline: boolean, isNestedInline: boolean): string {
|
|
340
341
|
return twMerge(
|
|
341
342
|
css.icon,
|
|
342
343
|
isInline && 'w-auto h-auto bg-transparent [&_svg]:w-icon [&_svg]:h-icon mobile:[&_svg]:w-icon-mobile mobile:[&_svg]:h-icon-mobile',
|
|
343
344
|
isNestedInline && 'w-toolbox-btn h-toolbox-btn',
|
|
344
345
|
iconWithGap && 'mr-3',
|
|
345
346
|
iconWithGap && isInline && 'shadow-none mr-0!',
|
|
346
|
-
iconWithGap && isNestedInline && 'mr-2!'
|
|
347
|
-
isWobbling && 'animate-wobble'
|
|
347
|
+
iconWithGap && isNestedInline && 'mr-2!'
|
|
348
348
|
);
|
|
349
349
|
}
|
|
350
350
|
|
|
@@ -639,47 +639,13 @@ export class PopoverItemDefault extends PopoverItem {
|
|
|
639
639
|
item.onActivate?.(item);
|
|
640
640
|
this.disableConfirmationMode();
|
|
641
641
|
} catch {
|
|
642
|
-
|
|
642
|
+
// onActivate threw an error
|
|
643
643
|
}
|
|
644
644
|
} else {
|
|
645
645
|
this.enableConfirmationMode(item.confirmation);
|
|
646
646
|
}
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
-
/**
|
|
650
|
-
* Animates item which symbolizes that error occurred while executing 'onActivate()' callback
|
|
651
|
-
*/
|
|
652
|
-
private animateError(): void {
|
|
653
|
-
this.triggerWobble();
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Triggers wobble animation on the icon
|
|
658
|
-
*/
|
|
659
|
-
private triggerWobble(): void {
|
|
660
|
-
if (!this.nodes.icon) {
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const isInline = this.renderParams?.isInline ?? false;
|
|
665
|
-
const isNestedInline = this.renderParams?.isNestedInline ?? false;
|
|
666
|
-
const iconWithGap = this.renderParams?.iconWithGap ?? true;
|
|
667
|
-
|
|
668
|
-
// Add wobble class
|
|
669
|
-
this.nodes.icon.setAttribute(DATA_ATTR.popoverItemWobble, 'true');
|
|
670
|
-
this.nodes.icon.className = this.getIconClass(iconWithGap, isInline, isNestedInline, true);
|
|
671
|
-
|
|
672
|
-
// Remove wobble after animation ends
|
|
673
|
-
const handleAnimationEnd = (): void => {
|
|
674
|
-
if (this.nodes.icon) {
|
|
675
|
-
this.nodes.icon.removeAttribute(DATA_ATTR.popoverItemWobble);
|
|
676
|
-
this.nodes.icon.className = this.getIconClass(iconWithGap, isInline, isNestedInline, false);
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
|
|
680
|
-
this.nodes.icon.addEventListener('animationend', handleAnimationEnd, { once: true });
|
|
681
|
-
}
|
|
682
|
-
|
|
683
649
|
/**
|
|
684
650
|
* Gets reference to the icon element
|
|
685
651
|
*/
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Tailwind CSS classes for popover separator component
|
|
3
3
|
*/
|
|
4
4
|
export const css = {
|
|
5
|
-
container: 'py-1.5 px-2
|
|
5
|
+
container: 'py-1.5 px-2 max-h-5 overflow-hidden',
|
|
6
6
|
containerHidden: 'opacity-0 max-h-0! py-0!',
|
|
7
7
|
line: 'h-px w-full bg-popover-border/60',
|
|
8
8
|
};
|
|
@@ -12,7 +12,7 @@ export const css = {
|
|
|
12
12
|
*/
|
|
13
13
|
export const cssInline = {
|
|
14
14
|
// Inline context: horizontal separator
|
|
15
|
-
container: 'px-1 py-0',
|
|
15
|
+
container: 'px-1 py-0 max-h-none',
|
|
16
16
|
line: 'h-full w-px',
|
|
17
17
|
// Nested inline context: back to vertical separator
|
|
18
18
|
nestedContainer: 'py-1 px-[3px]',
|
|
@@ -170,6 +170,17 @@ export abstract class PopoverItem {
|
|
|
170
170
|
return this.params.children?.width;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Returns the min-width for children popover, if specified
|
|
175
|
+
*/
|
|
176
|
+
public get childrenMinWidth(): string | undefined {
|
|
177
|
+
if (this.params === undefined || !('children' in this.params)) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return this.params.children?.minWidth;
|
|
182
|
+
}
|
|
183
|
+
|
|
173
184
|
/**
|
|
174
185
|
* True if popover should close once item is activated
|
|
175
186
|
*/
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* CSS class names to be used in popover search input class
|
|
3
3
|
*/
|
|
4
4
|
export const css = {
|
|
5
|
-
wrapper: 'bg-search-input-bg border border-search-input-border rounded-lg p-1 grid grid-cols-[
|
|
5
|
+
wrapper: 'bg-search-input-bg border border-search-input-border rounded-lg p-1 grid grid-cols-[auto_1fr] grid-rows-[auto] focus-within:border-search-input-focus-border focus-within:shadow-[0_0_0_2px_rgba(35,131,226,0.15)]',
|
|
6
6
|
icon: 'w-toolbox-btn h-toolbox-btn flex items-center justify-center mr-2 [&_svg]:w-icon [&_svg]:h-icon [&_svg]:text-gray-text',
|
|
7
|
-
input: "text-sm outline-hidden font-medium font-inherit border-0 bg-transparent m-0 p-0 leading-[22px] min-w-[calc(100%-(--spacing(6))-10px)] placeholder:text-gray-text/
|
|
8
|
-
clearButton: 'flex items-center justify-center w-toolbox-btn h-toolbox-btn cursor-pointer border-0 bg-transparent rounded p-0 opacity-0 pointer-events-none transition-opacity duration-150 [&_svg]:w-3 [&_svg]:h-3 [&_svg]:text-gray-text can-hover:hover:[&_svg]:text-dark',
|
|
7
|
+
input: "text-sm outline-hidden font-medium font-inherit border-0 bg-transparent m-0 p-0 leading-[22px] min-w-[calc(100%-(--spacing(6))-10px)] text-text-primary placeholder:text-gray-text/80 placeholder:font-normal",
|
|
9
8
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Dom } from '../../../../dom';
|
|
2
|
-
import {
|
|
2
|
+
import { IconSearch } from '../../../../icons';
|
|
3
3
|
import { EventsDispatcher } from '../../../events';
|
|
4
4
|
import { Listeners } from '../../../listeners';
|
|
5
5
|
|
|
@@ -23,11 +23,6 @@ export class SearchInput extends EventsDispatcher<SearchInputEventMap> {
|
|
|
23
23
|
*/
|
|
24
24
|
private input: HTMLInputElement;
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
* Clear button element
|
|
28
|
-
*/
|
|
29
|
-
private clearButton: HTMLButtonElement;
|
|
30
|
-
|
|
31
26
|
/**
|
|
32
27
|
* The instance of the Listeners util
|
|
33
28
|
*/
|
|
@@ -78,15 +73,8 @@ export class SearchInput extends EventsDispatcher<SearchInputEventMap> {
|
|
|
78
73
|
this.input.setAttribute('data-blok-flipper-navigation-target', 'true');
|
|
79
74
|
this.input.setAttribute('data-blok-testid', 'popover-search-input');
|
|
80
75
|
|
|
81
|
-
this.clearButton = Dom.make('button', css.clearButton, {
|
|
82
|
-
type: 'button',
|
|
83
|
-
innerHTML: IconCross,
|
|
84
|
-
}) as HTMLButtonElement;
|
|
85
|
-
this.clearButton.setAttribute('aria-label', 'Clear search');
|
|
86
|
-
|
|
87
76
|
this.wrapper.appendChild(iconWrapper);
|
|
88
77
|
this.wrapper.appendChild(this.input);
|
|
89
|
-
this.wrapper.appendChild(this.clearButton);
|
|
90
78
|
|
|
91
79
|
this.overrideValueProperty();
|
|
92
80
|
|
|
@@ -95,11 +83,6 @@ export class SearchInput extends EventsDispatcher<SearchInputEventMap> {
|
|
|
95
83
|
eventsToHandle.forEach((eventName) => {
|
|
96
84
|
this.listeners.on(this.input, eventName, this.handleValueChange);
|
|
97
85
|
});
|
|
98
|
-
|
|
99
|
-
this.listeners.on(this.clearButton, 'click', () => {
|
|
100
|
-
this.clear();
|
|
101
|
-
this.input.focus();
|
|
102
|
-
});
|
|
103
86
|
}
|
|
104
87
|
|
|
105
88
|
/**
|
|
@@ -121,14 +104,12 @@ export class SearchInput extends EventsDispatcher<SearchInputEventMap> {
|
|
|
121
104
|
*/
|
|
122
105
|
public clear(): void {
|
|
123
106
|
this.input.value = '';
|
|
124
|
-
this.updateClearButtonVisibility();
|
|
125
107
|
}
|
|
126
108
|
|
|
127
109
|
/**
|
|
128
110
|
* Handles value changes for the input element
|
|
129
111
|
*/
|
|
130
112
|
private handleValueChange = (): void => {
|
|
131
|
-
this.updateClearButtonVisibility();
|
|
132
113
|
this.applySearch(this.input.value);
|
|
133
114
|
};
|
|
134
115
|
|
|
@@ -178,18 +159,6 @@ export class SearchInput extends EventsDispatcher<SearchInputEventMap> {
|
|
|
178
159
|
});
|
|
179
160
|
}
|
|
180
161
|
|
|
181
|
-
/**
|
|
182
|
-
* Shows or hides the clear button based on whether the input has a value
|
|
183
|
-
*/
|
|
184
|
-
private updateClearButtonVisibility(): void {
|
|
185
|
-
const visible = this.input.value.length > 0;
|
|
186
|
-
|
|
187
|
-
this.clearButton.classList.toggle('opacity-0', !visible);
|
|
188
|
-
this.clearButton.classList.toggle('pointer-events-none', !visible);
|
|
189
|
-
this.clearButton.classList.toggle('opacity-100', visible);
|
|
190
|
-
this.clearButton.classList.toggle('pointer-events-auto', visible);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
162
|
/**
|
|
194
163
|
* Clears memory
|
|
195
164
|
*/
|
|
@@ -391,11 +391,9 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
|
|
391
391
|
protected toggleNothingFoundMessage(isDisplayed: boolean): void {
|
|
392
392
|
if (isDisplayed) {
|
|
393
393
|
this.nodes.nothingFoundMessage.classList.remove('hidden');
|
|
394
|
-
this.nodes.nothingFoundMessage.classList.add('animate-[fade-in_150ms_ease_forwards]');
|
|
395
394
|
this.nodes.nothingFoundMessage.setAttribute(DATA_ATTR.nothingFoundDisplayed, 'true');
|
|
396
395
|
} else {
|
|
397
396
|
this.nodes.nothingFoundMessage.classList.add('hidden');
|
|
398
|
-
this.nodes.nothingFoundMessage.classList.remove('animate-[fade-in_150ms_ease_forwards]');
|
|
399
397
|
this.nodes.nothingFoundMessage.removeAttribute(DATA_ATTR.nothingFoundDisplayed);
|
|
400
398
|
}
|
|
401
399
|
}
|
|
@@ -461,14 +459,14 @@ export abstract class PopoverAbstract<Nodes extends PopoverNodes = PopoverNodes>
|
|
|
461
459
|
// Create popover container
|
|
462
460
|
const popoverContainer = document.createElement('div');
|
|
463
461
|
popoverContainer.className = css.popoverContainer;
|
|
464
|
-
popoverContainer.style.boxShadow = '
|
|
462
|
+
popoverContainer.style.boxShadow = 'var(--blok-popover-box-shadow)';
|
|
465
463
|
popoverContainer.setAttribute(DATA_ATTR.popoverContainer, '');
|
|
466
464
|
popoverContainer.setAttribute('data-blok-testid', 'popover-container');
|
|
467
465
|
|
|
468
466
|
// Create nothing found message
|
|
469
467
|
const nothingFoundMessage = document.createElement('div');
|
|
470
468
|
nothingFoundMessage.className = twMerge(
|
|
471
|
-
'cursor-default text-[13px] leading-5 font-normal whitespace-nowrap overflow-hidden text-ellipsis text-gray-text
|
|
469
|
+
'cursor-default text-[13px] leading-5 font-normal whitespace-nowrap overflow-hidden text-ellipsis text-gray-text px-3 py-4 text-center',
|
|
472
470
|
'hidden'
|
|
473
471
|
);
|
|
474
472
|
nothingFoundMessage.setAttribute('data-blok-testid', 'popover-nothing-found');
|
|
@@ -562,6 +562,7 @@ export class PopoverDesktop extends PopoverAbstract {
|
|
|
562
562
|
messages: this.messages,
|
|
563
563
|
onNavigateBack: this.destroyNestedPopoverIfExists.bind(this),
|
|
564
564
|
width: item.childrenWidth,
|
|
565
|
+
minWidth: item.childrenMinWidth,
|
|
565
566
|
handleContentEditableNavigation: handleContentEditable,
|
|
566
567
|
autoFocusFirstItem: this.params.autoFocusFirstItem,
|
|
567
568
|
});
|
|
@@ -1035,15 +1036,6 @@ export class PopoverDesktop extends PopoverAbstract {
|
|
|
1035
1036
|
|
|
1036
1037
|
const isNothingFound = matchingTopLevel.length === 0 && data.promotedItems.length === 0;
|
|
1037
1038
|
|
|
1038
|
-
/**
|
|
1039
|
-
* When nothing is found, disable transitions so items hide instantly.
|
|
1040
|
-
*/
|
|
1041
|
-
if (isNothingFound) {
|
|
1042
|
-
this.items.forEach(item => {
|
|
1043
|
-
item.getElement()?.style.setProperty('transition-duration', '0s');
|
|
1044
|
-
});
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
1039
|
this.items
|
|
1048
1040
|
.forEach((item) => {
|
|
1049
1041
|
const isDefaultItem = item instanceof PopoverItemDefault;
|
|
@@ -1055,13 +1047,6 @@ export class PopoverDesktop extends PopoverAbstract {
|
|
|
1055
1047
|
item.toggleHidden(isHidden);
|
|
1056
1048
|
});
|
|
1057
1049
|
|
|
1058
|
-
if (isNothingFound) {
|
|
1059
|
-
this.nodes.popoverContainer.offsetHeight;
|
|
1060
|
-
this.items.forEach(item => {
|
|
1061
|
-
item.getElement()?.style.removeProperty('transition-duration');
|
|
1062
|
-
});
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
1050
|
// Reorder top-level DOM elements to reflect ranking
|
|
1066
1051
|
if (!isEmptyQuery && matchingTopLevel.length > 0) {
|
|
1067
1052
|
this.reorderItemsByRank(matchingTopLevel);
|
|
@@ -204,8 +204,7 @@ export class PopoverInline extends PopoverDesktop {
|
|
|
204
204
|
this.nodes.popoverContainer.className = twMerge(
|
|
205
205
|
css.popoverContainer,
|
|
206
206
|
css.popoverContainerOpened,
|
|
207
|
-
cssInline.popoverContainer
|
|
208
|
-
'animate-none'
|
|
207
|
+
cssInline.popoverContainer
|
|
209
208
|
);
|
|
210
209
|
|
|
211
210
|
// Set height based on screen
|
|
@@ -90,7 +90,7 @@ export class PopoverMobile extends PopoverAbstract<PopoverMobileNodes> {
|
|
|
90
90
|
*/
|
|
91
91
|
public show(): void {
|
|
92
92
|
this.nodes.overlay.removeAttribute(DATA_ATTR.overlayHidden);
|
|
93
|
-
this.nodes.overlay.className = twMerge(css.popoverOverlay, 'fixed inset-0 block visible z-3 opacity-50
|
|
93
|
+
this.nodes.overlay.className = twMerge(css.popoverOverlay, 'fixed inset-0 block visible z-3 opacity-50');
|
|
94
94
|
|
|
95
95
|
super.show();
|
|
96
96
|
|
|
@@ -102,7 +102,7 @@ export class PopoverMobile extends PopoverAbstract<PopoverMobileNodes> {
|
|
|
102
102
|
css.popoverContainer,
|
|
103
103
|
css.popoverContainerMobile,
|
|
104
104
|
css.popoverContainerOpened,
|
|
105
|
-
'max-h-none z-4
|
|
105
|
+
'max-h-none z-4'
|
|
106
106
|
);
|
|
107
107
|
|
|
108
108
|
this.scrollLocker.lock();
|
|
@@ -12,7 +12,7 @@ export const css = {
|
|
|
12
12
|
popoverContainerMobile: 'fixed max-w-none rounded-[10px] min-w-[calc(100%-var(--offset)*2)] left-auto top-auto inset-[auto_var(--offset)_calc(var(--offset)+env(safe-area-inset-bottom))_var(--offset)]',
|
|
13
13
|
|
|
14
14
|
// Popover container - opened state
|
|
15
|
-
popoverContainerOpened: 'opacity-100 pointer-events-auto p-1.5 max-h-(--max-height) border-none
|
|
15
|
+
popoverContainerOpened: 'opacity-100 pointer-events-auto p-1.5 max-h-(--max-height) border-none',
|
|
16
16
|
|
|
17
17
|
// Popover overlay
|
|
18
18
|
popoverOverlay: 'hidden bg-dark',
|
|
@@ -7,6 +7,8 @@ import { createEditorContainer, defaultTools, simulateClick, waitForToolbar } fr
|
|
|
7
7
|
import type { EditorFactoryOptions } from './helpers';
|
|
8
8
|
|
|
9
9
|
import type { OutputData, ToolSettings } from '@/types';
|
|
10
|
+
import { GRIP_ACTIVE_CLASSES, GRIP_IDLE_CLASSES, GRIP_VISIBLE_CLASSES } from '../tools/table/table-row-col-controls';
|
|
11
|
+
import { GRIP_HOVER_SIZE } from '../tools/table/table-grip-visuals';
|
|
10
12
|
|
|
11
13
|
// ── Constants ────────────────────────────────────────────────────────
|
|
12
14
|
|
|
@@ -77,20 +79,24 @@ const waitForTable = async (canvas: HTMLElement): Promise<void> => {
|
|
|
77
79
|
const forceGripsVisible = (grips: NodeListOf<Element>): void => {
|
|
78
80
|
grips.forEach((grip) => {
|
|
79
81
|
grip.setAttribute('data-blok-table-grip-visible', '');
|
|
80
|
-
grip.classList.remove(
|
|
81
|
-
grip.classList.add(
|
|
82
|
+
GRIP_IDLE_CLASSES.forEach(cls => grip.classList.remove(cls));
|
|
83
|
+
GRIP_VISIBLE_CLASSES.forEach(cls => grip.classList.add(cls));
|
|
82
84
|
});
|
|
83
85
|
};
|
|
84
86
|
|
|
85
|
-
const forceGripActive = (grip: Element
|
|
87
|
+
const forceGripActive = (grip: Element): void => {
|
|
86
88
|
grip.setAttribute('data-blok-table-grip-visible', '');
|
|
87
|
-
grip.classList.remove(
|
|
88
|
-
grip.classList.add(
|
|
89
|
+
GRIP_IDLE_CLASSES.forEach(cls => grip.classList.remove(cls));
|
|
90
|
+
GRIP_ACTIVE_CLASSES.forEach(cls => grip.classList.add(cls));
|
|
89
91
|
|
|
90
92
|
const el = grip as HTMLElement;
|
|
93
|
+
const isCol = grip.hasAttribute('data-blok-table-grip-col');
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
if (isCol) {
|
|
96
|
+
el.style.height = `${GRIP_HOVER_SIZE}px`;
|
|
97
|
+
} else {
|
|
98
|
+
el.style.width = `${GRIP_HOVER_SIZE}px`;
|
|
99
|
+
}
|
|
94
100
|
|
|
95
101
|
const svg = grip.querySelector('svg');
|
|
96
102
|
|
|
@@ -701,7 +707,7 @@ export const ColumnGripActive: Story = {
|
|
|
701
707
|
const grip = canvasElement.querySelector(GRIP_COL_SELECTOR);
|
|
702
708
|
|
|
703
709
|
if (grip) {
|
|
704
|
-
forceGripActive(grip
|
|
710
|
+
forceGripActive(grip);
|
|
705
711
|
}
|
|
706
712
|
|
|
707
713
|
await waitFor(
|
|
@@ -749,7 +755,7 @@ export const RowGripActive: Story = {
|
|
|
749
755
|
const grip = canvasElement.querySelector(GRIP_ROW_SELECTOR);
|
|
750
756
|
|
|
751
757
|
if (grip) {
|
|
752
|
-
forceGripActive(grip
|
|
758
|
+
forceGripActive(grip);
|
|
753
759
|
}
|
|
754
760
|
|
|
755
761
|
await waitFor(
|