/
45case
/
ptable
Обзор
Документация
Войти
/
45case
/
ptable
Код
Запросы
3
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
dev
src/entities/table/ui/TableGrid.vue
2 259 строк
68 KB
Olya
Отредактирована надпись и блоки электронов
24 июл 2026, 12:53
24 июл 2026, 12:53
67e6930
Код
Авторство
О чём код?
<script setup lang="ts"> import { computed, onBeforeUnmount, onMounted, ref, watch, type Ref } from "vue"; import { useRoute, useRouter } from "vue-router"; import TableLegend from "./TableLegend.vue"; import { getElements, getElementBySlug, searchElements } from "../../element/api/elements"; import type { PeriodicElement, ElementPhaseAtStp } from "../../element/model/elements"; import type { ElementCategory } from "../../element/model/category"; import { getBigencArticleUrl, formatElectronsPerShellCardDisplay, formatElectronAffinity, formatAbundance, formatConductivity, formatDensity, formatDiscoveredYear, formatHeat, formatKelvinTemperature, formatMolarEnergy, formatPropertyNumber, type LegendArticleRef, } from "../../element/lib/formatHomeProperties"; import { abundanceSchemeDefaults, atomicMassSchemeDefaults, atomicRadiusSchemeDefaults, conductivitySchemeDefaults, densitySchemeDefaults, electronAffinitySchemeDefaults, electronegativitySchemeDefaults, energyLevelsSchemeDefaults, hardnessSchemeDefaults, heatSchemeDefaults, ionizationEnergySchemeDefaults, meltingPointSchemeDefaults, modulusSchemeDefaults, getOuterShellElectronCount, getPropertySchemeColor, type PropertyScaleKind, } from "../../element/lib/propertyColorScale"; import type { HomePropertyKey } from "../../element/model/homeProperties"; import { getHomePropertyHelp } from "../../element/model/homeProperties"; import { getAbundanceValue, getAtomicRadiusPm, getConductivityValue, getDensityKgM3, getElementPropertiesByAtomicNumber, getHardnessValue, getHeatValue, getIonizationEnergyKJmol, getModulusGPa, } from "../../element/api/getElementProperties"; import { useTheme } from "@/shared/lib/useTheme"; import ElementCard from "../../element/ui/ElementCard.vue"; import { TEMPERATURE_MAX_K, useTemperatureStore } from "../../temperature"; import { getExtendedPeriodicTableMatrix, getPeriodicTableMatrix, } from "../lib/getPeriodicTableMatrix"; import { hasCategoryLegendSelection, isElementCategoryHighlighted, isElementCategorySelected, type CategoryLegendGroupKey, type CategoryLegendHover, } from "../model/categoryLegend"; import { type LegendFilter, unstableIsotopeMassNote } from "../model/legend"; import { getLegendCategoryArticle, getLegendGroupArticle } from "../model/legendArticles"; import { useTableView } from "@/shared/lib/useTableView"; import { formatBlockLabel, formatElectronConfigurationForCard, formatExpandedConfigurationForCard, formatElectronsPerShellForCard, formatOxidationStatesForCard, formatQuantumNumbersForCard, getElectronPropertiesByAtomicNumber, getElectronPropertyHelp, getElementBlock, getHoaoElectronCount, isMadelungException, madelungExceptionSchemeDefaults, usesElectronBlockColors, usesElectronConfigurationHeatmap, usesElectronExpandedMadelungColors, type ElectronBlock, type ElectronPropertyKey, } from "@/entities/electrons"; /** Footnote under the Electrons table (replaces isotope mass note). */ const electronsOxidationFootnote = "Степень окисления — это число электронов, присоединенных к элементу или отнятых у него при образовании химического соединения."; const props = defineProps<{ searchQuery?: string; /** Electrons page: block colors + block legend instead of phases. Home stays default. */ variant?: "home" | "electrons"; /** Home: which property is selected in the detail panel (controls card footers). */ selectedHomeProperty?: HomePropertyKey; /** Electrons: which property is selected (controls coloring + card footers). */ selectedElectronProperty?: ElectronPropertyKey; /** Swipe transition name when switching Properties ↔ Electrons legend. */ legendSwipe?: string; }>(); const isElectronsVariant = computed(() => props.variant === "electrons"); const electronProperty = computed( () => props.selectedElectronProperty ?? "oxidation", ); const usesBlockColors = computed( () => isElectronsVariant.value && usesElectronBlockColors(electronProperty.value), ); const isElectronConfigurationHeatmap = computed( () => isElectronsVariant.value && usesElectronConfigurationHeatmap(electronProperty.value), ); const isElectronExpandedMadelung = computed( () => isElectronsVariant.value && usesElectronExpandedMadelungColors(electronProperty.value), ); const isElectronConfigurationSelected = computed( () => isElectronsVariant.value && electronProperty.value === "configuration", ); const isElectronExpandedSelected = computed( () => isElectronsVariant.value && electronProperty.value === "expanded", ); const isElectronEnergySelected = computed( () => isElectronsVariant.value && electronProperty.value === "energy", ); const isElectronHoaoSelected = computed( () => isElectronsVariant.value && electronProperty.value === "hoao", ); const isArticlePropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "article", ); const isAtomicMassPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "atomicMass", ); const isEnergyLevelsPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "energyLevels", ); const isElectronegativityPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "electronegativity", ); const isMeltingPointPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "meltingPoint", ); const isBoilingPointPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "boilingPoint", ); const isElectronAffinityPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "electronAffinity", ); const isIonizationEnergyPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "ionizationEnergy", ); const isRadiusPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "radius", ); const isHardnessPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "hardness", ); const isModulusPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "modulus", ); const isDensityPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "density", ); const isConductivityPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "conductivity", ); const isHeatPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "heat", ); const isAbundancePropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "abundance", ); const isDiscoveredPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "discovered", ); const isMeltBoilPropertySelected = computed( () => isMeltingPointPropertySelected.value || isBoilingPointPropertySelected.value, ); const isNumericPropertyHeatmapSelected = computed( () => isAtomicMassPropertySelected.value || isEnergyLevelsPropertySelected.value || isElectronegativityPropertySelected.value || isMeltBoilPropertySelected.value || isElectronAffinityPropertySelected.value || isIonizationEnergyPropertySelected.value || isRadiusPropertySelected.value || isHardnessPropertySelected.value || isModulusPropertySelected.value || isDensityPropertySelected.value || isConductivityPropertySelected.value || isHeatPropertySelected.value || isAbundancePropertySelected.value || isElectronConfigurationHeatmap.value, ); const isStateAtPropertySelected = computed( () => !isElectronsVariant.value && props.selectedHomeProperty === "stateAt", ); const hideCategoryLegend = computed( () => isNumericPropertyHeatmapSelected.value || isStateAtPropertySelected.value, ); const leftLegendMode = computed<"phases" | "blocks" | "property" | "none">(() => { if (isElectronsVariant.value) { if (isElectronConfigurationHeatmap.value) { return "property"; } // Keep ElectronKey (blocks + ConfigBlocks) for oxidation / expanded / energy / HOAO. if (usesBlockColors.value) { return "blocks"; } return "none"; } if (isNumericPropertyHeatmapSelected.value) { return "property"; } if (isDiscoveredPropertySelected.value) { return "none"; } return "phases"; }); const tableHomeProperty = computed(() => { if (isElectronsVariant.value) { return undefined; } return props.selectedHomeProperty ?? "series"; }); const tableFootnote = computed(() => { if (isElectronsVariant.value) { return getElectronPropertyHelp(electronProperty.value) ?? electronsOxidationFootnote; } const propertyHelp = getHomePropertyHelp(props.selectedHomeProperty); return propertyHelp ?? unstableIsotopeMassNote; }); const emit = defineEmits<{ select: [element: PeriodicElement | null]; "open-article": [article: LegendArticleRef]; }>(); const router = useRouter(); const route = useRoute(); const compactMatrix = getPeriodicTableMatrix(); const extendedMatrix = getExtendedPeriodicTableMatrix(); const { isTableExtended: isExtended, toggleTableExtended: toggleExtended } = useTableView(); const matrix = computed(() => (isExtended.value ? extendedMatrix : compactMatrix)); const columnCount = computed(() => (isExtended.value ? 32 : 18)); const groupLabels = computed(() => { if (!isExtended.value) { return Array.from({ length: 18 }, (_, index) => index + 1); } return Array.from({ length: 32 }, (_, index) => { const column = index + 1; if (column <= 2) { return column; } if (column >= 17) { return column - 14; } return null; }); }); const periodLabels = computed(() => { if (isExtended.value) { return matrix.value.map((_, index) => (index < 7 ? index + 1 : null)); } return Array.from({ length: matrix.value.length + 1 }, (_, index) => (index < 7 ? index + 1 : null)); }); const allElements = getElements(); const { theme } = useTheme(); const { currentTemperatureK, elementStates, isStatePreviewActive, temperatureUnit, molarEnergyUnit, ionizationOrder, atomicRadiusKind, hardnessKind, modulusKind, densityKind, densityUnit, conductivityKind, heatKind, abundanceKind, discoveryYear, } = useTemperatureStore(); /** Ptable melt/boil legend: zero stop at Kelvin/maxKelvin * 100%. */ const meltBoilZeroStopPercent = computed(() => { if (TEMPERATURE_MAX_K <= 0) { return 50; } return (currentTemperatureK.value / TEMPERATURE_MAX_K) * 100; }); function getActivePropertySchemeDefaults() { const isDark = theme.value === "dark"; if (isEnergyLevelsPropertySelected.value || isElectronConfigurationHeatmap.value) { // Configuration (`electronstring`) and Energy levels share `--electrons-*` on ptable. return isDark ? energyLevelsSchemeDefaults.dark : energyLevelsSchemeDefaults.light; } if (isElectronegativityPropertySelected.value) { return isDark ? electronegativitySchemeDefaults.dark : electronegativitySchemeDefaults.light; } if (isElectronAffinityPropertySelected.value) { return isDark ? electronAffinitySchemeDefaults.dark : electronAffinitySchemeDefaults.light; } if (isIonizationEnergyPropertySelected.value) { return isDark ? ionizationEnergySchemeDefaults.dark : ionizationEnergySchemeDefaults.light; } if (isRadiusPropertySelected.value) { return isDark ? atomicRadiusSchemeDefaults.dark : atomicRadiusSchemeDefaults.light; } if (isHardnessPropertySelected.value) { return isDark ? hardnessSchemeDefaults.dark : hardnessSchemeDefaults.light; } if (isModulusPropertySelected.value) { return isDark ? modulusSchemeDefaults.dark : modulusSchemeDefaults.light; } if (isDensityPropertySelected.value) { return isDark ? densitySchemeDefaults.dark : densitySchemeDefaults.light; } if (isConductivityPropertySelected.value) { return isDark ? conductivitySchemeDefaults.dark : conductivitySchemeDefaults.light; } if (isHeatPropertySelected.value) { const base = isDark ? heatSchemeDefaults.dark : heatSchemeDefaults.light; return { ...base, scale: heatKind.value === "specific" ? ("log" as const) : ("lin" as const), }; } if (isAbundancePropertySelected.value) { return isDark ? abundanceSchemeDefaults.dark : abundanceSchemeDefaults.light; } if (isMeltBoilPropertySelected.value) { return isDark ? meltingPointSchemeDefaults.dark : meltingPointSchemeDefaults.light; } return isDark ? atomicMassSchemeDefaults.dark : atomicMassSchemeDefaults.light; } function getSchemeZeroColor( defaults: ReturnType<typeof getActivePropertySchemeDefaults>, ): string | undefined { return "zero" in defaults ? (defaults as { zero?: string }).zero : undefined; } const propertyStartColor: Ref<string> = ref(String(getActivePropertySchemeDefaults().start)); const propertyEndColor: Ref<string> = ref(String(getActivePropertySchemeDefaults().end)); const propertyUnknownColor: Ref<string> = ref(String(getActivePropertySchemeDefaults().unknown)); const propertyZeroColor: Ref<string> = ref( String(getSchemeZeroColor(getActivePropertySchemeDefaults()) ?? "#ffffff"), ); const propertyScale = ref<PropertyScaleKind>(getActivePropertySchemeDefaults().scale); function resetPropertySchemeToThemeDefaults() { const defaults = getActivePropertySchemeDefaults(); propertyStartColor.value = defaults.start; propertyEndColor.value = defaults.end; propertyUnknownColor.value = defaults.unknown; propertyScale.value = defaults.scale; const zero = getSchemeZeroColor(defaults); if (zero) { propertyZeroColor.value = zero; } } function setPropertyStartColor(value: string) { propertyStartColor.value = value; } function setPropertyEndColor(value: string) { propertyEndColor.value = value; } function setPropertyUnknownColor(value: string) { propertyUnknownColor.value = value; } function setPropertyZeroColor(value: string) { propertyZeroColor.value = value; } function setPropertyScale(value: PropertyScaleKind) { propertyScale.value = value; } watch(theme, () => { if (isNumericPropertyHeatmapSelected.value) { resetPropertySchemeToThemeDefaults(); } }); watch(isNumericPropertyHeatmapSelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isEnergyLevelsPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isElectronConfigurationHeatmap, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isAtomicMassPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isElectronegativityPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isMeltingPointPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isBoilingPointPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isElectronAffinityPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isIonizationEnergyPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isRadiusPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isHardnessPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isModulusPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isDensityPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isConductivityPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(isHeatPropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); watch(heatKind, () => { if (isHeatPropertySelected.value) { resetPropertySchemeToThemeDefaults(); } }); watch(isAbundancePropertySelected, (selected) => { if (selected) { resetPropertySchemeToThemeDefaults(); } }); const atomicMassValues = computed(() => allElements.map((element) => element.atomicMass)); const energyLevelsValues = computed(() => allElements.map((element) => { const shells = getElementPropertiesByAtomicNumber(element.atomicNumber)?.electronsPerShell; return getOuterShellElectronCount(shells); }), ); /** Electrons in HOAO subshell — ptable Configuration (`electronstring`) coloring. */ const hoaoElectronCountValues = computed(() => allElements.map((element) => { const data = getElectronPropertiesByAtomicNumber(element.atomicNumber); return data ? getHoaoElectronCount(data) : null; }), ); const electronegativityValues = computed(() => allElements.map( (element) => getElementPropertiesByAtomicNumber(element.atomicNumber)?.electronegativityPauling ?? null, ), ); const meltingPointValues = computed(() => allElements.map((element) => { const physical = getElementPropertiesByAtomicNumber(element.atomicNumber); return physical?.meltingPointKelvin ?? element.meltingPointKelvin ?? null; }), ); const boilingPointValues = computed(() => allElements.map((element) => { const physical = getElementPropertiesByAtomicNumber(element.atomicNumber); return physical?.boilingPointKelvin ?? element.boilingPointKelvin ?? null; }), ); const electronAffinityValues = computed(() => allElements.map( (element) => getElementPropertiesByAtomicNumber(element.atomicNumber)?.electronAffinityKJmol ?? null, ), ); const ionizationEnergyValues = computed(() => allElements.map((element) => getIonizationEnergyKJmol( getElementPropertiesByAtomicNumber(element.atomicNumber), ionizationOrder.value, ), ), ); const atomicRadiusValues = computed(() => allElements.map((element) => getAtomicRadiusPm( getElementPropertiesByAtomicNumber(element.atomicNumber), atomicRadiusKind.value, ), ), ); const hardnessValues = computed(() => allElements.map((element) => getHardnessValue( getElementPropertiesByAtomicNumber(element.atomicNumber), hardnessKind.value, ), ), ); const modulusValues = computed(() => allElements.map((element) => getModulusGPa(getElementPropertiesByAtomicNumber(element.atomicNumber), modulusKind.value), ), ); const densityValues = computed(() => allElements.map((element) => getDensityKgM3(getElementPropertiesByAtomicNumber(element.atomicNumber), densityKind.value), ), ); const conductivityValues = computed(() => allElements.map((element) => getConductivityValue( getElementPropertiesByAtomicNumber(element.atomicNumber), conductivityKind.value, ), ), ); const heatValues = computed(() => allElements.map((element) => getHeatValue(getElementPropertiesByAtomicNumber(element.atomicNumber), heatKind.value), ), ); const abundanceValues = computed(() => allElements.map((element) => getAbundanceValue( getElementPropertiesByAtomicNumber(element.atomicNumber), abundanceKind.value, ), ), ); const atomicMassColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = atomicMassValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (const element of allElements) { colors.set(element.atomicNumber, getPropertySchemeColor(element.atomicMass, values, scheme)); } return colors; }); const energyLevelsColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = energyLevelsValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const hoaoElectronCountColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = hoaoElectronCountValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); /** Expanded: only Madelung exceptions override block colors with `--gas`. */ const expandedMadelungColorByAtomicNumber = computed(() => { const colors = new Map<number, { background: string; ink: string }>(); const isDark = theme.value === "dark"; const scheme = isDark ? madelungExceptionSchemeDefaults.dark : madelungExceptionSchemeDefaults.light; for (const element of allElements) { const data = getElectronPropertiesByAtomicNumber(element.atomicNumber); if (!isMadelungException(data?.expandedConfiguration)) { continue; } colors.set(element.atomicNumber, { background: scheme.exception, ink: scheme.ink, }); } return colors; }); const electronegativityColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = electronegativityValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const meltingPointColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = meltingPointValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, zero: propertyZeroColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; const zeroPoint = currentTemperatureK.value; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme, { zeroPoint }), ); } return colors; }); const boilingPointColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = boilingPointValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, zero: propertyZeroColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; const zeroPoint = currentTemperatureK.value; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme, { zeroPoint }), ); } return colors; }); const electronAffinityColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = electronAffinityValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const ionizationEnergyColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = ionizationEnergyValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const atomicRadiusColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = atomicRadiusValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const hardnessColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = hardnessValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const modulusColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = modulusValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const densityColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = densityValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const conductivityColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = conductivityValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const heatColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = heatValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); const abundanceColorByAtomicNumber = computed(() => { const colors = new Map<number, string>(); const values = abundanceValues.value; const scheme = { start: propertyStartColor.value, end: propertyEndColor.value, unknown: propertyUnknownColor.value, scale: propertyScale.value, }; for (let index = 0; index < allElements.length; index += 1) { const element = allElements[index]!; colors.set( element.atomicNumber, getPropertySchemeColor(values[index], values, scheme), ); } return colors; }); function getCompactCell(element: PeriodicElement) { if (isElectronsVariant.value && element.atomicNumber === 2) { // He beside H (s-block), like ptable Electrons / Blocks layout return { row: 1, column: 2 }; } const columnOffset = element.group === null ? 1 : 0; const rowOffset = element.row >= 8 ? 1 : 0; return { row: element.row + rowOffset, column: element.column + columnOffset }; } function getExtendedCell(element: PeriodicElement) { if (isElectronsVariant.value && element.atomicNumber === 2) { return { row: 1, column: 2 }; } if (element.group === null) { return { row: element.period, column: element.column }; } const column = element.group <= 2 ? element.group : element.group + 14; return { row: element.period, column }; } const placedElements = computed(() => allElements.map((element) => { const cell = isExtended.value ? getExtendedCell(element) : getCompactCell(element); return { element, row: cell.row, column: cell.column }; }), ); const selectedAtomicNumber = computed(() => { const slug = route.query.slug; if (typeof slug !== "string") { return null; } return getElementBySlug(slug)?.atomicNumber ?? null; }); const categoryLegendHover = ref<CategoryLegendHover | null>(null); const selectedCategories = ref<Set<ElementCategory>>(new Set()); const selectedGroups = ref<Set<CategoryLegendGroupKey>>(new Set()); const selectedPhases = ref<Set<ElementPhaseAtStp>>(new Set()); const hoveredPhaseFilter = ref<LegendFilter | null>(null); const hoveredBlock = ref<ElectronBlock | null>(null); const selectedBlocks = ref<Set<ElectronBlock>>(new Set()); const keyboardSearchEnabled = ref(false); const keyboardSearchQuery = ref(""); const activeKeyboardSearchQuery = computed(() => keyboardSearchQuery.value.trim()); const keyboardSearchMatches = computed(() => getKeyboardSearchMatches(activeKeyboardSearchQuery.value), ); const keyboardSearchAtomicNumbers = computed( () => new Set(keyboardSearchMatches.value.map((element) => element.atomicNumber)), ); const hasKeyboardSearch = computed(() => activeKeyboardSearchQuery.value.length > 0); const navbarSearchMatches = computed(() => getKeyboardSearchMatches(props.searchQuery ?? ""), ); const navbarSearchAtomicNumbers = computed( () => new Set(navbarSearchMatches.value.map((element) => element.atomicNumber)), ); const hasNavbarSearch = computed(() => (props.searchQuery ?? "").trim().length > 0); const activePhaseFilters = computed<Set<ElementPhaseAtStp> | null>(() => { if (isDiscoveredPropertySelected.value || isNumericPropertyHeatmapSelected.value) { return null; } if (selectedPhases.value.size > 0) { return selectedPhases.value; } if (hoveredPhaseFilter.value?.kind === "phase") { return new Set([hoveredPhaseFilter.value.key]); } return null; }); const hasActiveElementFilter = computed( () => activePhaseFilters.value !== null || hasKeyboardSearch.value || hasNavbarSearch.value, ); function toggleBlockFilter(block: ElectronBlock) { categoryLegendHover.value = null; const next = new Set(selectedBlocks.value); if (next.has(block)) { next.delete(block); } else { next.add(block); } selectedBlocks.value = next; } function previewBlockFilter(block: ElectronBlock | null) { categoryLegendHover.value = null; if (selectedBlocks.value.size > 0) { return; } hoveredBlock.value = block; } function isBlockFilterActive() { return selectedBlocks.value.size > 0 || hoveredBlock.value !== null; } function matchesActiveBlocks(element: PeriodicElement) { const block = getElementBlock(element.atomicNumber); if (selectedBlocks.value.size > 0) { return selectedBlocks.value.has(block); } if (hoveredBlock.value) { return block === hoveredBlock.value; } return true; } function isBlockHighlighted(element: PeriodicElement) { return usesBlockColors.value && isBlockFilterActive() && matchesActiveBlocks(element); } function isBlockDimmed(element: PeriodicElement) { return usesBlockColors.value && isBlockFilterActive() && !matchesActiveBlocks(element); } function getElectronCardTitle(element: PeriodicElement) { const block = getElementBlock(element.atomicNumber); const parts = [element.name, element.symbol, formatBlockLabel(block)]; const value = getElectronCardValueText(element); if (value) { parts.push(value); } return parts.join(" · "); } function getElectronOxidationText(element: PeriodicElement) { const propsForElement = getElectronPropertiesByAtomicNumber(element.atomicNumber); return formatOxidationStatesForCard(propsForElement?.oxidationStates ?? []); } function getElectronCardValueText(element: PeriodicElement): string | undefined { const data = getElectronPropertiesByAtomicNumber(element.atomicNumber); if (isElectronConfigurationSelected.value) { return formatElectronConfigurationForCard(data?.electronConfiguration); } if (isElectronExpandedSelected.value) { return formatExpandedConfigurationForCard(data?.expandedConfiguration); } if (isElectronEnergySelected.value) { return formatElectronsPerShellForCard(data?.electronsPerShell); } if (isElectronHoaoSelected.value) { return formatQuantumNumbersForCard(data?.quantum); } // Default / oxidation return getElectronOxidationText(element); } function getCardValueText(element: PeriodicElement): string | undefined { if (isElectronsVariant.value) { return getElectronCardValueText(element); } if (isArticlePropertySelected.value) { return "Энциклопедия"; } if (isEnergyLevelsPropertySelected.value) { const shells = getElementPropertiesByAtomicNumber(element.atomicNumber)?.electronsPerShell; return formatElectronsPerShellCardDisplay(shells); } if (isElectronegativityPropertySelected.value) { const value = getElementPropertiesByAtomicNumber(element.atomicNumber)?.electronegativityPauling ?? null; return formatPropertyNumber(value, { maxDigits: 2 }); } if (isMeltingPointPropertySelected.value) { const physical = getElementPropertiesByAtomicNumber(element.atomicNumber); const valueK = physical?.meltingPointKelvin ?? element.meltingPointKelvin ?? null; return formatKelvinTemperature(valueK, temperatureUnit.value); } if (isBoilingPointPropertySelected.value) { const physical = getElementPropertiesByAtomicNumber(element.atomicNumber); const valueK = physical?.boilingPointKelvin ?? element.boilingPointKelvin ?? null; return formatKelvinTemperature(valueK, temperatureUnit.value); } if (isElectronAffinityPropertySelected.value) { const value = getElementPropertiesByAtomicNumber(element.atomicNumber)?.electronAffinityKJmol ?? null; return formatElectronAffinity(value, molarEnergyUnit.value, { includeUnit: false }); } if (isIonizationEnergyPropertySelected.value) { const value = getIonizationEnergyKJmol( getElementPropertiesByAtomicNumber(element.atomicNumber), ionizationOrder.value, ); return formatMolarEnergy(value, molarEnergyUnit.value, { includeUnit: false }); } if (isRadiusPropertySelected.value) { const value = getAtomicRadiusPm( getElementPropertiesByAtomicNumber(element.atomicNumber), atomicRadiusKind.value, ); return formatPropertyNumber(value, { digits: 0 }); } if (isHardnessPropertySelected.value) { const value = getHardnessValue( getElementPropertiesByAtomicNumber(element.atomicNumber), hardnessKind.value, ); return formatPropertyNumber(value, { maxDigits: 2 }); } if (isModulusPropertySelected.value) { const value = getModulusGPa( getElementPropertiesByAtomicNumber(element.atomicNumber), modulusKind.value, ); return formatPropertyNumber(value, { maxDigits: 2 }); } if (isDensityPropertySelected.value) { const value = getDensityKgM3( getElementPropertiesByAtomicNumber(element.atomicNumber), densityKind.value, ); return formatDensity(value, densityUnit.value, { includeUnit: false }); } if (isConductivityPropertySelected.value) { const value = getConductivityValue( getElementPropertiesByAtomicNumber(element.atomicNumber), conductivityKind.value, ); return formatConductivity(value, conductivityKind.value, { includeUnit: false }); } if (isHeatPropertySelected.value) { const value = getHeatValue( getElementPropertiesByAtomicNumber(element.atomicNumber), heatKind.value, ); return formatHeat(value, heatKind.value, molarEnergyUnit.value, { includeUnit: false }); } if (isAbundancePropertySelected.value) { const value = getAbundanceValue( getElementPropertiesByAtomicNumber(element.atomicNumber), abundanceKind.value, ); return formatAbundance(value, { includeUnit: false }); } if (isDiscoveredPropertySelected.value) { const value = getElementPropertiesByAtomicNumber(element.atomicNumber)?.discoveredYear ?? null; return formatDiscoveredYear(value); } return undefined; } function getCardSurfaceColor(element: PeriodicElement): string | undefined { if (isElectronExpandedMadelung.value) { return expandedMadelungColorByAtomicNumber.value.get(element.atomicNumber)?.background; } if (isElectronConfigurationHeatmap.value) { return hoaoElectronCountColorByAtomicNumber.value.get(element.atomicNumber); } if (isAtomicMassPropertySelected.value) { return atomicMassColorByAtomicNumber.value.get(element.atomicNumber); } if (isEnergyLevelsPropertySelected.value) { return energyLevelsColorByAtomicNumber.value.get(element.atomicNumber); } if (isElectronegativityPropertySelected.value) { return electronegativityColorByAtomicNumber.value.get(element.atomicNumber); } if (isMeltingPointPropertySelected.value) { return meltingPointColorByAtomicNumber.value.get(element.atomicNumber); } if (isBoilingPointPropertySelected.value) { return boilingPointColorByAtomicNumber.value.get(element.atomicNumber); } if (isElectronAffinityPropertySelected.value) { return electronAffinityColorByAtomicNumber.value.get(element.atomicNumber); } if (isIonizationEnergyPropertySelected.value) { return ionizationEnergyColorByAtomicNumber.value.get(element.atomicNumber); } if (isRadiusPropertySelected.value) { return atomicRadiusColorByAtomicNumber.value.get(element.atomicNumber); } if (isHardnessPropertySelected.value) { return hardnessColorByAtomicNumber.value.get(element.atomicNumber); } if (isModulusPropertySelected.value) { return modulusColorByAtomicNumber.value.get(element.atomicNumber); } if (isDensityPropertySelected.value) { return densityColorByAtomicNumber.value.get(element.atomicNumber); } if (isConductivityPropertySelected.value) { return conductivityColorByAtomicNumber.value.get(element.atomicNumber); } if (isHeatPropertySelected.value) { return heatColorByAtomicNumber.value.get(element.atomicNumber); } if (isAbundancePropertySelected.value) { return abundanceColorByAtomicNumber.value.get(element.atomicNumber); } return undefined; } function getCardSurfaceInk(element: PeriodicElement): string | undefined { if (isElectronExpandedMadelung.value) { return expandedMadelungColorByAtomicNumber.value.get(element.atomicNumber)?.ink; } return undefined; } function getCardValueHref(element: PeriodicElement): string | undefined { if (isArticlePropertySelected.value) { return getBigencArticleUrl(element.name); } return undefined; } function togglePhaseFilter(phase: ElementPhaseAtStp) { categoryLegendHover.value = null; const next = new Set(selectedPhases.value); if (next.has(phase)) { next.delete(phase); } else { next.add(phase); } selectedPhases.value = next; } function toggleCategorySelection(category: ElementCategory) { if (isElectronsVariant.value) { return; } if (isArticlePropertySelected.value) { const article = getLegendCategoryArticle(category); if (article) { emit("open-article", article); return; } } categoryLegendHover.value = null; const next = new Set(selectedCategories.value); if (next.has(category)) { next.delete(category); } else { next.add(category); } selectedCategories.value = next; } function toggleCategoryGroup(group: CategoryLegendGroupKey) { if (isElectronsVariant.value) { return; } if (isArticlePropertySelected.value) { const article = getLegendGroupArticle(group); if (article) { emit("open-article", article); return; } } categoryLegendHover.value = null; const next = new Set(selectedGroups.value); if (next.has(group)) { next.delete(group); } else { next.add(group); } selectedGroups.value = next; } function previewPhaseFilter(filter: LegendFilter | null) { categoryLegendHover.value = null; if (selectedPhases.value.size > 0) { return; } hoveredPhaseFilter.value = filter?.kind === "phase" ? filter : null; } function setCategoryLegendHover(hover: CategoryLegendHover | null) { if (isElectronsVariant.value) { return; } hoveredPhaseFilter.value = null; if (hasCategoryLegendSelection(selectedCategories.value, selectedGroups.value)) { return; } categoryLegendHover.value = hover; } function isCategoryFilterActive() { if ( isElectronsVariant.value || isStateAtPropertySelected.value || isNumericPropertyHeatmapSelected.value ) { return false; } return ( hasCategoryLegendSelection(selectedCategories.value, selectedGroups.value) || categoryLegendHover.value !== null ); } function isCategoryHighlighted(element: PeriodicElement) { if (isStateAtPropertySelected.value || isNumericPropertyHeatmapSelected.value) { return false; } if (hasCategoryLegendSelection(selectedCategories.value, selectedGroups.value)) { return isElementCategorySelected( element.category, selectedCategories.value, selectedGroups.value, ); } return isElementCategoryHighlighted(element.category, categoryLegendHover.value); } function isCategoryDimmed(element: PeriodicElement) { return isCategoryFilterActive() && !isCategoryHighlighted(element); } function getElementCardState(element: PeriodicElement) { if ( isElectronsVariant.value || isNumericPropertyHeatmapSelected.value || isDiscoveredPropertySelected.value ) { return undefined; } const state = getElementState(element); if (isStateAtPropertySelected.value) { return state; } const hasCategorySelection = hasCategoryLegendSelection( selectedCategories.value, selectedGroups.value, ); const hasPinnedPhaseFilter = selectedPhases.value.size > 0; if (isCategoryHighlighted(element) && hasCategorySelection) { if (hasPinnedPhaseFilter && selectedPhases.value.has(state)) { return state; } if (!hasPinnedPhaseFilter && isStatePreviewActive.value) { return state; } return undefined; } if (isCategoryHighlighted(element)) { return undefined; } if (hasPinnedPhaseFilter && selectedPhases.value.has(state)) { return state; } if (isStatePreviewActive.value) { return state; } if (activePhaseFilters.value && matchesActivePhases(element)) { return state; } return undefined; } const elementCardStateByAtomicNumber = computed(() => { void currentTemperatureK.value; void isStatePreviewActive.value; void isAtomicMassPropertySelected.value; void isEnergyLevelsPropertySelected.value; void isElectronConfigurationHeatmap.value; void isElectronExpandedMadelung.value; void isElectronegativityPropertySelected.value; void isMeltingPointPropertySelected.value; void isBoilingPointPropertySelected.value; void isElectronAffinityPropertySelected.value; void isIonizationEnergyPropertySelected.value; void isRadiusPropertySelected.value; void isHardnessPropertySelected.value; void isModulusPropertySelected.value; void isDensityPropertySelected.value; void isConductivityPropertySelected.value; void isStateAtPropertySelected.value; void isArticlePropertySelected.value; void isDiscoveredPropertySelected.value; void isHeatPropertySelected.value; void isAbundancePropertySelected.value; const states = new Map<number, ReturnType<typeof getElementCardState>>(); for (const element of allElements) { states.set(element.atomicNumber, getElementCardState(element)); } return states; }); function matchesActivePhases(element: PeriodicElement) { const phases = activePhaseFilters.value; if (!phases) { return true; } return phases.has(getElementState(element)); } function clearLegendFilters() { selectedPhases.value = new Set(); selectedCategories.value = new Set(); selectedGroups.value = new Set(); selectedBlocks.value = new Set(); hoveredPhaseFilter.value = null; hoveredBlock.value = null; categoryLegendHover.value = null; } function clearKeyboardSearch() { keyboardSearchQuery.value = ""; } function deactivateKeyboardSearch() { keyboardSearchEnabled.value = false; keyboardSearchQuery.value = ""; } function activateKeyboardSearch({ resetQuery = false } = {}) { keyboardSearchEnabled.value = true; if (resetQuery) { keyboardSearchQuery.value = ""; } } function getKeyboardSearchMatches(query: string) { const normalizedQuery = query.trim().toLowerCase(); if (!normalizedQuery) { return []; } const normalizedAtomicNumber = Number.parseInt(normalizedQuery, 10); const hasAtomicNumberQuery = Number.isInteger(normalizedAtomicNumber); if (hasAtomicNumberQuery) { const atomicNumberMatches = allElements.filter( (element) => element.atomicNumber === normalizedAtomicNumber, ); if (atomicNumberMatches.length > 0) { return atomicNumberMatches; } } const exactSymbolMatches = allElements.filter( (element) => element.symbol.toLowerCase() === normalizedQuery, ); if (exactSymbolMatches.length > 0) { return exactSymbolMatches; } const prefixMatches = allElements.filter((element) => { return ( element.symbol.toLowerCase().startsWith(normalizedQuery) || element.name.toLowerCase().startsWith(normalizedQuery) || element.nameEn.toLowerCase().startsWith(normalizedQuery) || element.slug.startsWith(normalizedQuery) || (hasAtomicNumberQuery && element.atomicNumber.toString().startsWith(normalizedQuery)) ); }); if (prefixMatches.length > 0) { return prefixMatches; } return searchElements(normalizedQuery); } function isEditableTarget(target: EventTarget | null) { if (!(target instanceof HTMLElement)) { return false; } return target.closest("input, textarea, select, [contenteditable='true']") !== null; } function getElementState(element: PeriodicElement) { return elementStates.value.get(element.atomicNumber) ?? "unknown"; } function matchesAnySearch(element: PeriodicElement) { if (!hasKeyboardSearch.value && !hasNavbarSearch.value) { return true; } if (hasKeyboardSearch.value && !keyboardSearchAtomicNumbers.value.has(element.atomicNumber)) { return false; } if (hasNavbarSearch.value && !navbarSearchAtomicNumbers.value.has(element.atomicNumber)) { return false; } return true; } function isElementHighlighted(element: PeriodicElement) { if (!hasActiveElementFilter.value) { return false; } const matchesLegend = activePhaseFilters.value === null || matchesActivePhases(element); return matchesLegend && matchesAnySearch(element); } function handleElementSelect(element: PeriodicElement) { emit("select", element); router.push({ query: { slug: element.slug } }); } function isElementMuted(element: PeriodicElement) { if (!hasActiveElementFilter.value) { return false; } return !isElementHighlighted(element); } function isElementUndiscovered(element: PeriodicElement) { if (!isDiscoveredPropertySelected.value) { return false; } const year = getElementPropertiesByAtomicNumber(element.atomicNumber)?.discoveredYear ?? null; if (year == null) { return true; } return year > discoveryYear.value; } function handleWindowKeydown(event: KeyboardEvent) { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "f") { if (keyboardSearchEnabled.value) { deactivateKeyboardSearch(); } else { activateKeyboardSearch({ resetQuery: true }); } event.preventDefault(); return; } if (keyboardSearchEnabled.value && !event.metaKey && !event.ctrlKey && !event.altKey) { if (event.key === "Backspace") { if (keyboardSearchQuery.value.length > 0) { keyboardSearchQuery.value = keyboardSearchQuery.value.slice(0, -1); event.preventDefault(); return; } } if ( event.key.length === 1 && !isEditableTarget(event.target) && /[\p{L}\p{N}\s-]/u.test(event.key) ) { keyboardSearchQuery.value += event.key; event.preventDefault(); return; } } if (event.key !== "Escape") { return; } if (keyboardSearchEnabled.value && keyboardSearchQuery.value.length > 0) { clearKeyboardSearch(); return; } if (keyboardSearchEnabled.value) { deactivateKeyboardSearch(); return; } clearLegendFilters(); } onMounted(() => { window.addEventListener("keydown", handleWindowKeydown); }); onBeforeUnmount(() => { window.removeEventListener("keydown", handleWindowKeydown); }); </script> <template> <section class="table-grid" :class="{ 'table-grid--article': isArticlePropertySelected, 'table-grid--electrons': isElectronsVariant, }" :style="{ '--table-columns': columnCount }" :data-home-property="tableHomeProperty" > <div class="table-grid__frame" :data-extended="isExtended || undefined" > <div class="table-grid__corner" aria-hidden="true" /> <div class="table-grid__groups" aria-hidden="true"> <div v-for="(group, groupIndex) in groupLabels" :key="`group-${groupIndex + 1}`" class="table-grid__group-label" > {{ group ?? "" }} </div> </div> <div class="table-grid__periods" aria-hidden="true"> <div v-for="(period, periodIndex) in periodLabels" :key="`period-${periodIndex + 1}`" class="table-grid__period-label" > {{ period ?? "" }} </div> </div> <div class="table-grid__area"> <div class="table-grid__overlay"> <div v-if="isExtended" class="table-grid__legend-stack"> <div class="table-grid__legend"> <TableLegend :is-wide="isExtended" :left-legend="leftLegendMode" :show-category-legend="!hideCategoryLegend" :phase-hover="hoveredPhaseFilter" :selected-phases="selectedPhases" :selected-categories="selectedCategories" :selected-groups="selectedGroups" :category-hover="categoryLegendHover" :hovered-block="hoveredBlock" :selected-blocks="selectedBlocks" :selected-atomic-number="selectedAtomicNumber" :legend-swipe="legendSwipe" :property-start-color="propertyStartColor" :property-end-color="propertyEndColor" :property-unknown-color="propertyUnknownColor" :property-zero-color="propertyZeroColor" :property-scale="propertyScale" :show-property-zero="isMeltBoilPropertySelected" :disable-property-logarithmic="isMeltBoilPropertySelected" :property-zero-stop-percent=" isMeltBoilPropertySelected ? meltBoilZeroStopPercent : undefined " @hover-legend="setCategoryLegendHover" @preview-phase="previewPhaseFilter" @toggle-phase="togglePhaseFilter" @toggle-category="toggleCategorySelection" @toggle-group="toggleCategoryGroup" @hover-block="previewBlockFilter" @toggle-block="toggleBlockFilter" @update:property-start-color="setPropertyStartColor" @update:property-end-color="setPropertyEndColor" @update:property-unknown-color="setPropertyUnknownColor" @update:property-zero-color="setPropertyZeroColor" @update:property-scale="setPropertyScale" /> </div> <div id="Parentheses" class="Parentheses table-grid__parentheses table-grid__parentheses--wide" :data-default="tableFootnote" > {{ tableFootnote }} </div> </div> <template v-else> <div class="table-grid__legend"> <TableLegend :is-wide="isExtended" :left-legend="leftLegendMode" :show-category-legend="!hideCategoryLegend" :phase-hover="hoveredPhaseFilter" :selected-phases="selectedPhases" :selected-categories="selectedCategories" :selected-groups="selectedGroups" :category-hover="categoryLegendHover" :hovered-block="hoveredBlock" :selected-blocks="selectedBlocks" :legend-swipe="legendSwipe" :selected-atomic-number="selectedAtomicNumber" :property-start-color="propertyStartColor" :property-end-color="propertyEndColor" :property-unknown-color="propertyUnknownColor" :property-zero-color="propertyZeroColor" :property-scale="propertyScale" :show-property-zero="isMeltBoilPropertySelected" :disable-property-logarithmic="isMeltBoilPropertySelected" :property-zero-stop-percent=" isMeltBoilPropertySelected ? meltBoilZeroStopPercent : undefined " @hover-legend="setCategoryLegendHover" @preview-phase="previewPhaseFilter" @toggle-phase="togglePhaseFilter" @toggle-category="toggleCategorySelection" @toggle-group="toggleCategoryGroup" @hover-block="previewBlockFilter" @toggle-block="toggleBlockFilter" @update:property-start-color="setPropertyStartColor" @update:property-end-color="setPropertyEndColor" @update:property-unknown-color="setPropertyUnknownColor" @update:property-zero-color="setPropertyZeroColor" @update:property-scale="setPropertyScale" /> </div> <div id="Parentheses" class="Parentheses table-grid__parentheses" :data-default="tableFootnote" > {{ tableFootnote }} </div> </template> </div> <div class="table-grid__matrix"> <ElementCard v-for="item in placedElements" :key="item.element.atomicNumber" :element="item.element" :is-wide="isExtended" :dimmed="isCategoryDimmed(item.element)" :highlighted="isCategoryHighlighted(item.element)" :is-highlighted="isElementHighlighted(item.element)" :is-muted="isElementMuted(item.element)" :is-undiscovered="isElementUndiscovered(item.element)" :is-selected="selectedAtomicNumber === item.element.atomicNumber" :state="elementCardStateByAtomicNumber.get(item.element.atomicNumber)" :temperature-state=" isElectronsVariant || isDiscoveredPropertySelected ? undefined : getElementState(item.element) " :use-category-color=" !isElectronsVariant && !isNumericPropertyHeatmapSelected " :block-class=" usesBlockColors ? `block-${getElementBlock(item.element.atomicNumber)}` : undefined " :block-highlighted="isBlockHighlighted(item.element)" :block-dimmed="isBlockDimmed(item.element)" :card-title="isElectronsVariant ? getElectronCardTitle(item.element) : undefined" :value-text="getCardValueText(item.element)" :value-href="getCardValueHref(item.element)" :show-mass-unit="isAtomicMassPropertySelected" :surface-color="getCardSurfaceColor(item.element)" :surface-ink="getCardSurfaceInk(item.element)" :style="{ gridColumn: String(item.column), gridRow: String(item.row) }" @select="handleElementSelect" /> <button v-if="!isExtended" type="button" class="table-grid__fblock-connector" title="Развернуть: показать лантаноиды и актиноиды в 6 и 7 периодах" @click="toggleExtended" > <span class="table-grid__fblock-range">57–71</span> <span class="table-grid__fblock-range">89–103</span> <span class="table-grid__fblock-gap" aria-hidden="true" /> <span class="table-grid__fblock-period">6</span> <span class="table-grid__fblock-period">7</span> </button> </div> </div> </div> </section> </template> <style scoped> .table-grid { container-type: inline-size; --table-frame-pad: clamp(0.45rem, 1.1cqw, 0.72rem); --table-axis-gap: clamp(0.18rem, 0.45cqw, 0.3rem); --table-period-size: clamp(0.72rem, calc(100cqw * 0.018), 0.95rem); --table-cell-gap: clamp(0.18rem, calc(100cqw * 0.0022), 0.28rem); --table-cell-size: min( 5.5rem, calc( ( 100cqw - var(--table-period-size) - var(--table-axis-gap) - (var(--table-columns) - 1) * var(--table-cell-gap) - 2 * var(--table-frame-pad) ) / var(--table-columns) ) ); --table-cell-font: calc(var(--table-cell-size) * 0.348); --table-element-pad: 0.07rem 0.05rem 0.11rem; --table-element-radius: clamp(0.34rem, calc(var(--table-cell-size) * 0.115), 0.58rem); --table-legend-split: clamp(0.78rem, calc(var(--table-cell-size) * 0.26), 1.35rem); --table-legend-inset-inline: clamp(0.34rem, calc(var(--table-cell-size) * 0.12), 0.56rem); --table-legend-inset-bottom: clamp(0.42rem, calc(var(--table-cell-size) * 0.14), 0.64rem); --table-legend-inset-top: clamp(0.32rem, calc(var(--table-cell-size) * 0.08), 0.48rem); width: 100%; max-width: 100%; overflow-x: clip; padding-bottom: 0.4rem; } .table-grid__frame { position: relative; width: 100%; max-width: 100%; margin-inline: auto; display: grid; grid-template-columns: var(--table-period-size) minmax(0, 1fr); grid-template-rows: auto auto; column-gap: var(--table-axis-gap); row-gap: var(--table-axis-gap); padding: var(--table-frame-pad); box-sizing: border-box; border: 1px solid var(--table-line); border-radius: 1.5rem; background: var(--table-surface-soft); box-shadow: 0 8px 22px -20px var(--color-shadow), 0 1px 0 color-mix(in oklab, white 16%, transparent); } .table-grid__corner { grid-area: 1 / 1; width: var(--table-period-size); } .table-grid__groups { grid-area: 1 / 2; display: grid; width: calc( var(--table-columns) * var(--table-cell-size) + (var(--table-columns) - 1) * var(--table-cell-gap) ); max-width: 100%; grid-template-columns: repeat(var(--table-columns, 18), var(--table-cell-size)); gap: var(--table-cell-gap); } .table-grid__periods { grid-area: 2 / 1; display: grid; width: var(--table-period-size); grid-auto-rows: var(--table-cell-size); gap: var(--table-cell-gap); } .table-grid__group-label, .table-grid__period-label { display: flex; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 650; letter-spacing: 0.01em; color: var(--table-ink-muted); } .table-grid__area { grid-area: 2 / 2; position: relative; } .table-grid__overlay { position: absolute; inset: 0; z-index: 2; display: grid; width: calc( var(--table-columns) * var(--table-cell-size) + (var(--table-columns) - 1) * var(--table-cell-gap) ); max-width: 100%; grid-template-columns: repeat(var(--table-columns, 18), var(--table-cell-size)); grid-auto-rows: var(--table-cell-size); gap: var(--table-cell-gap); pointer-events: none; } .table-grid__legend { grid-column: 3 / 13; grid-row: 1 / 4; box-sizing: border-box; min-width: 0; min-height: 0; height: 100%; padding: var(--table-legend-inset-top) var(--table-legend-inset-inline) var(--table-legend-inset-bottom) var(--table-legend-inset-inline); pointer-events: auto; } .table-grid__frame:not([data-extended]) { --table-parentheses-band: clamp(2rem, calc(var(--table-cell-size) * 0.72), 2.75rem); --table-compact-grid-rows: repeat(6, var(--table-cell-size)) var(--table-cell-size) var(--table-parentheses-band) repeat(2, var(--table-cell-size)); } .table-grid__frame:not([data-extended]) .table-grid__matrix, .table-grid__frame:not([data-extended]) .table-grid__overlay { grid-auto-rows: unset; grid-template-rows: var(--table-compact-grid-rows); } .table-grid__frame:not([data-extended]) .table-grid__periods { grid-auto-rows: unset; grid-template-rows: var(--table-compact-grid-rows); } .table-grid__frame:not([data-extended]) .table-grid__parentheses { grid-column: 4 / -1; grid-row: 8; align-self: center; z-index: 3; min-width: 0; margin: 0 calc(var(--table-cell-size) * 0.08); padding: calc(var(--table-cell-gap) + 0.14rem) 0; font-family: var(--table-font-ui); font-size: calc(var(--table-cell-font) * 0.7); line-height: 1.28; letter-spacing: -0.01em; color: var(--table-ink-muted); pointer-events: none; } .table-grid__matrix { position: relative; grid-area: 1 / 1; display: grid; width: calc( var(--table-columns) * var(--table-cell-size) + (var(--table-columns) - 1) * var(--table-cell-gap) ); max-width: 100%; grid-template-columns: repeat(var(--table-columns, 18), var(--table-cell-size)); grid-auto-rows: var(--table-cell-size); gap: var(--table-cell-gap); } .table-grid__fblock-connector { position: absolute; left: calc(2 * (var(--table-cell-size) + var(--table-cell-gap))); top: calc(5 * (var(--table-cell-size) + var(--table-cell-gap))); z-index: 1; width: var(--table-cell-size); height: calc(4 * var(--table-cell-size) + 3 * var(--table-cell-gap)); display: grid; grid-template-rows: repeat(4, var(--table-cell-size)); row-gap: var(--table-cell-gap); padding: 0; border: 1px dashed var(--table-line); border-radius: 0.78rem; background: var(--table-surface); color: var(--table-ink-muted); font: inherit; cursor: pointer; transition: border-color 180ms ease, color 180ms ease, background-color 180ms ease; } .table-grid__frame:not([data-extended]) .table-grid__fblock-connector { height: calc( 4 * var(--table-cell-size) + 3 * var(--table-cell-gap) + var(--table-parentheses-band) ); grid-template-rows: var(--table-cell-size) var(--table-cell-size) var(--table-parentheses-band) var(--table-cell-size) var(--table-cell-size); } .table-grid__fblock-gap { display: block; min-height: 0; } .table-grid__fblock-connector > span { display: flex; align-items: center; justify-content: center; } .table-grid__fblock-range { font-size: 0.7rem; font-weight: 650; letter-spacing: -0.02em; } .table-grid__fblock-period { font-size: 0.82rem; font-weight: 700; } .table-grid__fblock-connector:hover, .table-grid__fblock-connector:focus-visible { border-color: var(--table-selected-line); color: var(--table-ink-strong); background: var(--table-selected-surface); } .table-grid__frame[data-extended] { --table-cell-gap: clamp(0.06rem, calc(100cqw * 0.0015), 0.14rem); --table-period-size: clamp(0.62rem, calc(100cqw * 0.014), 0.88rem); --table-frame-pad: clamp(0.32rem, calc(100cqw * 0.008), 0.58rem); --table-axis-gap: clamp(0.14rem, calc(100cqw * 0.004), 0.26rem); --table-cell-size: min( 5.5rem, calc( ( 100cqw - var(--table-period-size) - var(--table-axis-gap) - (var(--table-columns) - 1) * var(--table-cell-gap) - 2 * var(--table-frame-pad) ) / var(--table-columns) ) ); --table-font-ui: system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; --table-font-data: system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif; --table-wide-legend-font: clamp(0.62rem, calc(var(--table-cell-size) * 0.36), 0.82rem); --table-wide-legend-split: clamp(0.72rem, calc(100cqw * 0.028), 1.18rem); --table-legend-inset-inline: clamp(0.3rem, calc(var(--table-cell-size) * 0.1), 0.5rem); --table-legend-inset-bottom: clamp(0.36rem, calc(var(--table-cell-size) * 0.12), 0.56rem); --table-legend-inset-top: clamp(0.3rem, calc(var(--table-cell-size) * 0.07), 0.44rem); --table-cell-font: calc(var(--table-cell-size) * 0.348); --table-element-pad: 0.06rem 0.04rem 0.09rem; --table-element-radius: clamp(0.28rem, calc(var(--table-cell-size) * 0.105), 0.48rem); --table-wide-state-ink: var(--table-surface); } .table-grid__frame[data-extended] .table-grid__legend-stack { grid-column: 3 / 17; grid-row: 1 / 6; align-self: end; z-index: 3; display: flex; flex-direction: column; gap: clamp(0.22rem, calc(var(--table-cell-size) * 0.08), 0.38rem); min-width: 0; margin-bottom: clamp(0.16rem, calc(var(--table-cell-size) * 0.06), 0.32rem); padding: var(--table-legend-inset-top) var(--table-legend-inset-inline) 0 var(--table-legend-inset-inline); box-sizing: border-box; overflow: visible; pointer-events: none; } .table-grid__frame[data-extended] .table-grid__legend-stack .table-grid__legend { position: static; flex: 0 0 auto; height: auto; max-height: none; padding: 0; overflow: visible; pointer-events: auto; } .table-grid__frame[data-extended] .table-grid__legend-stack .table-grid__legend :deep(.table-legend--wide), .table-grid__frame[data-extended] .table-grid__legend-stack .table-grid__legend :deep(.category-legend--wide) { height: auto; max-height: none; overflow: visible; } /* Electrons wide: pin ConfigBlocks inside rows 1–5 so orbitals scale via cqh. */ .table-grid--electrons .table-grid__frame[data-extended] .table-grid__legend-stack { align-self: stretch; min-height: 0; height: 100%; max-height: 100%; overflow: hidden; margin-bottom: 0; padding-bottom: var(--table-legend-inset-bottom); } .table-grid--electrons .table-grid__frame[data-extended] .table-grid__legend-stack .table-grid__legend { flex: 1 1 auto; min-height: 0; height: auto; max-height: 100%; overflow: hidden; display: flex; flex-direction: column; } .table-grid--electrons .table-grid__frame[data-extended] .table-grid__legend-stack .table-grid__legend :deep(.table-legend--wide) { flex: 1 1 auto; height: 100%; max-height: 100%; min-height: 0; overflow: hidden; } .table-grid--electrons .table-grid__frame[data-extended] .table-grid__parentheses--wide { flex: 0 0 auto; max-width: 100%; } .table-grid__frame[data-extended] .table-grid__parentheses--wide { flex: 0 0 auto; position: relative; z-index: 4; min-width: 0; margin: 0; padding: 0 calc(var(--table-cell-size) * 0.04); font-family: var(--table-font-ui); font-size: calc(var(--table-cell-font) * 0.7); line-height: 1.22; letter-spacing: -0.01em; color: var(--table-ink-muted); pointer-events: none; } .table-grid__frame[data-extended] .table-grid__group-label { font-size: clamp(0.58rem, calc(var(--table-cell-size) * 0.28), 0.78rem); font-weight: 650; } @media (max-width: 980px) { .table-grid__overlay { position: static; inset: auto; display: block; margin-bottom: 0.8rem; pointer-events: auto; } .table-grid__legend { grid-column: auto; grid-row: auto; width: min(100%, 58rem); margin: 0 auto; } .table-grid__frame:not([data-extended]) .table-grid__parentheses { grid-column: auto; grid-row: auto; transform: none; width: min(100%, 58rem); margin: 0.55rem auto 0; padding: 0 0.35rem; pointer-events: auto; } } </style>