diff --git a/biome.json b/biome.json
index e0a44483..7138fd9a 100644
--- a/biome.json
+++ b/biome.json
@@ -1,38 +1,57 @@
{
- "$schema": "https://biomejs.dev/schemas/1.7.3/schema.json",
- "organizeImports": {
- "enabled": true
- },
- "vcs": {
- "clientKind": "git",
- "enabled": true,
- "useIgnoreFile": true
- },
- "linter": {
- "enabled": true,
- "rules": {
- "recommended": false
- }
- },
- "formatter": {
- "enabled": true,
- "formatWithErrors": false,
- "ignore": [],
- "indentStyle": "space",
- "indentWidth": 2,
- "lineEnding": "lf",
- "lineWidth": 120
- },
- "javascript": {
- "formatter": {
- "quoteStyle": "single",
- "semicolons": "asNeeded",
- "arrowParentheses": "asNeeded"
- }
- },
- "json": {
- "formatter": {
- "indentStyle": "space"
- }
- }
+ "$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
+ "files": {
+ "ignore": ["node_modules/**", "dist/**"]
+ },
+ "organizeImports": {
+ "enabled": true
+ },
+ "vcs": {
+ "clientKind": "git",
+ "enabled": true,
+ "useIgnoreFile": true
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "correctness": {
+ "noUndeclaredVariables": "error",
+ "noUnusedVariables": "error",
+ "useExhaustiveDependencies": "error"
+ },
+ "suspicious": {
+ "noImplicitAnyLet": "error",
+ "noConsoleLog": "warn"
+ },
+ "complexity": {
+ "useLiteralKeys": "error",
+ "noUselessFragments": "error"
+ },
+ "style": {
+ "recommended": true
+ }
+ }
+ },
+ "formatter": {
+ "enabled": true,
+ "formatWithErrors": false,
+ "ignore": [],
+ "indentStyle": "space",
+ "indentWidth": 2,
+ "lineEnding": "lf",
+ "lineWidth": 120
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "single",
+ "semicolons": "asNeeded",
+ "arrowParentheses": "asNeeded"
+ }
+ },
+ "json": {
+ "formatter": {
+ "indentStyle": "space"
+ }
+ }
}
diff --git a/src/lib/icons/formeo-sprite.svg b/src/lib/icons/formeo-sprite.svg
index 0f35fa8a..4cc512e2 100644
--- a/src/lib/icons/formeo-sprite.svg
+++ b/src/lib/icons/formeo-sprite.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/lib/icons/icon-component-corner.svg b/src/lib/icons/icon-component-corner.svg
new file mode 100644
index 00000000..568bbc84
--- /dev/null
+++ b/src/lib/icons/icon-component-corner.svg
@@ -0,0 +1,11 @@
+
+
diff --git a/src/lib/icons/icon-email.svg b/src/lib/icons/icon-email.svg
new file mode 100644
index 00000000..aa3c9e01
--- /dev/null
+++ b/src/lib/icons/icon-email.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/src/lib/icons/icon-handle-column.svg b/src/lib/icons/icon-handle-column.svg
new file mode 100644
index 00000000..9e395976
--- /dev/null
+++ b/src/lib/icons/icon-handle-column.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/src/lib/icons/icon-handle-field.svg b/src/lib/icons/icon-handle-field.svg
new file mode 100644
index 00000000..11a1e598
--- /dev/null
+++ b/src/lib/icons/icon-handle-field.svg
@@ -0,0 +1,48 @@
+
+
diff --git a/src/lib/icons/icon-handle-row.svg b/src/lib/icons/icon-handle-row.svg
new file mode 100644
index 00000000..a59f5b64
--- /dev/null
+++ b/src/lib/icons/icon-handle-row.svg
@@ -0,0 +1,56 @@
+
+
diff --git a/src/lib/js/common/dom.js b/src/lib/js/common/dom.js
index 016b0081..adc9901c 100644
--- a/src/lib/js/common/dom.js
+++ b/src/lib/js/common/dom.js
@@ -1,9 +1,8 @@
-import h, { indexOfNode, forEach } from './helpers.mjs'
+import h, { forEach } from './helpers.mjs'
import i18n from 'mi18n'
-import events from './events.js'
import animate from './animation.js'
-import Components, { Stages, Columns } from '../components/index.js'
-import { uuid, clone, numToPercent, componentType, merge } from './utils/index.mjs'
+import Components from '../components/index.js'
+import { uuid, componentType, merge } from './utils/index.mjs'
import {
ROW_CLASSNAME,
STAGE_CLASSNAME,
@@ -11,9 +10,18 @@ import {
FIELD_CLASSNAME,
CONTROL_GROUP_CLASSNAME,
CHILD_CLASSNAME_MAP,
- bsColRegExp,
+ iconPrefix,
} from '../constants.js'
+const iconFontTemplates = {
+ glyphicons: icon => ``,
+ 'font-awesome': icon => {
+ const [style, name] = icon.split(' ')
+ return ``
+ },
+ fontello: icon => `${icon}`,
+}
+
/**
* General purpose markup utilities and generator.
*/
@@ -258,6 +266,38 @@ class DOM {
})
}
+ get icons() {
+ if (this.iconSymbols) {
+ return this.iconSymbols
+ }
+
+ const iconSymbolNodes = document.querySelectorAll('#formeo-sprite svg symbol')
+
+ const createSvgIconConfig = symbolId => ({
+ tag: 'svg',
+ attrs: {
+ className: `svg-icon ${symbolId}`,
+ },
+ children: [
+ {
+ tag: 'use',
+ attrs: {
+ 'xmlns:xlink': 'http://www.w3.org/1999/xlink',
+ 'xlink:href': `#${symbolId}`,
+ },
+ },
+ ],
+ })
+
+ this.iconSymbols = Array.from(iconSymbolNodes).reduce((acc, symbol) => {
+ const name = symbol.id.replace(iconPrefix, '')
+ acc[name] = dom.create(createSvgIconConfig(symbol.id))
+ return acc
+ }, {})
+
+ return this.iconSymbols
+ }
+
/**
* Create and SVG or font icon.
* Simple string concatenation instead of DOM.create because:
@@ -265,32 +305,22 @@ class DOM {
* - it forces the icon to be appended using innerHTML which helps svg render
* @param {String} name - icon name
* @return {String} icon markup
- * @todo remove document.getElementById
*/
- icon(name = null) {
+ icon(name = null, classNames = []) {
if (!name) {
return
}
- const iconPrefix = 'f-i-'
- const iconLink = document.getElementById(iconPrefix + name)
- let icon
- const iconFontTemplates = {
- glyphicons: icon => ``,
- 'font-awesome': icon => {
- const [style, name] = icon.split(' ')
- return ``
- },
- fontello: icon => `${icon}`,
- }
- if (iconLink) {
- icon = ``
- } else if (dom.options.iconFont) {
- icon = iconFontTemplates[dom.options.iconFont](name)
- } else {
- icon = name
+ const icon = this.icons[name]
+
+ if (icon) {
+ const iconClone = icon.cloneNode(true)
+ iconClone.classList.add(...classNames)
+
+ return iconClone.outerHTML
}
- return icon
+
+ return iconFontTemplates[dom.options.iconFont]?.(name) || name
}
/**
@@ -366,7 +396,7 @@ class DOM {
forEach(collection, elem => {
const txt = elem.textContent.toLowerCase()
const contains = txt.indexOf(term.toLowerCase()) !== -1
- cb && cb(elem, contains)
+ cb?.(elem, contains)
contains && elementsContainingText.push(elem)
})
return elementsContainingText
@@ -473,7 +503,8 @@ class DOM {
},
button: option => {
const { type, label, className, id } = option
- return Object.assign({}, elem, {
+ return {
+ ...elem,
attrs: {
type,
},
@@ -482,13 +513,13 @@ class DOM {
options: undefined,
children: label,
action: elem.action,
- })
+ }
},
checkbox: defaultInput,
radio: defaultInput,
}
- return optionMarkup[fieldType] && optionMarkup[fieldType](option)
+ return optionMarkup[fieldType]?.(option)
}
const mappedOptions = options.map(optionMap)
@@ -574,7 +605,7 @@ class DOM {
const fieldLabel = {
tag: 'label',
attrs: {
- for: elemId || (attrs && attrs.id),
+ for: elemId || attrs?.id,
},
className: [],
children: [labelText, required && this.requiredMark()],
@@ -604,7 +635,7 @@ class DOM {
return [
['array', content => Array.isArray(content)],
['node', content => content instanceof window.Node || content instanceof window.HTMLElement],
- ['component', () => content && content.dom],
+ ['component', () => content?.dom],
[typeof content, () => true],
].find(typeCondition => typeCondition[1](content))[0]
}
@@ -656,62 +687,6 @@ class DOM {
return elem
}
- /**
- * Clones an element, it's data and
- * it's nested elements and data
- * @param {Object} elem element we are cloning
- * @param {Object} parent
- * @todo move to Component
- * @return {Object} cloned element
- */
- clone(elem, parent) {
- // remove
- const formData = {}
- const _this = this
- const { id, fType } = elem
- const dataClone = clone(formData[fType].get(id))
- const newIndex = indexOfNode(elem) + 1
- let noParent = false
- dataClone.id = uuid()
- formData[fType].set(dataClone.id, dataClone)
- if (!parent) {
- parent = elem.parentElement
- noParent = true
- }
- const cloneType = {
- rows: () => {
- dataClone.columns = []
- const stage = Stages.active
- const newRow = stage.addRow(null, dataClone.id)
- const columns = elem.getElementsByClassName(COLUMN_CLASSNAME)
-
- stage.insertBefore(newRow, stage.childNodes[newIndex])
- h.forEach(columns, column => _this.clone(column, newRow))
- // data.saveRowOrder()
- return newRow
- },
- columns: () => {
- dataClone.fields = []
- const newColumn = _this.addColumn(parent, dataClone.id)
- parent.insertBefore(newColumn, parent.childNodes[newIndex])
- const fields = elem.getElementsByClassName(FIELD_CLASSNAME)
-
- if (noParent) {
- dom.columnWidths(parent)
- }
- h.forEach(fields, field => _this.clone(field, newColumn))
- return newColumn
- },
- fields: () => {
- const newField = _this.addField(parent, dataClone.id)
- parent.insertBefore(newField, parent.childNodes[newIndex])
- return newField
- },
- }
-
- return cloneType[fType]()
- }
-
/**
* Remove elements without f children
* @param {Object} element DOM element
@@ -774,32 +749,32 @@ class DOM {
h.forEach(nodeList, addClass[this.childType(className)])
}
- /**
- * Read columns and generate bootstrap cols
- * @param {Object} row DOM element
- */
- columnWidths(row) {
- const columns = row.getElementsByClassName(COLUMN_CLASSNAME)
- if (!columns.length) {
- return
- }
- const width = parseFloat((100 / columns.length).toFixed(1)) / 1
+ // /**
+ // * Read columns and generate bootstrap cols
+ // * @param {Object} row DOM element
+ // */
+ // columnWidths(row) {
+ // const columns = row.getElementsByClassName(COLUMN_CLASSNAME)
+ // if (!columns.length) {
+ // return
+ // }
+ // const width = parseFloat((100 / columns.length).toFixed(1)) / 1
- this.removeClasses(columns, bsColRegExp)
- h.forEach(columns, column => {
- Columns.get(column.id).refreshFieldPanels()
+ // this.removeClasses(columns, bsColRegExp)
+ // h.forEach(columns, column => {
+ // Columns.get(column.id).refreshFieldPanels()
- const newColWidth = numToPercent(width)
+ // const newColWidth = numToPercent(width)
- column.style.width = newColWidth
- column.style.float = 'left'
- Columns.set(`${column.id}.config.width`, newColWidth)
- column.dataset.colWidth = newColWidth
- document.dispatchEvent(events.columnResized)
- })
+ // column.style.width = newColWidth
+ // column.style.float = 'left'
+ // Columns.set(`${column.id}.config.width`, newColWidth)
+ // column.dataset.colWidth = newColWidth
+ // document.dispatchEvent(events.columnResized)
+ // })
- dom.updateColumnPreset(row)
- }
+ // dom.updateColumnPreset(row)
+ // }
/**
* Wrap content in a formGroup
@@ -895,40 +870,6 @@ class DOM {
elem.classList.toggle('empty', !children.length)
}
- /**
- * Style Object
- * Usage:
- *
- const rules = [['.css-class-selector', ['width', '100%', true]]]
- dom.insertRule(rules)
- * @param {Object} rules
- * @return {Number} index of added rule
- */
- insertRule(rules) {
- const styleSheet = this.styleSheet
- const rulesLength = styleSheet.cssRules.length
- for (let i = 0, rl = rules.length; i < rl; i++) {
- let j = 1
- let rule = rules[i]
- const selector = rules[i][0]
- let propStr = ''
- // If the second argument of a rule is an array
- // of arrays, correct our variables.
- if (Object.prototype.toString.call(rule[1][0]) === '[object Array]') {
- rule = rule[1]
- j = 0
- }
-
- for (let pl = rule.length; j < pl; j++) {
- const prop = rule[j]
- const important = prop[2] ? ' !important' : ''
- propStr += `${prop[0]}:${prop[1]}${important};`
- }
-
- // Insert CSS Rule
- return styleSheet.insertRule(`${selector} { ${propStr} }`, rulesLength)
- }
- }
btnTemplate = ({ title = '', ...rest }) => ({
tag: 'button',
attrs: {
diff --git a/src/lib/js/common/helpers.mjs b/src/lib/js/common/helpers.mjs
index d8584b13..d2a494d4 100644
--- a/src/lib/js/common/helpers.mjs
+++ b/src/lib/js/common/helpers.mjs
@@ -66,12 +66,23 @@ export const map = (arr, cb) => {
return newArray
}
+const sanitizedAttributeNames = {}
+
export const safeAttrName = name => {
- const safeAttr = {
+ const attributeMap = {
className: 'class',
}
- return safeAttr[name] || slugify(name)
+ if (sanitizedAttributeNames[name]) {
+ return sanitizedAttributeNames[name]
+ }
+
+ const attributeName = attributeMap[name] || name
+ const sanitizedAttributeName = attributeName.replace(/^\d/, '').replace(/[^a-zA-Z0-9-:]/g, '')
+
+ sanitizedAttributeNames[name] = sanitizedAttributeName
+
+ return sanitizedAttributeName
}
export const capitalize = str => str.replace(/\b\w/g, m => m.toUpperCase())
diff --git a/src/lib/js/common/utils/index.mjs b/src/lib/js/common/utils/index.mjs
index c5d163de..31f4ffa7 100644
--- a/src/lib/js/common/utils/index.mjs
+++ b/src/lib/js/common/utils/index.mjs
@@ -158,8 +158,6 @@ export const clone = obj => {
return copy
}
- debugger
-
throw new Error('Unable to copy Object, type not supported.')
}
diff --git a/src/lib/js/common/utils/object.mjs b/src/lib/js/common/utils/object.mjs
index 765a6227..e13d8d52 100644
--- a/src/lib/js/common/utils/object.mjs
+++ b/src/lib/js/common/utils/object.mjs
@@ -1,82 +1,8 @@
-/**
- * Retrieves the value at a given path within an object.
- *
- * @param {Object} obj - The object to query.
- * @param {string|string[]} pathArg - The path of the property to get. If a string is provided, it will be converted to an array.
- * @returns {*} - Returns the value at the specified path of the object.
- *
- * @example
- * const obj = { foo: [{ bar: 'baz' }] };
- * get(obj, 'foo[0].bar'); // 'baz'
- * get(obj, ['foo', '0', 'bar']); // 'baz'
- */
-export function get(obj, pathArg) {
- let path = pathArg
- if (!Array.isArray(pathArg)) {
- /*
- If pathString is not an array, then this statement replaces any
- instances of `[]` with `.` using a regular expression
- (/\[(\d)\]/g) and then splits pathString into an array using the
- delimiter `.`. The g flag in the regex indicates that we want to
- replace all instances of [] (not just the first instance).
- We're doing this because the walker needs each path segment to be
- a valid dot notation property.
-
- For example, if we have a path string of 'foo[0].bar', we need to
- convert it to 'foo.0.bar' so that we can split it into an array
- of ['foo', '0', 'bar'].
- */
- path = pathArg.replace(/\[(\d)\]/g, '.$1').split('.')
- }
-
- return path.reduce((acc, part) => {
- const currentVal = acc[part]
- path.shift()
- if (Array.isArray(currentVal)) {
- const [nextPart] = path
- const nextPartIndex = Number(nextPart)
- path.shift()
- if (nextPart) {
- if (isNaN(nextPartIndex)) {
- return get(currentVal.map(aObj => aObj[nextPart]).flat(), path)
- }
+import lodashSet from 'lodash/set.js'
+import lodashGet from 'lodash/get.js'
- return get(obj[part][nextPartIndex], path)
- }
-
- return currentVal
- }
-
- if (!currentVal) {
- path.splice(0)
- }
-
- return get(currentVal, path)
- }, obj)
-}
-
-/**
- * Sets the value at the specified path of the object. If the path does not exist, it will be created.
- *
- * @param {Object} obj - The object to modify.
- * @param {string|string[]} pathArg - The path of the property to set. Can be a string with dot notation or an array of strings/numbers.
- * @param {*} value - The value to set at the specified path.
- */
-export function set(obj, pathArg, value) {
- let path = pathArg
- if (!Array.isArray(pathArg)) {
- path = pathArg.replace(/\[(\d)\]/g, '.$1').split('.')
- }
-
- path.reduce((acc, part, index) => {
- if (index === path.length - 1) {
- acc[part] = value
- } else if (!acc[part] || typeof acc[part] !== 'object') {
- acc[part] = isNaN(Number(path[index + 1])) ? {} : []
- }
- return acc[part]
- }, obj)
-}
+export const get = lodashGet
+export const set = lodashSet
/**
* Empty an objects contents
@@ -100,3 +26,38 @@ export const cleanObj = obj => {
return fresh
}
+
+/**
+ * Determines if a value should be cloned.
+ *
+ * @param {*} value - The value to check.
+ * @returns {boolean} - Returns `true` if the value is an object and not null, otherwise `false`.
+ */
+export function shouldClone(value) {
+ return value !== null && typeof value === 'object'
+}
+
+/**
+ * Deeply clones an object or array.
+ *
+ * This function recursively clones all nested objects and arrays within the provided object.
+ * If the input is not an object or array, it returns the input as is.
+ *
+ * @param {Object|Array} obj - The object or array to be deeply cloned.
+ * @returns {Object|Array} - A deep clone of the input object or array.
+ */
+export function deepClone(obj) {
+ if (!shouldClone(obj)) return obj
+
+ if (Array.isArray(obj)) {
+ return obj.map(item => deepClone(item))
+ }
+
+ const cloned = {}
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ cloned[key] = deepClone(obj[key])
+ }
+ }
+ return cloned
+}
diff --git a/src/lib/js/components/columns/column.js b/src/lib/js/components/columns/column.js
index f58d37ad..38c04e3c 100644
--- a/src/lib/js/components/columns/column.js
+++ b/src/lib/js/components/columns/column.js
@@ -5,7 +5,7 @@ import h from '../../common/helpers.mjs'
import events from '../../common/events.js'
import dom from '../../common/dom.js'
import { COLUMN_CLASSNAME, FIELD_CLASSNAME } from '../../constants.js'
-import { resize } from './events.js'
+import { ResizeColumn } from './event-handlers.js'
const DEFAULT_DATA = () =>
Object.freeze({
@@ -13,14 +13,14 @@ const DEFAULT_DATA = () =>
width: '100%',
},
children: [],
- className: COLUMN_CLASSNAME,
+ className: [COLUMN_CLASSNAME],
})
const DOM_CONFIGS = {
- resizeHandle: () => ({
+ resizeHandle: columnRisizer => ({
className: 'resize-x-handle',
action: {
- pointerdown: resize,
+ pointerdown: columnRisizer.onStart.bind(columnRisizer),
},
content: [dom.icon('triangle-down'), dom.icon('triangle-up')],
}),
@@ -39,7 +39,7 @@ export default class Column extends Component {
* @return {Object} Column config object
*/
constructor(columnData) {
- super('column', Object.assign({}, DEFAULT_DATA(), columnData))
+ super('column', { ...DEFAULT_DATA(), ...columnData })
const _this = this
@@ -51,17 +51,14 @@ export default class Column extends Component {
dataset: {
hoverTag: i18n.get('column'),
},
- action: {
- mouseup: evt => {
- const column = evt.target.parentElement
- if (column.resizing) {
- column.resizing = false
- column.parentElement.classList.remove('resizing-columns')
- }
- },
- },
id: this.id,
- content: [this.getActionButtons(), DOM_CONFIGS.editWindow(), DOM_CONFIGS.resizeHandle(), children],
+ content: [
+ this.getComponentTag(),
+ this.getActionButtons(),
+ DOM_CONFIGS.editWindow(),
+ DOM_CONFIGS.resizeHandle(new ResizeColumn()),
+ children,
+ ],
})
this.processConfig(this.dom)
@@ -93,12 +90,9 @@ export default class Column extends Component {
if (evt.from !== evt.to) {
evt.from.classList.remove('hovering-column')
}
- // if (evt.related.parentElement.fType === 'columns') {
- // evt.related.parentElement.classList.add('hovering-column')
- // }
},
draggable: `.${FIELD_CLASSNAME}`,
- handle: '.item-handle',
+ handle: '.item-move',
})
}
@@ -111,30 +105,9 @@ export default class Column extends Component {
if (columnWidth) {
column.dataset.colWidth = columnWidth
column.style.width = columnWidth
- column.style.float = 'left'
}
}
- /**
- * Sets classes for legacy browsers to identify first and last fields in a block
- * consider removing
- * @param {DOM} column
- */
- fieldOrderClasses = () => {
- const fields = this.children.map(({ dom }) => dom)
-
- if (fields.length) {
- this.removeClasses(['first-field', 'last-field'])
- fields[0].classList.add('first-field')
- fields[fields.length - 1].classList.add('last-field')
- }
- }
-
- addChild(...args) {
- super.addChild(...args)
- this.fieldOrderClasses()
- }
-
// loops through children and refresh their edit panels
refreshFieldPanels = () => {
this.children.forEach(field => field.panels.nav.refresh())
diff --git a/src/lib/js/components/columns/event-handlers.js b/src/lib/js/components/columns/event-handlers.js
new file mode 100644
index 00000000..587f2ee1
--- /dev/null
+++ b/src/lib/js/components/columns/event-handlers.js
@@ -0,0 +1,225 @@
+import dom from '../../common/dom.js'
+import Components from '../index.js'
+import { percent, numToPercent } from '../../common/utils/index.mjs'
+import {
+ COLUMN_PRESET_CLASSNAME,
+ COLUMN_RESIZE_CLASSNAME,
+ CUSTOM_COLUMN_OPTION_CLASSNAME,
+ ROW_CLASSNAME,
+ bsColRegExp,
+} from '../../constants.js'
+import { map } from '../../common/helpers.mjs'
+
+export class ResizeColumn {
+ /**
+ * Binds the event handlers to the instance.
+ */
+ constructor() {
+ this.onMove = this.onMove.bind(this)
+ this.onStop = this.onStop.bind(this)
+ this.cleanup = this.cleanup.bind(this)
+ }
+
+ /**
+ * Calculates the total width of a row excluding the gaps between columns.
+ * @param {HTMLElement} row - The row element.
+ * @returns {number} - The total width of the row.
+ */
+ getRowWidth(row) {
+ const rowChildren = row.querySelector('.children')
+ if (!rowChildren) return 0
+
+ const numberOfColumns = rowChildren.children.length
+ const gapSize = dom.getStyle(rowChildren, 'gap') || '0px'
+ const gapPixels = parseFloat(gapSize, 10) || 0
+
+ // Cache the total gap width for potential future use
+ this.totalGapWidth = gapPixels * (numberOfColumns - 1)
+
+ return rowChildren.offsetWidth - this.totalGapWidth
+ }
+
+ /**
+ * Validates if the resize target columns are valid.
+ * @param {HTMLElement} column - The column being resized.
+ * @param {HTMLElement} sibling - The sibling column.
+ * @returns {boolean} - True if both columns are valid, false otherwise.
+ */
+ validateResizeTarget(column, sibling) {
+ return column && sibling && column.offsetWidth && sibling.offsetWidth
+ }
+
+ /**
+ * Handles the start of the resize event.
+ * @param {Event} evt - The event object.
+ */
+ onStart(evt) {
+ evt.preventDefault()
+ this.resized = false
+
+ if (evt.button !== 0) return
+
+ const column = evt.target.parentElement
+ const sibling = column.nextSibling || column.previousSibling
+ const row = column.closest(`.${ROW_CLASSNAME}`)
+
+ // Validate resize targets
+ if (!this.validateResizeTarget(column, sibling)) {
+ console.warn('Invalid resize targets')
+ this.cleanup()
+ return
+ }
+
+ this.startX = evt.type === 'touchstart' ? evt.touches[0].clientX : evt.clientX
+
+ row.classList.add(COLUMN_RESIZE_CLASSNAME)
+ this.columnPreset = row.querySelector(`.${COLUMN_PRESET_CLASSNAME}`)
+
+ // Store original classes in case we need to revert
+ this.originalColumnClass = column.className
+ this.originalSiblingClass = sibling.className
+
+ // remove bootstrap column classes since we are custom sizing
+ column.className = column.className.replace(bsColRegExp, '')
+ sibling.className = sibling.className.replace(bsColRegExp, '')
+
+ this.colStartWidth = column.offsetWidth
+ this.sibStartWidth = sibling.offsetWidth
+ this.rowWidth = this.getRowWidth(row)
+
+ // Validate calculated width
+ if (this.rowWidth <= 0) {
+ console.warn('Invalid row width calculated')
+ this.cleanup()
+ return
+ }
+
+ this.column = column
+ this.sibling = sibling
+ this.row = row
+
+ try {
+ window.addEventListener('pointermove', this.onMove, false)
+ window.addEventListener('pointerup', this.onStop, false)
+ } catch (error) {
+ console.error('Failed to initialize resize listeners:', error)
+ this.cleanup()
+ }
+ }
+
+ /**
+ * Calculates the new widths for the columns based on the mouse position.
+ * @param {number} clientX - The current X position of the mouse.
+ * @returns {Object|null} - The new widths for the columns or null if invalid.
+ */
+ calculateNewWidths(clientX) {
+ const newColWidth = this.colStartWidth + clientX - this.startX
+ const newSibWidth = this.sibStartWidth - clientX + this.startX
+
+ const colWidthPercent = parseFloat(percent(newColWidth, this.rowWidth))
+ const sibWidthPercent = parseFloat(percent(newSibWidth, this.rowWidth))
+
+ // Add minimum width check
+ if (colWidthPercent < 10 || sibWidthPercent < 10) {
+ return null
+ }
+
+ return {
+ colWidth: numToPercent(colWidthPercent.toFixed(1)),
+ siblingColWidth: numToPercent(sibWidthPercent.toFixed(1)),
+ }
+ }
+
+ /**
+ * Handles the movement during the resize event.
+ * @param {Event} evt - The event object.
+ */
+ onMove(evt) {
+ evt.preventDefault()
+ const { column, sibling } = this
+
+ const clientX = evt.type === 'touchmove' ? evt.touches[0].clientX : evt.clientX
+
+ const newWidths = this.calculateNewWidths(clientX)
+ if (!newWidths) return
+
+ const { colWidth, siblingColWidth } = newWidths
+
+ column.dataset.colWidth = colWidth
+ sibling.dataset.colWidth = siblingColWidth
+ column.style.width = colWidth
+ sibling.style.width = siblingColWidth
+ this.resized = true
+ }
+
+ onStop() {
+ const { column, sibling } = this
+
+ window.removeEventListener('pointermove', this.onMove)
+ window.removeEventListener('pointerup', this.onStop)
+
+ if (!this.resized) {
+ return
+ }
+
+ this.setCustomWidthValue()
+
+ Components.setAddress(`columns.${column.id}.config.width`, column.dataset.colWidth)
+ Components.setAddress(`columns.${sibling.id}.config.width`, sibling.dataset.colWidth)
+ this.row.classList.remove(COLUMN_RESIZE_CLASSNAME)
+
+ this.resized = false
+
+ this.cleanup()
+ }
+
+ // Helper method to clean up if resize fails
+ cleanup() {
+ if (this.column && this.originalColumnClass) {
+ this.column.className = this.originalColumnClass
+ }
+ if (this.sibling && this.originalSiblingClass) {
+ this.sibling.className = this.originalSiblingClass
+ }
+ if (this.row) {
+ this.row.classList.remove(COLUMN_RESIZE_CLASSNAME)
+ }
+ window.removeEventListener('pointermove', this.onMove)
+ window.removeEventListener('pointerup', this.onStop)
+ }
+
+ /**
+ * Adds a custom option from the column width present selecy
+ * @param {Node} row
+ */
+ setCustomWidthValue() {
+ const columnPreset = this.columnPreset
+ const customOption = columnPreset.querySelector(`.${CUSTOM_COLUMN_OPTION_CLASSNAME}`)
+ const cols = this.row.querySelector('.children').children
+ const widths = map(cols, col => percent(col.clientWidth, this.rowWidth).toFixed(1))
+ const value = widths.join(',')
+ const content = widths.join(' | ')
+
+ if (customOption) {
+ customOption.value = value
+ customOption.textContent = content
+ } else {
+ const newCustomOption = dom.create({
+ tag: 'option',
+ attrs: {
+ className: CUSTOM_COLUMN_OPTION_CLASSNAME,
+ value,
+ selected: true,
+ },
+ content,
+ })
+
+ columnPreset.append(newCustomOption)
+ }
+
+ columnPreset.value = value
+
+ return value
+ }
+}
+
diff --git a/src/lib/js/components/columns/events.js b/src/lib/js/components/columns/events.js
deleted file mode 100644
index e19143c8..00000000
--- a/src/lib/js/components/columns/events.js
+++ /dev/null
@@ -1,130 +0,0 @@
-import dom from '../../common/dom.js'
-import Components from '../index.js'
-import { percent, numToPercent } from '../../common/utils/index.mjs'
-import { ROW_CLASSNAME, bsColRegExp } from '../../constants.js'
-import { map } from '../../common/helpers.mjs'
-
-const CUSTOM_COLUMN_OPTION_CLASSNAME = 'custom-column-widths'
-const COLUMN_PRESET_CLASSNAME = 'column-preset'
-const COLUMN_RESIZE_CLASSNAME = 'resizing-columns'
-
-/**
- * Handle column resizing
- * @param {Object} evt resize event
- */
-export function resize(evt) {
- const resize = this
- const column = evt.target.parentElement
- const sibling = column.nextSibling || column.previousSibling
- const row = column.closest(`.${ROW_CLASSNAME}`)
- const rowStyle = dom.getStyle(row)
- const rowPadding = parseFloat(rowStyle.paddingLeft) + parseFloat(rowStyle.paddingRight)
- evt.target.removeEventListener('pointerdown', resize)
-
- /**
- * Set the width before resizing so the column
- * does not resize near window edges
- * @param {Object} evt
- */
- resize.move = ({ touches, type, clientX }) => {
- if (type === 'touchmove') {
- const [firstTouch] = touches
- clientX = firstTouch.clientX
- }
- const newColWidth = resize.colStartWidth + clientX - resize.startX
- const newSibWidth = resize.sibStartWidth - clientX + resize.startX
-
- const colWidthPercent = parseFloat(percent(newColWidth, resize.rowWidth))
- const sibWidthPercent = parseFloat(percent(newSibWidth, resize.rowWidth))
-
- const colWidth = numToPercent(colWidthPercent.toFixed(1))
- const siblingColWidth = numToPercent(sibWidthPercent.toFixed(1))
-
- column.dataset.colWidth = colWidth
- sibling.dataset.colWidth = siblingColWidth
- column.style.width = colWidth
- sibling.style.width = siblingColWidth
- resize.resized = true
- }
-
- resize.stop = function() {
- window.removeEventListener('pointermove', resize.move)
- window.removeEventListener('pointerup', resize.stop)
- window.removeEventListener('touchmove', resize.move)
- window.removeEventListener('touchend', resize.stop)
- if (!resize.resized) {
- return
- }
-
- setCustomWidthValue(row, resize.rowWidth)
- row.classList.remove(COLUMN_RESIZE_CLASSNAME)
-
- Components.setAddress(`columns.${column.id}.config.width`, column.dataset.colWidth)
- Components.setAddress(`columns.${sibling.id}.config.width`, sibling.dataset.colWidth)
- resize.resized = false
- }
-
- if (evt.type === 'touchstart') {
- const [firstTouch] = evt.touches
- resize.startX = firstTouch.clientX
- } else {
- resize.startX = evt.clientX
- }
- row.classList.add(COLUMN_RESIZE_CLASSNAME)
-
- // remove bootstrap column classes since we are custom sizing
- column.className.replace(bsColRegExp, '')
- sibling.className.replace(bsColRegExp, '')
-
- // eslint-disable-next-line
- resize.colStartWidth = column.offsetWidth || dom.getStyle(column, 'width')
- // eslint-disable-next-line
- resize.sibStartWidth = sibling.offsetWidth || dom.getStyle(sibling, 'width')
- resize.rowWidth = row.offsetWidth - rowPadding // compensate for padding
-
- window.addEventListener('pointerup', resize.stop, false)
- window.addEventListener('pointermove', resize.move, false)
- window.addEventListener('touchend', resize.stop, false)
- window.addEventListener('touchmove', resize.move, false)
-}
-
-/**
- * Removes a custom option from the column width present selecy
- * @param {Node} row
- * @return {Node} columnPreset input || null
- */
-export const removeCustomOption = (row, columnPreset = row.querySelector(`.${COLUMN_PRESET_CLASSNAME}`)) => {
- const customOption = columnPreset.querySelector(`.${CUSTOM_COLUMN_OPTION_CLASSNAME}`)
- return customOption && columnPreset.removeChild(customOption)
-}
-
-/**
- * Adds a custom option from the column width present selecy
- * @param {Node} row
- */
-export const setCustomWidthValue = (row, rowWidth) => {
- const columnPreset = row.querySelector(`.${COLUMN_PRESET_CLASSNAME}`)
- const customOption = columnPreset.querySelector(`.${CUSTOM_COLUMN_OPTION_CLASSNAME}`)
- const cols = row.querySelector('.children').children
- const widths = map(cols, col => percent(col.clientWidth, rowWidth).toFixed(1))
- const value = widths.join(',')
- const content = widths.join(' | ')
-
- if (customOption) {
- removeCustomOption(row, columnPreset)
- }
-
- const newCustomOption = dom.create({
- tag: 'option',
- attrs: {
- className: CUSTOM_COLUMN_OPTION_CLASSNAME,
- value,
- },
- content,
- })
-
- columnPreset.add(newCustomOption)
- columnPreset.value = value
-
- return value
-}
diff --git a/src/lib/js/components/columns/index.js b/src/lib/js/components/columns/index.js
index 4ab41d45..52a38c77 100644
--- a/src/lib/js/components/columns/index.js
+++ b/src/lib/js/components/columns/index.js
@@ -3,7 +3,7 @@ import Column from './column.js'
const DEFAULT_CONFIG = {
actionButtons: {
- buttons: ['clone', 'handle', 'remove'],
+ buttons: ['clone', 'move', 'remove'],
disabled: [],
},
}
diff --git a/src/lib/js/components/component.js b/src/lib/js/components/component.js
index 86ffc66c..842bfb00 100644
--- a/src/lib/js/components/component.js
+++ b/src/lib/js/components/component.js
@@ -1,5 +1,5 @@
/* global MutationObserver */
-import { uuid, componentType, merge, clone, remove, identity } from '../common/utils/index.mjs'
+import { uuid, componentType, merge, clone, remove, identity, closest } from '../common/utils/index.mjs'
import { isInt, map, forEach, indexOfNode } from '../common/helpers.mjs'
import dom from '../common/dom.js'
import {
@@ -10,12 +10,14 @@ import {
COMPONENT_TYPE_CLASSNAMES,
COLUMN_CLASSNAME,
CONTROL_GROUP_CLASSNAME,
+ COMPONENT_TYPES,
} from '../constants.js'
import Components from './index.js'
import Data from './data.js'
import animate from '../common/animation.js'
import Controls from './controls/index.js'
import { get } from '../common/utils/object.mjs'
+import { toTitleCase } from '../common/utils/string.mjs'
export default class Component extends Data {
constructor(name, data = {}, render) {
@@ -39,6 +41,7 @@ export default class Component extends Data {
this.observer.disconnect()
this.observer.observe(container, { childList: true })
}
+
get js() {
return this.data
}
@@ -109,28 +112,46 @@ export default class Component extends Data {
* @return {Object} element config object
*/
getActionButtons() {
- let expandSize = '97px'
- const hoverClassname = `hovering-${this.name}`
+ const hoverClassnames = [`hovering-${this.name}`, 'hovering']
return {
- className: `${this.name}-actions group-actions`,
+ className: [`${this.name}-actions`, 'group-actions'],
action: {
- onRender: elem => (expandSize = `${elem.getElementsByTagName('button').length * 24 + 1}px`),
mouseenter: ({ target }) => {
- this.dom.classList.add(hoverClassname)
- target.style[this.name === 'row' ? 'height' : 'width'] = expandSize
+ Components.stages.active.dom.classList.add(`active-hover-${this.name}`)
+ this.dom.classList.add(...hoverClassnames)
},
mouseleave: ({ target }) => {
- this.dom.classList.remove(hoverClassname)
+ this.dom.classList.remove(...hoverClassnames)
+ Components.stages.active.dom.classList.remove(`active-hover-${this.name}`)
target.removeAttribute('style')
},
},
- children: {
- className: 'action-btn-wrap',
- children: this.buttons,
- },
+ children: [
+ {
+ ...dom.btnTemplate({ content: dom.icon(`handle-${this.name}`) }),
+ className: ['component-handle', `${this.name}-handle`],
+ },
+ {
+ className: ['action-btn-wrap', `${this.name}-action-btn-wrap`],
+ children: this.buttons,
+ },
+ ],
}
}
+ getComponentTag = () => {
+ return dom.create({
+ tag: 'span',
+ className: ['component-tag', `${this.name}-tag`],
+ children: [
+ (this.isColumn || this.isField) && dom.icon('component-corner', ['bottom-left']),
+ dom.icon(`handle-${this.name}`),
+ toTitleCase(this.name),
+ (this.isColumn || this.isRow) && dom.icon('component-corner', ['bottom-right']),
+ ].filter(Boolean),
+ })
+ }
+
/**
* Toggles the edit window
* @param {Boolean} open whether to open or close the edit window
@@ -138,34 +159,44 @@ export default class Component extends Data {
toggleEdit(open = !this.isEditing) {
this.isEditing = open
const element = this.dom
- const editClass = `editing-${this.name}`
+ const editingClassName = 'editing'
+ const editingComponentClassname = `${editingClassName}-${this.name}`
const editWindow = this.dom.querySelector(`.${this.name}-edit`)
animate.slideToggle(editWindow, ANIMATION_SPEED_BASE, open)
if (this.name === 'field') {
animate.slideToggle(this.preview, ANIMATION_SPEED_BASE, !open)
- element.parentElement.classList.toggle(`column-${editClass}`, open)
+ element.parentElement.classList.toggle(`column-${editingComponentClassname}`, open)
}
- element.classList.toggle(editClass, open)
+ element.classList.toggle(editingClassName, open)
+ element.classList.toggle(editingComponentClassname, open)
}
get buttons() {
const _this = this
- const parseIcons = icons => icons.map(icon => dom.icon(icon))
+ if (this.actionButtons) {
+ return this.actionButtons
+ }
+
+ // const parseIcons = icons => icons.map(icon => dom.icon(icon))
const buttonConfig = {
- handle: (icons = ['move', 'handle']) => {
+ handle: (icon = `handle-${this.name}`) => ({
+ ...dom.btnTemplate({ content: dom.icon(icon) }),
+ className: ['component-handle'],
+ }),
+ move: (icon = 'move') => {
return {
- ...dom.btnTemplate({ content: parseIcons(icons) }),
- className: ['item-handle'],
+ ...dom.btnTemplate({ content: dom.icon(icon) }),
+ className: ['item-move'],
meta: {
- id: 'handle',
+ id: 'move',
},
}
},
- edit: (icons = ['edit']) => {
+ edit: (icon = 'edit') => {
return {
- ...dom.btnTemplate({ content: parseIcons(icons) }),
+ ...dom.btnTemplate({ content: dom.icon(icon) }),
className: ['item-edit-toggle'],
meta: {
id: 'edit',
@@ -177,9 +208,9 @@ export default class Component extends Data {
},
}
},
- remove: (icons = ['remove']) => {
+ remove: (icon = 'remove') => {
return {
- ...dom.btnTemplate({ content: parseIcons(icons) }),
+ ...dom.btnTemplate({ content: dom.icon(icon) }),
className: ['item-remove'],
meta: {
id: 'remove',
@@ -200,15 +231,20 @@ export default class Component extends Data {
},
}
},
- clone: (icons = ['copy', 'handle']) => {
+ clone: (icon = 'copy') => {
return {
- ...dom.btnTemplate({ content: parseIcons(icons) }),
+ ...dom.btnTemplate({ content: dom.icon(icon) }),
className: ['item-clone'],
meta: {
id: 'clone',
},
action: {
- click: () => this.clone(),
+ click: () => {
+ this.clone(this.parent)
+ if (this.name === 'column') {
+ this.parent.autoColumnWidths()
+ }
+ },
},
}
},
@@ -216,8 +252,11 @@ export default class Component extends Data {
const { buttons, disabled } = this.config.actionButtons
const activeButtons = buttons.filter(btn => !disabled.includes(btn))
+ const actionButtonsConfigs = activeButtons.map(btn => buttonConfig[btn]?.() || btn)
- return activeButtons.map(btn => buttonConfig[btn]?.() || btn)
+ this.actionButtons = actionButtonsConfigs
+
+ return this.actionButtons
}
/**
@@ -562,27 +601,26 @@ export default class Component extends Data {
})
}
- cloneData = () => {
+ cloneData() {
const clonedData = { ...clone(this.data), id: uuid() }
if (this.name !== 'field') {
clonedData.children = []
}
+
return clonedData
}
- clone = (parent = this.parent) => {
+ clone(parent = this.parent) {
const newClone = parent.addChild(this.cloneData(), this.index + 1)
if (this.name !== 'field') {
this.cloneChildren(newClone)
}
- if (this.name === 'column') {
- parent.autoColumnWidths()
- }
+
return newClone
}
- cloneChildren = toParent => {
- this.children.forEach(child => child && child.clone(toParent))
+ cloneChildren(toParent) {
+ this.children.forEach(child => child?.clone(toParent))
}
createChildWrap = children =>
@@ -593,4 +631,27 @@ export default class Component extends Data {
},
children,
})
+
+ get isRow() {
+ return this.name === COMPONENT_TYPES.row
+ }
+ get isColumn() {
+ return this.name === COMPONENT_TYPES.column
+ }
+ get isField() {
+ return this.name === COMPONENT_TYPES.field
+ }
+
+ // set(path, val) {
+ // super.set(path, val)
+ // debugger
+ // const [key, ...rest] = path.split('.')
+ // const parent = this.get(rest.slice(0, rest.length - 1).join('.'))
+ // const property = rest.slice(rest.length - 1, rest.length).join('.')
+ // const value = val || this.get(path)
+ // if (parent) {
+ // parent[key] = { ...parent[key], [property]: value }
+ // }
+ // return this.get(path)
+ // }
}
diff --git a/src/lib/js/components/data.js b/src/lib/js/components/data.js
index 8d9fdefa..dfbff878 100644
--- a/src/lib/js/components/data.js
+++ b/src/lib/js/components/data.js
@@ -2,6 +2,7 @@ import { uuid } from '../common/utils/index.mjs'
import events from '../common/events.js'
import { CHANGE_TYPES } from '../constants.js'
import { get, set } from '../common/utils/object.mjs'
+import isEqual from 'lodash/isEqual'
export default class Data {
constructor(name, data = Object.create(null)) {
@@ -28,14 +29,14 @@ export default class Data {
set(path, newVal) {
const oldVal = get(this.data, path)
- // if (isEqual(oldVal, newVal)) {
- // return this.data
- // }
+ if (isEqual(oldVal, newVal)) {
+ return this.data
+ }
const data = set(this.data, path, newVal)
- const callBackPath = Array.isArray(path) ? path.join('.') : path
- const callBackGroups = Object.keys(this.setCallbacks).filter(setKey => new RegExp(setKey).test(callBackPath))
+ const callbackPath = Array.isArray(path) ? path.join('.') : path
+ const callBackGroups = Object.keys(this.setCallbacks).filter(setKey => new RegExp(setKey).test(callbackPath))
const cbArgs = { newVal, oldVal, path }
callBackGroups.forEach(cbGroup => this.setCallbacks[cbGroup].forEach(cb => cb(cbArgs)))
diff --git a/src/lib/js/components/fields/field.js b/src/lib/js/components/fields/field.js
index bd1d43e4..43117e25 100644
--- a/src/lib/js/components/fields/field.js
+++ b/src/lib/js/components/fields/field.js
@@ -29,16 +29,17 @@ export default class Field extends Component {
this.editPanels = []
const actionButtons = this.getActionButtons()
- const hasEditButton = actionButtons.children.children.some(child => child.meta?.id === 'edit')
+ const hasEditButton = this.actionButtons.some(child => child.meta?.id === 'edit')
let field = {
tag: 'li',
attrs: {
- className: FIELD_CLASSNAME,
+ className: [FIELD_CLASSNAME],
},
id: this.id,
children: [
this.label,
+ this.getComponentTag(),
actionButtons,
hasEditButton && this.fieldEdit, // fieldEdit window,
this.preview,
@@ -48,8 +49,8 @@ export default class Field extends Component {
hoverTag: i18n.get('field'),
},
action: {
- mouseenter: () => this.dom.classList.add(`hovering-${this.name}`),
- mouseleave: () => this.dom.classList.remove(`hovering-${this.name}`),
+ // mouseenter: () => this.dom.classList.add(`hovering-${this.name}`),
+ // mouseleave: () => this.dom.classList.remove(`hovering-${this.name}`),
},
}
diff --git a/src/lib/js/components/fields/index.js b/src/lib/js/components/fields/index.js
index e0717be5..03ba52f5 100644
--- a/src/lib/js/components/fields/index.js
+++ b/src/lib/js/components/fields/index.js
@@ -5,7 +5,7 @@ import { get } from '../../common/utils/object.mjs'
const DEFAULT_CONFIG = {
actionButtons: {
- buttons: ['handle', 'edit', 'clone', 'remove'],
+ buttons: ['move', 'edit', 'clone', 'remove'],
disabled: [],
},
panels: {
diff --git a/src/lib/js/components/index.js b/src/lib/js/components/index.js
index c8549fae..2f8051ff 100644
--- a/src/lib/js/components/index.js
+++ b/src/lib/js/components/index.js
@@ -46,11 +46,13 @@ export class Components extends Data {
formData = JSON.parse(formData)
}
this.opts = opts
- const { stages = { [uuid()]: {} }, rows, columns, fields, id = uuid() } = Object.assign(
- {},
- this.sessionFormData(),
- formData
- )
+ const {
+ stages = { [uuid()]: {} },
+ rows,
+ columns,
+ fields,
+ id = uuid(),
+ } = { ...this.sessionFormData(), ...formData }
this.set('id', id)
this.add('stages', Stages.load(stages))
this.add('rows', Rows.load(rows))
@@ -103,13 +105,13 @@ export class Components extends Data {
/**
* call `set` on a component in memory
*/
- setAddress(address, value) {
- const [type, id, ...path] = Array.isArray(address) ? address : address.split('.')
+ setAddress(fullAddress, value) {
+ const [type, id, ...localAddress] = Array.isArray(fullAddress) ? fullAddress : fullAddress.split('.')
const componentType = type.replace(/s?$/, 's')
const component = this[componentType].get(id)
- if (component) {
- component.set(path, value)
- }
+
+ component?.set(localAddress, value)
+
return component
}
diff --git a/src/lib/js/components/rows/index.js b/src/lib/js/components/rows/index.js
index 98e186fc..19afa74f 100644
--- a/src/lib/js/components/rows/index.js
+++ b/src/lib/js/components/rows/index.js
@@ -3,7 +3,7 @@ import Row from './row.js'
const DEFAULT_CONFIG = {
actionButtons: {
- buttons: ['handle', 'edit', 'clone', 'remove'],
+ buttons: ['move', 'edit', 'clone', 'remove'],
disabled: [],
},
}
diff --git a/src/lib/js/components/rows/row.js b/src/lib/js/components/rows/row.js
index 45e56549..fc412bb1 100644
--- a/src/lib/js/components/rows/row.js
+++ b/src/lib/js/components/rows/row.js
@@ -4,8 +4,19 @@ import Component from '../component.js'
import dom from '../../common/dom.js'
import events from '../../common/events.js'
import { numToPercent } from '../../common/utils/index.mjs'
-import { ROW_CLASSNAME, COLUMN_TEMPLATES, ANIMATION_SPEED_FAST, COLUMN_CLASSNAME, bsColRegExp } from '../../constants.js'
-import { removeCustomOption } from '../columns/events.js'
+import {
+ ROW_CLASSNAME,
+ COLUMN_TEMPLATES,
+ ANIMATION_SPEED_FAST,
+ COLUMN_CLASSNAME,
+ bsColRegExp,
+ CUSTOM_COLUMN_OPTION_CLASSNAME,
+ COLUMN_PRESET_CLASSNAME,
+} from '../../constants.js'
+import columnsData from '../columns/index.js'
+import data from '../data.js'
+import components from '../index.js'
+import { forEach } from 'lodash'
const DEFAULT_DATA = () =>
Object.freeze({
@@ -28,7 +39,7 @@ export default class Row extends Component {
* @return {Object}
*/
constructor(rowData) {
- super('row', Object.assign({}, DEFAULT_DATA(), rowData))
+ super('row', { ...DEFAULT_DATA(), ...rowData })
const children = this.createChildWrap()
@@ -40,7 +51,7 @@ export default class Row extends Component {
editingHoverTag: i18n.get('editing.row'),
},
id: this.id,
- content: [this.getActionButtons(), this.editWindow, children],
+ content: [this.getComponentTag(), this.getActionButtons(), this.editWindow, children],
})
this.sortable = Sortable.create(children, {
@@ -58,12 +69,10 @@ export default class Row extends Component {
onEnd: this.onEnd.bind(this),
onAdd: this.onAdd.bind(this),
onSort: this.onSort.bind(this),
- filter: '.resize-x-handle',
+ // filter: '.resize-x-handle', // use filter for frozen columns
draggable: `.${COLUMN_CLASSNAME}`,
- handle: '.item-handle',
+ handle: '.item-move',
})
-
- this.onRender()
}
/**
@@ -72,9 +81,7 @@ export default class Row extends Component {
*/
get editWindow() {
const _this = this
- const editWindow = {
- className: `${this.name}-edit group-config`,
- }
+
const fieldsetLabel = {
tag: 'label',
content: i18n.get('row.settings.fieldsetWrap'),
@@ -111,9 +118,6 @@ export default class Row extends Component {
},
}
- // let fieldsetAddon = Object.assign({}, fieldsetLabel, {
- // content: [fieldsetInput, ' Fieldset']
- // });
const inputAddon = {
tag: 'span',
className: 'input-group-addon',
@@ -144,20 +148,31 @@ export default class Row extends Component {
content: i18n.get('defineColumnWidths'),
className: 'col-sm-4 form-control-label',
}
+ this.columnPresetControl = dom.create(this.columnPresetControlConfig)
const columnSettingsPresetSelect = {
className: 'col-sm-8',
- content: {
- className: 'column-preset',
- },
+ content: this.columnPresetControl,
action: {
- onRender: evt => {
+ onRender: () => {
this.updateColumnPreset()
},
},
}
const columnSettingsPreset = dom.formGroup([columnSettingsPresetLabel, columnSettingsPresetSelect], 'row')
+ const editWindowContents = [inputGroupInput, 'hr', fieldSetControls, 'hr', columnSettingsPreset]
- editWindow.children = [inputGroupInput, dom.create('hr'), fieldSetControls, dom.create('hr'), columnSettingsPreset]
+ const editWindow = dom.create({
+ className: `${this.name}-edit group-config`,
+ action: {
+ onRender: editWindow => {
+ const timeout = setTimeout(() => {
+ const elements = editWindowContents.map(elem => dom.create(elem))
+ editWindow.append(...elements)
+ clearTimeout(timeout)
+ }, 1000)
+ },
+ },
+ })
return editWindow
}
@@ -199,6 +214,7 @@ export default class Row extends Component {
}, ANIMATION_SPEED_FAST)
document.dispatchEvent(events.columnResized)
})
+
this.updateColumnPreset()
}
@@ -207,13 +223,15 @@ export default class Row extends Component {
* @return {Object} columnPresetConfig
*/
updateColumnPreset = () => {
- const oldColumnPreset = this.dom.querySelector('.column-preset')
- const rowEdit = oldColumnPreset.parentElement
- const columnPresetConfig = this.columnPresetControl(this.id)
- const newColumnPreset = dom.create(columnPresetConfig)
-
- rowEdit.replaceChild(newColumnPreset, oldColumnPreset)
- return columnPresetConfig
+ this.columnPresetControl.innerHTML = ''
+ const presetOptions = this.getColumnPresetOptions.map(({ label, ...attrs }) =>
+ dom.create({
+ tag: 'option',
+ content: label,
+ attrs,
+ }),
+ )
+ this.columnPresetControl.append(...presetOptions)
}
/**
@@ -222,9 +240,6 @@ export default class Row extends Component {
* @param {String} widths
*/
setColumnWidths = widths => {
- if (widths === 'custom') {
- return
- }
if (typeof widths === 'string') {
widths = widths.split(',')
}
@@ -235,30 +250,17 @@ export default class Row extends Component {
}
/**
- * Generates the element config for column layout in row
- * @return {Object} columnPresetControlConfig
+ * Retrieves the preset options for columns based on the current configuration.
+ *
+ * @returns {Array