first commit
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
import { registerPanel, PanelId, setPanelVisible, isPanelVisible } from '../../../state/panel-state.js';
|
||||
import { getToolbarTarget } from '../toolbar/toolbar.js';
|
||||
import { wrapSelectionWithStyle } from '../style-actions.js';
|
||||
import { getStylesAtCursor, onSelectionChange } from '../../inline-edit/cursor-styles.js';
|
||||
import { createPanelElement, positionDropdownPanel, createOutsideDismiss, captureSelectionRange } from '../dropdown-panel.js';
|
||||
import { SYSTEM_FONTS, FONT_ACTION_HTML } from './template.js';
|
||||
import { FONT_ACTION_STYLES } from './styles.js';
|
||||
|
||||
let fontActionElement = null;
|
||||
let savedFontRange = null;
|
||||
|
||||
const outsideDismiss = createOutsideDismiss({
|
||||
getElement: () => fontActionElement,
|
||||
dismiss: () => hideFontAction(),
|
||||
});
|
||||
|
||||
function getAvailableFonts() {
|
||||
const fonts = [...SYSTEM_FONTS];
|
||||
const seen = new Set(SYSTEM_FONTS.map(font => font.name.toLowerCase()));
|
||||
document.fonts?.forEach(face => {
|
||||
if (face.status !== 'loaded') return;
|
||||
const family = face.family.replace(/^['"]|['"]$/g, '');
|
||||
const key = family.toLowerCase();
|
||||
if (seen.has(key)) return;
|
||||
seen.add(key);
|
||||
const needsQuotes = /\s/.test(family);
|
||||
const quoted = needsQuotes ? `"${family}"` : family;
|
||||
fonts.push({ name: family, stack: `${quoted}, sans-serif` });
|
||||
});
|
||||
return fonts;
|
||||
}
|
||||
|
||||
function renderFontOptions() {
|
||||
if (!fontActionElement) return;
|
||||
fontActionElement.innerHTML = getAvailableFonts()
|
||||
.map(font => `<div class="tft-font-option" data-font='${font.stack}' style='font-family: ${font.stack}'>${font.name}</div>`)
|
||||
.join('\n ');
|
||||
}
|
||||
|
||||
function initFontAction() {
|
||||
if (fontActionElement) return;
|
||||
fontActionElement = createPanelElement({
|
||||
styleId: 'text-format-font-action-styles',
|
||||
styles: FONT_ACTION_STYLES,
|
||||
html: FONT_ACTION_HTML,
|
||||
});
|
||||
|
||||
fontActionElement.addEventListener('mousedown', (event) => event.preventDefault());
|
||||
|
||||
fontActionElement.addEventListener('click', (event) => {
|
||||
const option = event.target.closest('.tft-font-option');
|
||||
if (!option) return;
|
||||
applyFontAction(option.dataset.font);
|
||||
hideFontAction();
|
||||
});
|
||||
}
|
||||
|
||||
function applyFontAction(family) {
|
||||
if (!savedFontRange || !getToolbarTarget() || !family) return;
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(savedFontRange);
|
||||
wrapSelectionWithStyle(getToolbarTarget(), 'fontFamily', 'font-family', family);
|
||||
onSelectionChange();
|
||||
}
|
||||
|
||||
export function positionFontAction() {
|
||||
if (!isPanelVisible(PanelId.FONT) || !fontActionElement) return;
|
||||
positionDropdownPanel(fontActionElement, { buttonAction: 'font-family' });
|
||||
}
|
||||
|
||||
/** Opens the font family panel with the current family highlighted. */
|
||||
export function showFontAction() {
|
||||
if (!fontActionElement) initFontAction();
|
||||
renderFontOptions();
|
||||
|
||||
savedFontRange = captureSelectionRange();
|
||||
|
||||
const styles = getStylesAtCursor();
|
||||
const currentFamily = styles?.fontFamily
|
||||
?? (getToolbarTarget() ? getComputedStyle(getToolbarTarget()).fontFamily : '');
|
||||
const normalizedCurrent = currentFamily.split(',')[0].replace(/['"]/g, '').trim().toLowerCase();
|
||||
fontActionElement.querySelectorAll('.tft-font-option').forEach(option => {
|
||||
const optionName = option.dataset.font.split(',')[0].replace(/['"]/g, '').trim().toLowerCase();
|
||||
option.classList.toggle('tfa-active', optionName === normalizedCurrent);
|
||||
});
|
||||
|
||||
fontActionElement.classList.add('tfa-visible');
|
||||
setPanelVisible(PanelId.FONT, true);
|
||||
positionFontAction();
|
||||
|
||||
outsideDismiss.attach();
|
||||
}
|
||||
|
||||
/** Closes the font family panel. */
|
||||
export function hideFontAction() {
|
||||
if (!fontActionElement) return;
|
||||
fontActionElement.classList.remove('tfa-visible');
|
||||
setPanelVisible(PanelId.FONT, false);
|
||||
savedFontRange = null;
|
||||
outsideDismiss.detach();
|
||||
}
|
||||
|
||||
/** Toggles the font family panel visibility. */
|
||||
export function toggleFontAction() {
|
||||
if (isPanelVisible(PanelId.FONT)) {
|
||||
hideFontAction();
|
||||
} else {
|
||||
showFontAction();
|
||||
}
|
||||
}
|
||||
|
||||
registerPanel({ id: PanelId.FONT, hide: hideFontAction });
|
||||
@@ -0,0 +1,41 @@
|
||||
import { FONT_ACTION_ID } from './template.js';
|
||||
import { PANEL_BG, BORDER_COLOR, HOVER_BG, ACTIVE_BG, BOX_SHADOW_DROPDOWN, COLOR_WHITE, Z_INDEX_EDITOR_PANEL } from '../../../constants/theme.js';
|
||||
|
||||
/** Injected CSS for the font family dropdown panel. */
|
||||
export const FONT_ACTION_STYLES = `
|
||||
#${FONT_ACTION_ID} {
|
||||
position: fixed;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
max-height: 280px;
|
||||
background: ${PANEL_BG};
|
||||
border: 1px solid ${BORDER_COLOR};
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
box-shadow: ${BOX_SHADOW_DROPDOWN};
|
||||
user-select: none;
|
||||
z-index: ${Z_INDEX_EDITOR_PANEL};
|
||||
}
|
||||
|
||||
#${FONT_ACTION_ID}.tfa-visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tft-font-option {
|
||||
padding: 4px 10px;
|
||||
color: ${COLOR_WHITE};
|
||||
font-size: 14px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tft-font-option:hover {
|
||||
background: ${HOVER_BG};
|
||||
}
|
||||
|
||||
.tft-font-option.tfa-active {
|
||||
background: ${ACTIVE_BG};
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,16 @@
|
||||
/** DOM id for the font family action panel root element. */
|
||||
export const FONT_ACTION_ID = 'text-format-font-action';
|
||||
|
||||
/** System font presets shown in the font family dropdown. */
|
||||
export const SYSTEM_FONTS = [
|
||||
{ name: 'Arial', stack: 'Arial, sans-serif' },
|
||||
{ name: 'Helvetica', stack: 'Helvetica, sans-serif' },
|
||||
{ name: 'Verdana', stack: 'Verdana, sans-serif' },
|
||||
{ name: 'Trebuchet MS', stack: '"Trebuchet MS", sans-serif' },
|
||||
{ name: 'Georgia', stack: 'Georgia, serif' },
|
||||
{ name: 'Times New Roman', stack: '"Times New Roman", serif' },
|
||||
{ name: 'Courier New', stack: '"Courier New", monospace' },
|
||||
];
|
||||
|
||||
/** HTML scaffold for the font family action panel. */
|
||||
export const FONT_ACTION_HTML = `<div id="${FONT_ACTION_ID}"></div>`;
|
||||
Reference in New Issue
Block a user