Files
iD/modules/ui/fields/radio.js
Tobias ce51b33cf2 Improve combobox dropdown styling to give labels more room and readability (#10127)
* Fields: Break long lines in combo dropdown instead of ellipsis

Longer translations keys are not readable with ellipsis and the tooltip only shows the description (or fallback), not the title.

This change makes long use line breaks with small line height to make the whole text visible.

* Fields: Use table styles for rows to maximize available space

The flex box solution gave a lot of space to the label. This solution minimizes that space to give more space to the values dropdown.
2024-02-26 14:02:22 +01:00

345 lines
9.7 KiB
JavaScript

import { dispatch as d3_dispatch } from 'd3-dispatch';
import { select as d3_select } from 'd3-selection';
import { presetManager } from '../../presets';
import { t } from '../../core/localizer';
import { uiField } from '../field';
import { utilArrayUnion, utilRebind } from '../../util';
export { uiFieldRadio as uiFieldStructureRadio };
export function uiFieldRadio(field, context) {
var dispatch = d3_dispatch('change');
var placeholder = d3_select(null);
var wrap = d3_select(null);
var labels = d3_select(null);
var radios = d3_select(null);
var radioData = (field.options || field.keys).slice(); // shallow copy
var typeField;
var layerField;
var _oldType = {};
var _entityIDs = [];
function selectedKey() {
var node = wrap.selectAll('.form-field-input-radio label.active input');
return !node.empty() && node.datum();
}
function radio(selection) {
selection.classed('preset-radio', true);
wrap = selection.selectAll('.form-field-input-wrap')
.data([0]);
var enter = wrap.enter()
.append('div')
.attr('class', 'form-field-input-wrap form-field-input-radio');
enter
.append('span')
.attr('class', 'placeholder');
wrap = wrap
.merge(enter);
placeholder = wrap.selectAll('.placeholder');
labels = wrap.selectAll('label')
.data(radioData);
enter = labels.enter()
.append('label');
var stringsField = field.resolveReference('stringsCrossReference');
enter
.append('input')
.attr('type', 'radio')
.attr('name', field.id)
.attr('value', function(d) { return stringsField.t('options.' + d, { 'default': d }); })
.attr('checked', false);
enter
.append('span')
.each(function(d) { stringsField.t.append('options.' + d, { 'default': d })(d3_select(this)); });
labels = labels
.merge(enter);
radios = labels.selectAll('input')
.on('change', changeRadio);
}
function structureExtras(selection, tags) {
var selected = selectedKey() || tags.layer !== undefined;
var type = presetManager.field(selected);
var layer = presetManager.field('layer');
var showLayer = (selected === 'bridge' || selected === 'tunnel' || tags.layer !== undefined);
var extrasWrap = selection.selectAll('.structure-extras-wrap')
.data(selected ? [0] : []);
extrasWrap.exit()
.remove();
extrasWrap = extrasWrap.enter()
.append('div')
.attr('class', 'structure-extras-wrap')
.merge(extrasWrap);
var list = extrasWrap.selectAll('ul')
.data([0]);
list = list.enter()
.append('ul')
.attr('class', 'rows')
.merge(list);
// Type
if (type) {
if (!typeField || typeField.id !== selected) {
typeField = uiField(context, type, _entityIDs, { wrap: false })
.on('change', changeType);
}
typeField.tags(tags);
} else {
typeField = null;
}
var typeItem = list.selectAll('.structure-type-item')
.data(typeField ? [typeField] : [], function(d) { return d.id; });
// Exit
typeItem.exit()
.remove();
// Enter
var typeEnter = typeItem.enter()
.insert('li', ':first-child')
.attr('class', 'labeled-input structure-type-item');
typeEnter
.append('div')
.attr('class', 'label structure-label-type')
.attr('for', 'preset-input-' + selected)
.call(t.append('inspector.radio.structure.type'));
typeEnter
.append('div')
.attr('class', 'structure-input-type-wrap');
// Update
typeItem = typeItem
.merge(typeEnter);
if (typeField) {
typeItem.selectAll('.structure-input-type-wrap')
.call(typeField.render);
}
// Layer
if (layer && showLayer) {
if (!layerField) {
layerField = uiField(context, layer, _entityIDs, { wrap: false })
.on('change', changeLayer);
}
layerField.tags(tags);
field.keys = utilArrayUnion(field.keys, ['layer']);
} else {
layerField = null;
field.keys = field.keys.filter(function(k) { return k !== 'layer'; });
}
var layerItem = list.selectAll('.structure-layer-item')
.data(layerField ? [layerField] : []);
// Exit
layerItem.exit()
.remove();
// Enter
var layerEnter = layerItem.enter()
.append('li')
.attr('class', 'labeled-input structure-layer-item');
layerEnter
.append('div')
.attr('class', 'label structure-label-layer')
.attr('for', 'preset-input-layer')
.call(t.append('inspector.radio.structure.layer'));
layerEnter
.append('div')
.attr('class', 'structure-input-layer-wrap');
// Update
layerItem = layerItem
.merge(layerEnter);
if (layerField) {
layerItem.selectAll('.structure-input-layer-wrap')
.call(layerField.render);
}
}
function changeType(t, onInput) {
var key = selectedKey();
if (!key) return;
var val = t[key];
if (val !== 'no') {
_oldType[key] = val;
}
if (field.type === 'structureRadio') {
// remove layer if it should not be set
if (val === 'no' ||
(key !== 'bridge' && key !== 'tunnel') ||
(key === 'tunnel' && val === 'building_passage')) {
t.layer = undefined;
}
// add layer if it should be set
if (t.layer === undefined) {
if (key === 'bridge' && val !== 'no') {
t.layer = '1';
}
if (key === 'tunnel' && val !== 'no' && val !== 'building_passage') {
t.layer = '-1';
}
}
}
dispatch.call('change', this, t, onInput);
}
function changeLayer(t, onInput) {
if (t.layer === '0') {
t.layer = undefined;
}
dispatch.call('change', this, t, onInput);
}
function changeRadio() {
var t = {};
var activeKey;
if (field.key) {
t[field.key] = undefined;
}
radios.each(function(d) {
var active = d3_select(this).property('checked');
if (active) activeKey = d;
if (field.key) {
if (active) t[field.key] = d;
} else {
var val = _oldType[activeKey] || 'yes';
t[d] = active ? val : undefined;
}
});
if (field.type === 'structureRadio') {
if (activeKey === 'bridge') {
t.layer = '1';
} else if (activeKey === 'tunnel' && t.tunnel !== 'building_passage') {
t.layer = '-1';
} else {
t.layer = undefined;
}
}
dispatch.call('change', this, t);
}
radio.tags = function(tags) {
function isOptionChecked(d) {
if (field.key) {
return tags[field.key] === d;
}
return !!(typeof tags[d] === 'string' && tags[d].toLowerCase() !== 'no');
}
function isMixed(d) {
if (field.key) {
return Array.isArray(tags[field.key]) && tags[field.key].includes(d);
}
return Array.isArray(tags[d]);
}
radios.property('checked', function(d) {
return isOptionChecked(d) &&
(field.key || field.options.filter(isOptionChecked).length === 1);
});
labels
.classed('active', function(d) {
if (field.key) {
return (Array.isArray(tags[field.key]) && tags[field.key].includes(d))
|| tags[field.key] === d;
}
return Array.isArray(tags[d]) && tags[d].some(v => typeof v === 'string' && v.toLowerCase() !== 'no') ||
!!(typeof tags[d] === 'string' && tags[d].toLowerCase() !== 'no');
})
.classed('mixed', isMixed)
.attr('title', function(d) {
return isMixed(d) ? t('inspector.unshared_value_tooltip') : null;
});
var selection = radios.filter(function() { return this.checked; });
if (selection.empty()) {
placeholder.text('');
placeholder.call(t.append('inspector.none'));
} else {
placeholder.text(selection.attr('value'));
_oldType[selection.datum()] = tags[selection.datum()];
}
if (field.type === 'structureRadio') {
// For waterways without a tunnel tag, set 'culvert' as
// the _oldType to default to if the user picks 'tunnel'
if (!!tags.waterway && !_oldType.tunnel) {
_oldType.tunnel = 'culvert';
}
wrap.call(structureExtras, tags);
}
};
radio.focus = function() {
radios.node().focus();
};
radio.entityIDs = function(val) {
if (!arguments.length) return _entityIDs;
_entityIDs = val;
_oldType = {};
return radio;
};
radio.isAllowed = function() {
return _entityIDs.length === 1;
};
return utilRebind(radio, dispatch, 'on');
}