@themarioga/grid-editor 9.0.0-beta.2 → 9.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +76 -8
  3. package/dist/grideditor.bundle.min.js +2 -2
  4. package/dist/grideditor.css +187 -0
  5. package/dist/grideditor.d.ts +75 -0
  6. package/dist/grideditor.esm.js +38 -11
  7. package/dist/grideditor.js +38 -11
  8. package/dist/grideditor.min.css +1 -1
  9. package/dist/grideditor.min.css.map +1 -1
  10. package/dist/grideditor.min.js +1 -1
  11. package/dist/grideditor.min.js.map +3 -3
  12. package/dist/locales/grideditor.es.esm.js +22 -0
  13. package/dist/locales/grideditor.es.js +22 -0
  14. package/dist/locales/grideditor.es.min.js +2 -2
  15. package/dist/locales/grideditor.es.min.js.map +2 -2
  16. package/dist/plugins/grideditor.history.esm.js +360 -0
  17. package/dist/plugins/grideditor.history.js +365 -0
  18. package/dist/plugins/grideditor.history.min.js +1 -0
  19. package/dist/plugins/grideditor.history.min.js.map +7 -0
  20. package/dist/plugins/grideditor.offcanvas.esm.js +551 -0
  21. package/dist/plugins/grideditor.offcanvas.js +556 -0
  22. package/dist/plugins/grideditor.offcanvas.min.js +1 -0
  23. package/dist/plugins/grideditor.offcanvas.min.js.map +7 -0
  24. package/dist/plugins/grideditor.popup.esm.js +90 -37
  25. package/dist/plugins/grideditor.popup.js +90 -37
  26. package/dist/plugins/grideditor.popup.min.js +1 -1
  27. package/dist/plugins/grideditor.popup.min.js.map +4 -4
  28. package/docs/events.md +7 -3
  29. package/docs/locale-keys.md +32 -1
  30. package/docs/plugins.md +19 -5
  31. package/example/clipboard.html +22 -0
  32. package/example/containers.html +22 -4
  33. package/example/history.html +94 -0
  34. package/example/index.html +9 -1
  35. package/package.json +1 -1
  36. package/src/js/grideditor.js +37 -9
  37. package/src/js/locales/grideditor.es.js +22 -0
  38. package/src/js/plugins/grideditor.history.js +367 -0
  39. package/src/js/plugins/grideditor.offcanvas.js +420 -0
  40. package/src/js/plugins/grideditor.popup.js +8 -70
  41. package/src/js/triggers/triggers.js +121 -0
  42. package/src/less/grideditor.less +60 -0
  43. package/types/grideditor.d.ts +75 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../../src/js/register.js", "../../src/js/dom.js", "../../src/js/plugins/grideditor.popup.js"],
4
- "sourcesContent": ["/**\n * What a plugin's or a locale's classic script imports in place of the\n * editor: the GridEditor the page already loaded.\n *\n * The build points `import { GridEditor } from '../grideditor.js'` here for\n * the classic scripts, so each registers on the editor the page has rather\n * than bringing its own. Loaded before the editor there is nothing to\n * register on, and saying so is better than a TypeError three calls later.\n */\nvar GridEditor = window.GridEditor;\n\nif (!GridEditor || typeof GridEditor.get !== 'function') {\n throw new Error('grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, ' +\n 'its locales and grideditor.jquery.js');\n}\n\nexport { GridEditor };\nexport default GridEditor;\n", "/**\n * The DOM, the way grid-editor uses it.\n *\n * Small helpers over the browser's own API, for the core and the shipped\n * plugins. Not a public API: a plugin of a host's own is free to write its\n * DOM code however it likes, and this file makes no promise to it.\n *\n * Stateless on purpose. The build puts a copy of this file in each plugin's\n * classic script, so anything kept here would be kept once per copy; a\n * module that needs to remember something about a node keeps its own\n * WeakMap.\n */\n\n/** Every element under `root` matching `selector`, as an array. */\nexport function all(root, selector) {\n return root ? Array.prototype.slice.call(root.querySelectorAll(selector)) : [];\n}\n\n/** The first element under `root` matching `selector`, or null. */\nexport function one(root, selector) {\n return root ? root.querySelector(selector) : null;\n}\n\n/** `root` itself when it matches, then every element under it that does. */\nexport function selfAndAll(root, selector) {\n if (!root) { return []; }\n\n var found = all(root, selector);\n if (root.nodeType === 1 && root.matches(selector)) { found.unshift(root); }\n return found;\n}\n\n/** The element children of `node`, those matching `selector` if one is given. */\nexport function children(node, selector) {\n if (!node) { return []; }\n\n return Array.prototype.filter.call(node.children, function(each) {\n return !selector || each.matches(selector);\n });\n}\n\n/** The first element child matching `selector`, or null. */\nexport function child(node, selector) {\n return children(node, selector)[0] || null;\n}\n\n/** The nearest ancestor-or-self matching `selector` below `stopAt`, or null. */\nexport function closest(node, selector, stopAt) {\n var found = node && node.nodeType === 1 ? node.closest(selector) : null;\n\n if (found && stopAt && found !== stopAt && !stopAt.contains(found)) { return null; }\n return found;\n}\n\n/** Whether `node` is an element matching `selector`. */\nexport function is(node, selector) {\n return !!node && node.nodeType === 1 && node.matches(selector);\n}\n\n/** Whether `node` is in the document. */\nexport function attached(node) {\n return !!node && document.documentElement.contains(node);\n}\n\n/** The element siblings after `node` that match `selector`. */\nexport function nextAll(node, selector) {\n var found = [];\n\n for (var next = node.nextElementSibling; next; next = next.nextElementSibling) {\n if (!selector || next.matches(selector)) { found.push(next); }\n }\n return found;\n}\n\n/** The ancestors of `node` up to, and not including, `stop`, nearest first. */\nexport function parentsUntil(node, stop) {\n var found = [];\n\n for (var parent = node.parentElement; parent && parent !== stop; parent = parent.parentElement) {\n found.push(parent);\n }\n return found;\n}\n\n/**\n * The nodes the html parses into, detached. Parsed the way innerHTML parses,\n * so a <script> in it never runs, here or once it is in the page.\n */\nexport function parse(html) {\n var template = document.createElement('template');\n template.innerHTML = html;\n return Array.prototype.slice.call(template.content.childNodes);\n}\n\n/** The one element the html is, detached. */\nexport function create(html) {\n var nodes = parse(html.trim());\n\n for (var i = 0; i < nodes.length; i++) {\n if (nodes[i].nodeType === 1) { return nodes[i]; }\n }\n return null;\n}\n\n/** An element of `tag`, with `attributes` and `text`. */\nexport function element(tag, attributes, text) {\n var node = document.createElement(tag);\n\n Object.keys(attributes || {}).forEach(function(name) {\n var value = attributes[name];\n if (value !== null && value !== undefined && value !== false) { node.setAttribute(name, value); }\n });\n if (text !== undefined && text !== null) { node.textContent = text; }\n\n return node;\n}\n\n/** Replace what `node` holds with `html`. A <script> in it does not run. */\nexport function setHtml(node, html) {\n node.innerHTML = html === undefined || html === null ? '' : String(html);\n return node;\n}\n\n/** Add one or several space separated classes. */\nexport function addClass(node, names) {\n split(names).forEach(function(name) { node.classList.add(name); });\n return node;\n}\n\nexport function removeClass(node, names) {\n split(names).forEach(function(name) { node.classList.remove(name); });\n return node;\n}\n\n/** Each class on, or off, or flipped when `on` is not given. */\nexport function toggleClass(node, names, state) {\n split(names).forEach(function(name) {\n if (state === undefined) {\n node.classList.toggle(name);\n } else {\n node.classList.toggle(name, !!state);\n }\n });\n return node;\n}\n\nexport function hasClass(node, name) {\n return !!node && node.nodeType === 1 && node.classList.contains(name);\n}\n\nfunction split(names) {\n return String(names || '').split(/\\s+/).filter(Boolean);\n}\n\n/** Take the class attribute off when there is nothing left in it. */\nexport function dropEmptyClass(node) {\n if (!node.getAttribute('class')) { node.removeAttribute('class'); }\n return node;\n}\n\n/** Take the style attribute off when there is nothing left in it. */\nexport function dropEmptyStyle(node) {\n if (!node.getAttribute('style')) { node.removeAttribute('style'); }\n return node;\n}\n\n/** Set attributes; a null or undefined value takes the attribute off. */\nexport function attr(node, attributes) {\n Object.keys(attributes).forEach(function(name) {\n var value = attributes[name];\n\n if (value === null || value === undefined) {\n node.removeAttribute(name);\n } else {\n node.setAttribute(name, value);\n }\n });\n return node;\n}\n\n/**\n * Inline styles, by css property name. A number is pixels for the properties\n * that take a length, as jQuery had it; an empty string takes one off.\n */\nexport function css(node, styles) {\n Object.keys(styles).forEach(function(property) {\n var value = styles[property];\n\n if (typeof value === 'number' && !UNITLESS[property]) { value = value + 'px'; }\n node.style.setProperty(property, value === null || value === undefined ? '' : String(value));\n });\n return node;\n}\n\nvar UNITLESS = { opacity: true, 'z-index': true, 'flex-grow': true, 'flex-shrink': true, order: true };\n\n/** What an element of this tag shows as when nothing hides it. */\nvar defaultDisplays = {};\n\nfunction defaultDisplay(node) {\n var tag = node.nodeName;\n\n if (!defaultDisplays[tag]) {\n var probe = document.body.appendChild(document.createElement(tag));\n defaultDisplays[tag] = getComputedStyle(probe).display;\n probe.remove();\n if (defaultDisplays[tag] === 'none') { defaultDisplays[tag] = 'block'; }\n }\n return defaultDisplays[tag];\n}\n\n/** Show a node: its inline display:none off, and a display of its own if a stylesheet hides it. */\nexport function show(node) {\n node.style.removeProperty('display');\n if (getComputedStyle(node).display === 'none') { node.style.display = defaultDisplay(node); }\n return node;\n}\n\nexport function hide(node) {\n node.style.display = 'none';\n return node;\n}\n\n/** Show or hide; flip when `on` is not given. */\nexport function toggle(node, state) {\n if (state === undefined) { state = !visible(node); }\n return state ? show(node) : hide(node);\n}\n\n/** Whether a node takes up room: what jQuery's :visible meant. */\nexport function visible(node) {\n return !!(node.offsetWidth || node.offsetHeight || node.getClientRects().length);\n}\n\n/** The border box's width, fractions included. */\nexport function outerWidth(node) {\n return node.getBoundingClientRect().width;\n}\n\nexport function outerHeight(node) {\n return node.getBoundingClientRect().height;\n}\n\n/** The content box's height, which is what jQuery's height() gave. */\nexport function contentHeight(node) {\n var style = getComputedStyle(node);\n return node.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);\n}\n\n/** Where a node is on the page, not in the window. */\nexport function offset(node) {\n var box = node.getBoundingClientRect();\n return { top: box.top + window.pageYOffset, left: box.left + window.pageXOffset };\n}\n\n/** Its index among its parent's element children, those matching `selector` if given. */\nexport function indexIn(node, selector) {\n return children(node.parentElement, selector).indexOf(node);\n}\n\n/** Put `node` right after `reference`. */\nexport function insertAfter(node, reference) {\n reference.parentNode.insertBefore(node, reference.nextSibling);\n return node;\n}\n\n/** Replace `node` with what it holds. */\nexport function unwrap(node) {\n var parent = node.parentNode;\n\n while (node.firstChild) { parent.insertBefore(node.firstChild, node); }\n node.remove();\n}\n\n/** Put `node` inside `wrapper`, where `node` was. */\nexport function wrap(node, wrapper) {\n node.parentNode.insertBefore(wrapper, node);\n wrapper.appendChild(node);\n return wrapper;\n}\n\n/**\n * Listen on `root` for `types` (space separated) on descendants that match\n * `selector`, with `this` the matched element, as jQuery's delegated\n * handlers had it. Hands back the listener, for removeEventListener; a\n * signal in `options` removes it too.\n */\nexport function delegate(root, types, selector, handler, options) {\n var listener = function(event) {\n var match = closest(event.target, selector, root);\n if (match && root.contains(match)) { return handler.call(match, event); }\n return undefined;\n };\n\n split(types).forEach(function(type) { root.addEventListener(type, listener, options); });\n return listener;\n}\n\n/** Listen for several space separated event types with one handler. */\nexport function on(target, types, handler, options) {\n split(types).forEach(function(type) { target.addEventListener(type, handler, options); });\n return handler;\n}\n\nexport function off(target, types, handler, options) {\n split(types).forEach(function(type) { target.removeEventListener(type, handler, options); });\n}\n\n/** A mouse event of `type` sent to `node`, the way a pointer would. */\nexport function fire(node, type) {\n node.dispatchEvent(new MouseEvent(type, { bubbles: type !== 'mouseenter' && type !== 'mouseleave' }));\n}\n\n/* ------------------------------------------------------------------\n * Taking a node away, visibly.\n *\n * The durations are jQuery's: 400 ms unless the caller says otherwise. `done` runs once the node is out of sight, which is\n * when the caller removes it; a node that is already out of sight is done at\n * once, on the next task, as jQuery had it.\n * ------------------------------------------------------------------ */\n\nvar DEFAULT_DURATION = 400;\n\nfunction animateAway(node, frames, duration, done) {\n duration = duration === undefined ? DEFAULT_DURATION : duration;\n\n var finish = function() {\n hide(node);\n if (done) { done(); }\n };\n\n if (!visible(node) || !node.animate || duration <= 0) {\n window.setTimeout(finish, 0);\n return;\n }\n\n var animation = node.animate(frames(getComputedStyle(node)), {\n duration: duration,\n easing: 'ease-in-out',\n });\n var called = false;\n var once = function() {\n if (called) { return; }\n called = true;\n finish();\n };\n\n animation.onfinish = once;\n animation.oncancel = once;\n}\n\n/** Fold a node up to nothing, then hide it. */\nexport function slideUp(node, duration, done) {\n if (typeof duration === 'function') {\n done = duration;\n duration = undefined;\n }\n\n node.style.overflow = 'hidden';\n animateAway(node, function(style) {\n return [\n {\n height: style.height,\n paddingTop: style.paddingTop,\n paddingBottom: style.paddingBottom,\n marginTop: style.marginTop,\n marginBottom: style.marginBottom,\n },\n { height: '0px', paddingTop: '0px', paddingBottom: '0px', marginTop: '0px', marginBottom: '0px' },\n ];\n }, duration, function() {\n node.style.removeProperty('overflow');\n if (done) { done(); }\n });\n}\n\n/** Fade a node out, then hide it. */\nexport function fadeOut(node, duration, done) {\n animateAway(node, function(style) {\n return [{ opacity: style.opacity }, { opacity: 0 }];\n }, duration, done);\n}\n\n/** Fade and fold a node, in height and, with `width`, sideways too, then hide it. */\nexport function shrinkAway(node, duration, done, width) {\n node.style.overflow = 'hidden';\n animateAway(node, function(style) {\n var from = { opacity: style.opacity, height: style.height };\n var to = { opacity: 0, height: '0px' };\n\n if (width) {\n from.width = style.width;\n to.width = '0px';\n }\n return [from, to];\n }, duration, function() {\n node.style.removeProperty('overflow');\n if (done) { done(); }\n });\n}\n", "/**\n * Popups for grid-editor.\n *\n * A container plugin: load this file after the editor and the toolbar offers\n * a popup: a bootstrap modal and its trigger. What it can ask the editor for is the handle its factory is\n * called with, described in docs/plugins.md.\n *\n * <script src=\"dist/grideditor.min.js\"></script>\n * <script src=\"dist/plugins/grideditor.popup.min.js\"></script>\n */\nimport { GridEditor } from '../grideditor.js';\nimport * as dom from '../dom.js';\n\nObject.assign(GridEditor.locales.en, {\n 'container.add_popup': 'Popup',\n 'container.popup_title': 'Title',\n 'container.popup_trigger': 'Open',\n 'tool.toggle_popup': 'Fold this popup away while editing',\n});\n\nGridEditor.containers.popup = function(ge) {\n\n function popupIdOf(container) {\n return container.getAttribute('data-ge-popup-id');\n }\n\n /** Whether a popup with this id is on the canvas. */\n function popupExists(id, root) {\n return dom.all(root || ge.canvas, '[data-ge-popup-id]').some(function(popup) {\n return popupIdOf(popup) === id;\n });\n }\n\n function wirePopupTriggers() {\n dom.all(ge.canvas, '[data-ge-popup-target]').forEach(function(trigger) {\n dom.addClass(trigger, 'ge-popup-trigger');\n var wanted = trigger.getAttribute('data-ge-popup-target');\n\n // Bootstrap's own attributes are written at getHtml time, not\n // while editing, so a click here cannot open a real modal\n trigger.removeAttribute('data-bs-toggle');\n trigger.removeAttribute('data-bs-target');\n\n if (popupExists(wanted)) {\n dom.removeClass(trigger, 'ge-popup-orphan');\n return;\n }\n\n var column = trigger.closest('.column');\n var nearby = column ? dom.all(column, '[data-ge-popup-id]') : [];\n\n if (nearby.length === 1) {\n trigger.setAttribute('data-ge-popup-target', popupIdOf(nearby[0]));\n dom.removeClass(trigger, 'ge-popup-orphan');\n return;\n }\n\n dom.addClass(trigger, 'ge-popup-orphan');\n\n ge.operate(function() {\n ge.emit('popup-orphan', ge.payloadFor('popup', trigger, {\n parent: trigger.parentElement,\n source: 'api',\n missing: wanted,\n }));\n });\n });\n }\n\n function writePopupTriggerAttributes(root) {\n root = root || ge.canvas;\n\n dom.all(root, '[data-ge-popup-target]').forEach(function(trigger) {\n var wanted = trigger.getAttribute('data-ge-popup-target');\n\n // The warning marking is editing furniture, whether or not\n // the trigger can be wired up\n dom.removeClass(trigger, 'ge-popup-orphan');\n dom.dropEmptyClass(trigger);\n\n if (!popupExists(wanted, root)) { return; }\n\n trigger.setAttribute('data-bs-toggle', 'modal');\n trigger.setAttribute('data-bs-target', '#' + wanted);\n });\n }\n\n return {\n labelKey: 'container.add_popup',\n paneKind: 'popup',\n\n // Triggers are markup the host owns, anywhere in the canvas, so they\n // are looked at whenever the canvas is initialized rather than only\n // when a popup is touched\n onInit: wirePopupTriggers,\n onDeinit: function() { writePopupTriggerAttributes(); },\n\n // What onDeinit and unmark do, on a copy of the canvas\n cleanMarkup: function(root) {\n var definition = this;\n writePopupTriggerAttributes(root);\n dom.all(root, '[data-ge-container=\"popup\"]').forEach(function(container) { definition.unmark(container); });\n },\n\n create: function(options) {\n var id = ge.containerId('popup');\n var container = dom.element('div', { 'data-ge-container': 'popup', 'data-ge-popup-id': id });\n\n if (options.trigger !== false) {\n container.appendChild(dom.element('button', {\n type: 'button',\n 'class': 'btn btn-primary ge-popup-trigger',\n 'data-ge-popup-target': id,\n }, options.trigger_label || ge.t('container.popup_trigger')));\n }\n\n var modal = container.appendChild(dom.element('div', {\n 'class': 'modal fade',\n tabindex: '-1',\n 'aria-hidden': 'true',\n id: id,\n }));\n var dialog = modal.appendChild(dom.element('div', { 'class': 'modal-dialog' }));\n if (options.size) { dom.addClass(dialog, 'modal-' + options.size); }\n\n var content = dialog.appendChild(dom.element('div', { 'class': 'modal-content' }));\n var header = content.appendChild(dom.element('div', { 'class': 'modal-header' }));\n header.appendChild(dom.element('h5', { 'class': 'modal-title' }))\n .appendChild(dom.element('span', { 'class': 'ge-pane-label' },\n options.title || ge.t('container.popup_title')));\n header.appendChild(dom.create('<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>'));\n content.appendChild(dom.element('div', { 'class': 'modal-body' })).appendChild(ge.defaultRegion());\n\n return container;\n },\n\n mark: function(container) {\n // Nothing in a popup may reach Bootstrap while editing:\n // the modal is rendered unfolded and static, and a real\n // modal opening over it would be the editor fighting\n // itself\n dom.all(container, '[data-bs-dismiss]').forEach(function(dismiss) {\n ge.suspendToggles(dismiss);\n });\n dom.all(container, '.modal-title').forEach(function(title) {\n ge.makeLabelEditable(ge.labelIn(title));\n });\n },\n\n unmark: function(container) {\n ge.resumeToggles(container);\n dom.removeClass(container, 'ge-popup-collapsed');\n ge.unwrapLabels(container);\n },\n\n /** A page of unfolded modals stays workable if they can be folded away. */\n tools: function(drawer, container) {\n ge.createTool(drawer, ge.t('tool.toggle_popup'), 'ge-toggle-popup', 'bi bi-chevron-bar-contract',\n function() {\n dom.toggleClass(container, 'ge-popup-collapsed');\n });\n },\n };\n};\n"],
5
- "mappings": "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",
6
- "names": ["GridEditor", "all", "root", "selector", "parse", "html", "template", "create", "nodes", "i", "element", "tag", "attributes", "text", "node", "name", "value", "addClass", "node", "names", "split", "name", "removeClass", "toggleClass", "state", "split", "names", "dropEmptyClass", "node", "GridEditor", "ge", "popupIdOf", "container", "popupExists", "id", "root", "all", "popup", "wirePopupTriggers", "trigger", "addClass", "wanted", "removeClass", "column", "nearby", "writePopupTriggerAttributes", "dropEmptyClass", "definition", "options", "element", "modal", "dialog", "content", "header", "create", "dismiss", "title", "drawer", "toggleClass"]
3
+ "sources": ["../../src/js/register.js", "../../src/js/dom.js", "../../src/js/triggers/triggers.js", "../../src/js/plugins/grideditor.popup.js"],
4
+ "sourcesContent": ["/**\n * What a plugin's or a locale's classic script imports in place of the\n * editor: the GridEditor the page already loaded.\n *\n * The build points `import { GridEditor } from '../grideditor.js'` here for\n * the classic scripts, so each registers on the editor the page has rather\n * than bringing its own. Loaded before the editor there is nothing to\n * register on, and saying so is better than a TypeError three calls later.\n */\nvar GridEditor = window.GridEditor;\n\nif (!GridEditor || typeof GridEditor.get !== 'function') {\n throw new Error('grid-editor: load grideditor.js (or grideditor.bundle.min.js) before its plugins, ' +\n 'its locales and grideditor.jquery.js');\n}\n\nexport { GridEditor };\nexport default GridEditor;\n", "/**\n * The DOM, the way grid-editor uses it.\n *\n * Small helpers over the browser's own API, for the core and the shipped\n * plugins. Not a public API: a plugin of a host's own is free to write its\n * DOM code however it likes, and this file makes no promise to it.\n *\n * Stateless on purpose. The build puts a copy of this file in each plugin's\n * classic script, so anything kept here would be kept once per copy; a\n * module that needs to remember something about a node keeps its own\n * WeakMap.\n */\n\n/** Every element under `root` matching `selector`, as an array. */\nexport function all(root, selector) {\n return root ? Array.prototype.slice.call(root.querySelectorAll(selector)) : [];\n}\n\n/** The first element under `root` matching `selector`, or null. */\nexport function one(root, selector) {\n return root ? root.querySelector(selector) : null;\n}\n\n/** `root` itself when it matches, then every element under it that does. */\nexport function selfAndAll(root, selector) {\n if (!root) { return []; }\n\n var found = all(root, selector);\n if (root.nodeType === 1 && root.matches(selector)) { found.unshift(root); }\n return found;\n}\n\n/** The element children of `node`, those matching `selector` if one is given. */\nexport function children(node, selector) {\n if (!node) { return []; }\n\n return Array.prototype.filter.call(node.children, function(each) {\n return !selector || each.matches(selector);\n });\n}\n\n/** The first element child matching `selector`, or null. */\nexport function child(node, selector) {\n return children(node, selector)[0] || null;\n}\n\n/** The nearest ancestor-or-self matching `selector` below `stopAt`, or null. */\nexport function closest(node, selector, stopAt) {\n var found = node && node.nodeType === 1 ? node.closest(selector) : null;\n\n if (found && stopAt && found !== stopAt && !stopAt.contains(found)) { return null; }\n return found;\n}\n\n/** Whether `node` is an element matching `selector`. */\nexport function is(node, selector) {\n return !!node && node.nodeType === 1 && node.matches(selector);\n}\n\n/** Whether `node` is in the document. */\nexport function attached(node) {\n return !!node && document.documentElement.contains(node);\n}\n\n/** The element siblings after `node` that match `selector`. */\nexport function nextAll(node, selector) {\n var found = [];\n\n for (var next = node.nextElementSibling; next; next = next.nextElementSibling) {\n if (!selector || next.matches(selector)) { found.push(next); }\n }\n return found;\n}\n\n/** The ancestors of `node` up to, and not including, `stop`, nearest first. */\nexport function parentsUntil(node, stop) {\n var found = [];\n\n for (var parent = node.parentElement; parent && parent !== stop; parent = parent.parentElement) {\n found.push(parent);\n }\n return found;\n}\n\n/**\n * The nodes the html parses into, detached. Parsed the way innerHTML parses,\n * so a <script> in it never runs, here or once it is in the page.\n */\nexport function parse(html) {\n var template = document.createElement('template');\n template.innerHTML = html;\n return Array.prototype.slice.call(template.content.childNodes);\n}\n\n/** The one element the html is, detached. */\nexport function create(html) {\n var nodes = parse(html.trim());\n\n for (var i = 0; i < nodes.length; i++) {\n if (nodes[i].nodeType === 1) { return nodes[i]; }\n }\n return null;\n}\n\n/** An element of `tag`, with `attributes` and `text`. */\nexport function element(tag, attributes, text) {\n var node = document.createElement(tag);\n\n Object.keys(attributes || {}).forEach(function(name) {\n var value = attributes[name];\n if (value !== null && value !== undefined && value !== false) { node.setAttribute(name, value); }\n });\n if (text !== undefined && text !== null) { node.textContent = text; }\n\n return node;\n}\n\n/** Replace what `node` holds with `html`. A <script> in it does not run. */\nexport function setHtml(node, html) {\n node.innerHTML = html === undefined || html === null ? '' : String(html);\n return node;\n}\n\n/** Add one or several space separated classes. */\nexport function addClass(node, names) {\n split(names).forEach(function(name) { node.classList.add(name); });\n return node;\n}\n\nexport function removeClass(node, names) {\n split(names).forEach(function(name) { node.classList.remove(name); });\n return node;\n}\n\n/** Each class on, or off, or flipped when `on` is not given. */\nexport function toggleClass(node, names, state) {\n split(names).forEach(function(name) {\n if (state === undefined) {\n node.classList.toggle(name);\n } else {\n node.classList.toggle(name, !!state);\n }\n });\n return node;\n}\n\nexport function hasClass(node, name) {\n return !!node && node.nodeType === 1 && node.classList.contains(name);\n}\n\nfunction split(names) {\n return String(names || '').split(/\\s+/).filter(Boolean);\n}\n\n/** Take the class attribute off when there is nothing left in it. */\nexport function dropEmptyClass(node) {\n if (!node.getAttribute('class')) { node.removeAttribute('class'); }\n return node;\n}\n\n/** Take the style attribute off when there is nothing left in it. */\nexport function dropEmptyStyle(node) {\n if (!node.getAttribute('style')) { node.removeAttribute('style'); }\n return node;\n}\n\n/** Set attributes; a null or undefined value takes the attribute off. */\nexport function attr(node, attributes) {\n Object.keys(attributes).forEach(function(name) {\n var value = attributes[name];\n\n if (value === null || value === undefined) {\n node.removeAttribute(name);\n } else {\n node.setAttribute(name, value);\n }\n });\n return node;\n}\n\n/**\n * Inline styles, by css property name. A number is pixels for the properties\n * that take a length, as jQuery had it; an empty string takes one off.\n */\nexport function css(node, styles) {\n Object.keys(styles).forEach(function(property) {\n var value = styles[property];\n\n if (typeof value === 'number' && !UNITLESS[property]) { value = value + 'px'; }\n node.style.setProperty(property, value === null || value === undefined ? '' : String(value));\n });\n return node;\n}\n\nvar UNITLESS = { opacity: true, 'z-index': true, 'flex-grow': true, 'flex-shrink': true, order: true };\n\n/** What an element of this tag shows as when nothing hides it. */\nvar defaultDisplays = {};\n\nfunction defaultDisplay(node) {\n var tag = node.nodeName;\n\n if (!defaultDisplays[tag]) {\n var probe = document.body.appendChild(document.createElement(tag));\n defaultDisplays[tag] = getComputedStyle(probe).display;\n probe.remove();\n if (defaultDisplays[tag] === 'none') { defaultDisplays[tag] = 'block'; }\n }\n return defaultDisplays[tag];\n}\n\n/** Show a node: its inline display:none off, and a display of its own if a stylesheet hides it. */\nexport function show(node) {\n node.style.removeProperty('display');\n if (getComputedStyle(node).display === 'none') { node.style.display = defaultDisplay(node); }\n return node;\n}\n\nexport function hide(node) {\n node.style.display = 'none';\n return node;\n}\n\n/** Show or hide; flip when `on` is not given. */\nexport function toggle(node, state) {\n if (state === undefined) { state = !visible(node); }\n return state ? show(node) : hide(node);\n}\n\n/** Whether a node takes up room: what jQuery's :visible meant. */\nexport function visible(node) {\n return !!(node.offsetWidth || node.offsetHeight || node.getClientRects().length);\n}\n\n/** The border box's width, fractions included. */\nexport function outerWidth(node) {\n return node.getBoundingClientRect().width;\n}\n\nexport function outerHeight(node) {\n return node.getBoundingClientRect().height;\n}\n\n/** The content box's height, which is what jQuery's height() gave. */\nexport function contentHeight(node) {\n var style = getComputedStyle(node);\n return node.clientHeight - parseFloat(style.paddingTop) - parseFloat(style.paddingBottom);\n}\n\n/** Where a node is on the page, not in the window. */\nexport function offset(node) {\n var box = node.getBoundingClientRect();\n return { top: box.top + window.pageYOffset, left: box.left + window.pageXOffset };\n}\n\n/** Its index among its parent's element children, those matching `selector` if given. */\nexport function indexIn(node, selector) {\n return children(node.parentElement, selector).indexOf(node);\n}\n\n/** Put `node` right after `reference`. */\nexport function insertAfter(node, reference) {\n reference.parentNode.insertBefore(node, reference.nextSibling);\n return node;\n}\n\n/** Replace `node` with what it holds. */\nexport function unwrap(node) {\n var parent = node.parentNode;\n\n while (node.firstChild) { parent.insertBefore(node.firstChild, node); }\n node.remove();\n}\n\n/** Put `node` inside `wrapper`, where `node` was. */\nexport function wrap(node, wrapper) {\n node.parentNode.insertBefore(wrapper, node);\n wrapper.appendChild(node);\n return wrapper;\n}\n\n/**\n * Listen on `root` for `types` (space separated) on descendants that match\n * `selector`, with `this` the matched element, as jQuery's delegated\n * handlers had it. Hands back the listener, for removeEventListener; a\n * signal in `options` removes it too.\n */\nexport function delegate(root, types, selector, handler, options) {\n var listener = function(event) {\n var match = closest(event.target, selector, root);\n if (match && root.contains(match)) { return handler.call(match, event); }\n return undefined;\n };\n\n split(types).forEach(function(type) { root.addEventListener(type, listener, options); });\n return listener;\n}\n\n/** Listen for several space separated event types with one handler. */\nexport function on(target, types, handler, options) {\n split(types).forEach(function(type) { target.addEventListener(type, handler, options); });\n return handler;\n}\n\nexport function off(target, types, handler, options) {\n split(types).forEach(function(type) { target.removeEventListener(type, handler, options); });\n}\n\n/** A mouse event of `type` sent to `node`, the way a pointer would. */\nexport function fire(node, type) {\n node.dispatchEvent(new MouseEvent(type, { bubbles: type !== 'mouseenter' && type !== 'mouseleave' }));\n}\n\n/* ------------------------------------------------------------------\n * Taking a node away, visibly.\n *\n * The durations are jQuery's: 400 ms unless the caller says otherwise. `done` runs once the node is out of sight, which is\n * when the caller removes it; a node that is already out of sight is done at\n * once, on the next task, as jQuery had it.\n * ------------------------------------------------------------------ */\n\nvar DEFAULT_DURATION = 400;\n\nfunction animateAway(node, frames, duration, done) {\n duration = duration === undefined ? DEFAULT_DURATION : duration;\n\n var finish = function() {\n hide(node);\n if (done) { done(); }\n };\n\n if (!visible(node) || !node.animate || duration <= 0) {\n window.setTimeout(finish, 0);\n return;\n }\n\n var animation = node.animate(frames(getComputedStyle(node)), {\n duration: duration,\n easing: 'ease-in-out',\n });\n var called = false;\n var once = function() {\n if (called) { return; }\n called = true;\n finish();\n };\n\n animation.onfinish = once;\n animation.oncancel = once;\n}\n\n/** Fold a node up to nothing, then hide it. */\nexport function slideUp(node, duration, done) {\n if (typeof duration === 'function') {\n done = duration;\n duration = undefined;\n }\n\n node.style.overflow = 'hidden';\n animateAway(node, function(style) {\n return [\n {\n height: style.height,\n paddingTop: style.paddingTop,\n paddingBottom: style.paddingBottom,\n marginTop: style.marginTop,\n marginBottom: style.marginBottom,\n },\n { height: '0px', paddingTop: '0px', paddingBottom: '0px', marginTop: '0px', marginBottom: '0px' },\n ];\n }, duration, function() {\n node.style.removeProperty('overflow');\n if (done) { done(); }\n });\n}\n\n/** Fade a node out, then hide it. */\nexport function fadeOut(node, duration, done) {\n animateAway(node, function(style) {\n return [{ opacity: style.opacity }, { opacity: 0 }];\n }, duration, done);\n}\n\n/** Fade and fold a node, in height and, with `width`, sideways too, then hide it. */\nexport function shrinkAway(node, duration, done, width) {\n node.style.overflow = 'hidden';\n animateAway(node, function(style) {\n var from = { opacity: style.opacity, height: style.height };\n var to = { opacity: 0, height: '0px' };\n\n if (width) {\n from.width = style.width;\n to.width = '0px';\n }\n return [from, to];\n }, duration, function() {\n node.style.removeProperty('overflow');\n if (done) { done(); }\n });\n}\n", "/**\n * What the popup and the offcanvas plugins share: the host's triggers.\n *\n * A trigger is markup the host owns - a button, a link - anywhere in the\n * canvas, that points at a container by id. While editing it must not reach\n * Bootstrap, and one that points at nothing is warned about. Not a public\n * API: the build puts a copy of this file in each plugin that imports it,\n * so it keeps nothing of its own between plugins.\n */\nimport * as dom from '../dom.js';\n\n/**\n * The trigger handling of one kind of container.\n *\n * `kind` names the container (`popup`), its classes (`ge-popup-trigger`,\n * `ge-popup-orphan`) and the event for a trigger left pointing at nothing\n * (`popup-orphan`). `idAttribute` is the attribute a container has its id in,\n * `targetAttribute` the one a trigger names it with, and `toggle` is\n * Bootstrap's `data-bs-toggle` for it, written on the way out.\n *\n * Gives back `onInit` and `onDeinit`, for the plugin to hand the editor as\n * its own, and `write(root)`, what onDeinit does to the canvas, for\n * cleanMarkup to do to a copy.\n */\nexport function triggersFor(ge, kind, idAttribute, targetAttribute, toggle) {\n var triggerClass = 'ge-' + kind + '-trigger';\n var orphanClass = 'ge-' + kind + '-orphan';\n var listening = null;\n\n function idOf(container) {\n return container.getAttribute(idAttribute);\n }\n\n /** Whether a container with this id is on the canvas. */\n function exists(id, root) {\n return dom.all(root || ge.canvas, '[' + idAttribute + ']').some(function(container) {\n return idOf(container) === id;\n });\n }\n\n function wire() {\n dom.all(ge.canvas, '[' + targetAttribute + ']').forEach(function(trigger) {\n dom.addClass(trigger, triggerClass);\n var wanted = trigger.getAttribute(targetAttribute);\n\n // Bootstrap's own attributes are written at getHtml time, not\n // while editing, so a click here cannot open a real container\n trigger.removeAttribute('data-bs-toggle');\n trigger.removeAttribute('data-bs-target');\n\n if (exists(wanted)) {\n dom.removeClass(trigger, orphanClass);\n return;\n }\n\n var column = trigger.closest('.column');\n var nearby = column ? dom.all(column, '[' + idAttribute + ']') : [];\n\n if (nearby.length === 1) {\n trigger.setAttribute(targetAttribute, idOf(nearby[0]));\n dom.removeClass(trigger, orphanClass);\n return;\n }\n\n dom.addClass(trigger, orphanClass);\n\n ge.operate(function() {\n ge.emit(kind + '-orphan', ge.payloadFor(kind, trigger, {\n parent: trigger.parentElement,\n source: 'api',\n missing: wanted,\n }));\n });\n });\n }\n\n function write(root) {\n root = root || ge.canvas;\n\n dom.all(root, '[' + targetAttribute + ']').forEach(function(trigger) {\n var wanted = trigger.getAttribute(targetAttribute);\n\n // The warning marking is editing furniture, whether or not\n // the trigger can be wired up\n dom.removeClass(trigger, orphanClass);\n dom.dropEmptyClass(trigger);\n\n if (!exists(wanted, root)) { return; }\n\n trigger.setAttribute('data-bs-toggle', toggle);\n trigger.setAttribute('data-bs-target', '#' + wanted);\n });\n }\n\n return {\n write: write,\n\n // Triggers are markup the host owns, anywhere in the canvas, so they\n // are looked at whenever the canvas is initialized rather than only\n // when a container is touched\n onInit: function() {\n wire();\n\n if (listening) { listening.abort(); }\n listening = new AbortController();\n\n // A trigger is often a link, and a link still navigates even\n // with its Bootstrap attributes suspended\n dom.delegate(ge.canvas, 'click', '.' + triggerClass + ', [' + targetAttribute + ']', function(e) {\n if (dom.hasClass(ge.canvas, 'ge-editing')) { e.preventDefault(); }\n }, { signal: listening.signal });\n },\n\n onDeinit: function() {\n if (listening) { listening.abort(); }\n listening = null;\n\n write();\n },\n };\n}\n", "/**\n * Popups for grid-editor.\n *\n * A container plugin: load this file after the editor and the toolbar offers\n * a popup: a bootstrap modal and its trigger. What it can ask the editor for is the handle its factory is\n * called with, described in docs/plugins.md.\n *\n * <script src=\"dist/grideditor.min.js\"></script>\n * <script src=\"dist/plugins/grideditor.popup.min.js\"></script>\n */\nimport { GridEditor } from '../grideditor.js';\nimport * as dom from '../dom.js';\nimport { triggersFor } from '../triggers/triggers.js';\n\nObject.assign(GridEditor.locales.en, {\n 'container.add_popup': 'Popup',\n 'container.popup_title': 'Title',\n 'container.popup_trigger': 'Open',\n 'tool.toggle_popup': 'Fold this popup away while editing',\n});\n\nGridEditor.containers.popup = function(ge) {\n\n var triggers = triggersFor(ge, 'popup', 'data-ge-popup-id', 'data-ge-popup-target', 'modal');\n\n return {\n labelKey: 'container.add_popup',\n paneKind: 'popup',\n\n // The ids a copy of a popup has to rename together\n references: ['data-ge-popup-id', 'data-ge-popup-target'],\n\n onInit: triggers.onInit,\n onDeinit: triggers.onDeinit,\n\n // What onDeinit and unmark do, on a copy of the canvas\n cleanMarkup: function(root) {\n var definition = this;\n triggers.write(root);\n dom.all(root, '[data-ge-container=\"popup\"]').forEach(function(container) { definition.unmark(container); });\n },\n\n create: function(options) {\n var id = ge.containerId('popup');\n var container = dom.element('div', { 'data-ge-container': 'popup', 'data-ge-popup-id': id });\n\n if (options.trigger !== false) {\n container.appendChild(dom.element('button', {\n type: 'button',\n 'class': 'btn btn-primary ge-popup-trigger',\n 'data-ge-popup-target': id,\n }, options.trigger_label || ge.t('container.popup_trigger')));\n }\n\n var modal = container.appendChild(dom.element('div', {\n 'class': 'modal fade',\n tabindex: '-1',\n 'aria-hidden': 'true',\n id: id,\n }));\n var dialog = modal.appendChild(dom.element('div', { 'class': 'modal-dialog' }));\n if (options.size) { dom.addClass(dialog, 'modal-' + options.size); }\n\n var content = dialog.appendChild(dom.element('div', { 'class': 'modal-content' }));\n var header = content.appendChild(dom.element('div', { 'class': 'modal-header' }));\n header.appendChild(dom.element('h5', { 'class': 'modal-title' }))\n .appendChild(dom.element('span', { 'class': 'ge-pane-label' },\n options.title || ge.t('container.popup_title')));\n header.appendChild(dom.create('<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button>'));\n content.appendChild(dom.element('div', { 'class': 'modal-body' })).appendChild(ge.defaultRegion());\n\n return container;\n },\n\n mark: function(container) {\n // Nothing in a popup may reach Bootstrap while editing:\n // the modal is rendered unfolded and static, and a real\n // modal opening over it would be the editor fighting\n // itself\n dom.all(container, '[data-bs-dismiss]').forEach(function(dismiss) {\n ge.suspendToggles(dismiss);\n });\n dom.all(container, '.modal-title').forEach(function(title) {\n ge.makeLabelEditable(ge.labelIn(title));\n });\n },\n\n unmark: function(container) {\n ge.resumeToggles(container);\n dom.removeClass(container, 'ge-popup-collapsed');\n ge.unwrapLabels(container);\n },\n\n /** A page of unfolded modals stays workable if they can be folded away. */\n tools: function(drawer, container) {\n ge.createTool(drawer, ge.t('tool.toggle_popup'), 'ge-toggle-popup', 'bi bi-chevron-bar-contract',\n function() {\n dom.toggleClass(container, 'ge-popup-collapsed');\n });\n },\n };\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["GridEditor", "all", "root", "selector", "closest", "node", "selector", "stopAt", "found", "parse", "html", "template", "create", "nodes", "i", "element", "tag", "attributes", "text", "node", "name", "value", "addClass", "node", "names", "split", "name", "removeClass", "toggleClass", "state", "hasClass", "dropEmptyClass", "delegate", "root", "types", "selector", "handler", "options", "listener", "event", "match", "closest", "split", "type", "triggersFor", "ge", "kind", "idAttribute", "targetAttribute", "toggle", "triggerClass", "orphanClass", "listening", "idOf", "container", "exists", "id", "root", "all", "wire", "trigger", "addClass", "wanted", "removeClass", "column", "nearby", "write", "dropEmptyClass", "delegate", "e", "hasClass", "GridEditor", "ge", "triggers", "triggersFor", "root", "definition", "all", "container", "options", "id", "element", "modal", "dialog", "addClass", "content", "header", "create", "dismiss", "title", "removeClass", "drawer", "toggleClass"]
7
7
  }
package/docs/events.md CHANGED
@@ -63,7 +63,7 @@ about every insertion can bind `grideditor:before-add` and switch on
63
63
  | `grideditor:after-add-row` | `grideditor:after-add` | no | after insertion, once the canvas is up to date |
64
64
  | `grideditor:before-add-column` | `grideditor:before-add` | yes | before a column is inserted |
65
65
  | `grideditor:after-add-column` | `grideditor:after-add` | no | |
66
- | `grideditor:before-add-container` | `grideditor:before-add` | yes | before a container is inserted; `payload.kind` is `tabs`, `accordion`, `popup`, `carousel` or `card` |
66
+ | `grideditor:before-add-container` | `grideditor:before-add` | yes | before a container is inserted; `payload.kind` is `tabs`, `accordion`, `popup`, `offcanvas`, `carousel` or `card` |
67
67
  | `grideditor:after-add-container` | `grideditor:after-add` | no | |
68
68
  | `grideditor:before-add-tab` | `grideditor:before-add` | yes | before a tab is added to a tabs container |
69
69
  | `grideditor:after-add-tab` | `grideditor:after-add` | no | |
@@ -88,6 +88,7 @@ about every insertion can bind `grideditor:before-add` and switch on
88
88
  | `grideditor:before-indent` | — | yes | before a column's offset changes |
89
89
  | `grideditor:after-indent` | — | no | after the offset class is written |
90
90
  | `grideditor:popup-orphan` | — | no | on `init`, once per trigger whose popup is missing and cannot be re-pointed |
91
+ | `grideditor:offcanvas-orphan` | — | no | the same for an offcanvas's trigger |
91
92
  | `grideditor:before-utility` | — | yes | before a utility class is written, from the panel, a plugin's tool or `setUtility` |
92
93
  | `grideditor:after-utility` | — | no | after the class is written and the preview redrawn, only if something changed |
93
94
  | `grideditor:view-change` | — | no | after the view changes, only if it actually changed |
@@ -97,6 +98,9 @@ about every insertion can bind `grideditor:before-add` and switch on
97
98
  | `grideditor:after-autosave` | — | no | with the autosave plugin, after a draft is written |
98
99
  | `grideditor:after-restore-draft` | — | no | with the autosave plugin, after the user chose to restore a draft and the canvas is it |
99
100
  | `grideditor:autosave-error` | — | no | with the autosave plugin, when the storage refused a draft |
101
+ | `grideditor:after-undo` | — | no | with the history plugin, after the canvas went back a step: `source` is `'shortcut'`, `'tool'` or `'api'`, and the payload has `canvas` and `html`, the markup it went to |
102
+ | `grideditor:after-redo` | — | no | with the history plugin, after the canvas came forward a step; the same payload |
103
+ | `grideditor:history-change` | — | no | with the history plugin, after what can be done changes: `canvas`, `canUndo`, `canRedo`. Not for a step that leaves both as they were |
100
104
 
101
105
  Two things here are not in the 3.0 specification's catalogue. The indent pair,
102
106
  because the indent tools are an operation like any other and announce
@@ -110,7 +114,7 @@ The payload
110
114
  ```javascript
111
115
  {
112
116
  kind: 'row', // row | column | text | plain | element
113
- // tabs | accordion | popup | carousel | card | tab | accordion-item | carousel-item
117
+ // tabs | accordion | popup | offcanvas | carousel | card | tab | accordion-item | carousel-item
114
118
  // section, with the sections plugin
115
119
  node: Element, // the node added, deleted, moved or resized
116
120
  parent: Element, // where it is going, or where it came from on a delete;
@@ -143,7 +147,7 @@ The payload
143
147
  from: '<div class="row">…</div>', // the block's html as it was
144
148
  to: '<div class="row">…</div>', // what was written
145
149
 
146
- // popup-orphan only
150
+ // popup-orphan and offcanvas-orphan only
147
151
  missing: 'ge-popup-3-a91', // the id the trigger pointed at
148
152
 
149
153
  // utility only
@@ -43,6 +43,7 @@ The tools in a drawer, and the buttons in the toolbar above the canvas.
43
43
  | `tool.carousel_move_forward` | Move forward | Slide drawer, moves the slide a place on |
44
44
  | `tool.rename` | Double click to rename | Tooltip of a pane's label, which is edited in place |
45
45
  | `tool.toggle_popup` | Fold this popup away while editing | Popup drawer. Editing shows a popup unfolded, and this folds it away |
46
+ | `tool.toggle_offcanvas` | Fold this offcanvas away while editing | Offcanvas drawer. Editing shows an offcanvas unfolded, and this folds it away |
46
47
  | `tool.element_info` | Element: {name} | Tooltip of the element drawer's info tool. `{name}` is the element's `data-ge-label`, its `data-ge-element` type, or both |
47
48
  | `tool.column_narrower` | Make column narrower\n(hold shift for min) | Column drawer, the `-` tool. Two lines |
48
49
  | `tool.column_wider` | Make column wider\n(hold shift for max) | Column drawer, the `+` tool. Two lines |
@@ -104,7 +105,7 @@ title. `{kind}` is one of the `panel.kind_*` keys, or a container's own label.
104
105
  container.*
105
106
  -----------
106
107
 
107
- The containers: tabs, accordions, popups, carousels and cards, and the panes inside them.
108
+ The containers: tabs, accordions, popups, offcanvases, carousels and cards, and the panes inside them.
108
109
 
109
110
  | Key | English | Where |
110
111
  | --- | --- | --- |
@@ -155,6 +156,25 @@ The containers: tabs, accordions, popups, carousels and cards, and the panes ins
155
156
  | `container.add_popup` | Popup | Toolbar button that adds a popup |
156
157
  | `container.popup_title` | Title | Title of a new popup, in its modal header |
157
158
  | `container.popup_trigger` | Open | Label of the button a popup makes to open itself |
159
+ | `container.add_offcanvas` | Offcanvas | Toolbar button that adds an offcanvas |
160
+ | `container.offcanvas_title` | Title | Title of a new offcanvas, in its header |
161
+ | `container.offcanvas_trigger` | Open | Label of the button an offcanvas makes to open itself |
162
+ | `container.offcanvas_section` | Offcanvas | Heading of an offcanvas's section of its settings panel |
163
+ | `container.offcanvas_placement` | Placement | Its select for the edge it comes from |
164
+ | `container.offcanvas_start` | Start | Placement: the left in a left to right page |
165
+ | `container.offcanvas_end` | End | Placement: the right in a left to right page |
166
+ | `container.offcanvas_top` | Top | Placement: the top edge |
167
+ | `container.offcanvas_bottom` | Bottom | Placement: the bottom edge |
168
+ | `container.offcanvas_backdrop` | Backdrop | Its select for `data-bs-backdrop` |
169
+ | `container.offcanvas_backdrop_yes` | Yes | Backdrop: shown, and closes it on a click |
170
+ | `container.offcanvas_backdrop_no` | No | Backdrop: none |
171
+ | `container.offcanvas_backdrop_static` | Static | Backdrop: shown, and does not close it on a click |
172
+ | `container.offcanvas_scroll` | Allow body scroll | Its checkbox for `data-bs-scroll` |
173
+ | `container.offcanvas_keyboard` | Close with Esc | Its checkbox for `data-bs-keyboard` |
174
+ | `container.offcanvas_responsive` | Responsive | Its select for the breakpoint from which it is content |
175
+ | `container.offcanvas_responsive_never` | Always offcanvas | Responsive: a panel at every size |
176
+ | `container.offcanvas_responsive_from` | Inline from {breakpoint} | Responsive: content from that breakpoint up. `{breakpoint}` is `sm`, `md`… |
177
+ | `container.offcanvas_dark` | Dark theme | Its checkbox for `data-bs-theme="dark"` |
158
178
  | `container.add_card` | Card | Toolbar button that adds a card |
159
179
  | `container.card_title` | Card title | Title of a new card, in its header |
160
180
  | `container.card_footer` | Card footer | Text of a new card's footer, when it has one |
@@ -229,6 +249,17 @@ the same html saved.
229
249
  | `autosave.discard` | Discard | The modal's cancel button: the draft is taken away |
230
250
 
231
251
 
252
+ history.*
253
+ ---------
254
+
255
+ The history plugin: undo and redo.
256
+
257
+ | Key | English | Where |
258
+ | --- | --- | --- |
259
+ | `history.undo` | Undo | Title of the toolbar's undo button |
260
+ | `history.redo` | Redo | Title of the toolbar's redo button |
261
+
262
+
232
263
  codemirror.*
233
264
  ------------
234
265
 
package/docs/plugins.md CHANGED
@@ -1,7 +1,7 @@
1
1
  Writing a plugin
2
2
  ================
3
3
 
4
- Tabs, accordions, popups, carousels, cards and the element level controls are not built
4
+ Tabs, accordions, popups, offcanvases, carousels, cards and the element level controls are not built
5
5
  into grid-editor: each is a file you load beside it, and loading the file is
6
6
  what turns the feature on. `grideditor.card.js` is the shortest of them, and
7
7
  the one to read first if you are about to write your own.
@@ -10,6 +10,7 @@ the one to read first if you are about to write your own.
10
10
  <script src="dist/grideditor.min.js"></script>
11
11
  <script src="dist/plugins/grideditor.tabs.min.js"></script>
12
12
  <script src="dist/plugins/grideditor.popup.min.js"></script>
13
+ <script src="dist/plugins/grideditor.offcanvas.min.js"></script>
13
14
  <script src="dist/plugins/grideditor.card.min.js"></script>
14
15
  <script src="dist/plugins/grideditor.elements.min.js"></script>
15
16
  ```
@@ -74,6 +75,7 @@ GridEditor.containers.gallery = function(ge) {
74
75
  mark: function(container) { … }, // editing furniture on
75
76
  unmark: function(container) { … }, // and off again
76
77
  cleanMarkup: function(root, liveOf) { … }, // the same, on a copy: see "Reading on a copy"
78
+ references: ['data-ge-picture-id'], // optional: attributes of its own that point at an id
77
79
  afterPaneMove: function(container, pane) { … }, // optional
78
80
  tools: function(drawer, container) { … }, // optional, extra tools
79
81
  onInit: function() { … }, // optional, per canvas init
@@ -118,8 +120,13 @@ written for 6.x.
118
120
  runs before reading the markup. Whatever `mark` added comes off here. What
119
121
  is left is what the host ships.
120
122
  - **`onInit`/`onDeinit`** are for work that is about the whole canvas rather
121
- than one container — the popup plugin wires up the host's
122
- `data-ge-popup-target` triggers there.
123
+ than one container — the popup and offcanvas plugins wire up the host's
124
+ `data-ge-popup-target` and `data-ge-offcanvas-target` triggers there.
125
+ - **`references`** lists the attributes of the container's own that hold an id,
126
+ as the id itself or as a `#selector`, which `ge.freshNode` renames along with
127
+ the id they point at when a copy is made: a popup's `data-ge-popup-id` and
128
+ `data-ge-popup-target`. Bootstrap's own - `data-bs-target`, `aria-controls`
129
+ and the rest - are always followed.
123
130
 
124
131
 
125
132
  The handle
@@ -137,7 +144,7 @@ element, and where there may be several, an array.
137
144
  | `ge.t(key, params)` | A string from the locale |
138
145
  | `ge.warn(message)` | A console warning, prefixed like the editor's own |
139
146
  | `ge.containerId(type)` | A generated id, stable across a reset, for Bootstrap's toggles |
140
- | `ge.freshNode(html)` | The markup as a detached node, with new ids for those the page already has and the attributes that point at them - `data-bs-target`, `data-bs-parent`, `href="#…"`, `aria-controls`, `aria-labelledby`, `aria-describedby`, `for`, `data-ge-popup-id`, `data-ge-popup-target` - following. An id grid-editor generated gets a new one from `containerId`, any other a `-2`, `-3`… suffix. What the clipboard pastes, and what a host inserting saved markup should hand `ge.place`. Place each copy before making the next: it checks the page as it is |
147
+ | `ge.freshNode(html)` | The markup as a detached node, with new ids for those the page already has and the attributes that point at them - `data-bs-target`, `data-bs-parent`, `href="#…"`, `aria-controls`, `aria-labelledby`, `aria-describedby`, `for`, and the `references` of every container plugin in use - following. An id grid-editor generated gets a new one from `containerId`, any other a `-2`, `-3`… suffix. What the clipboard pastes, and what a host inserting saved markup should hand `ge.place`. Place each copy before making the next: it checks the page as it is |
141
148
  | `ge.defaultRegion()` | A row with one full width column: what an empty pane starts as |
142
149
  | `ge.createTool(drawer, title, className, iconClass, handlers)` | A tool in a drawer, which it returns. `handlers` is a click handler or `{ eventName: handler }`; each gets the DOM event, with `this` the tool |
143
150
  | `ge.createMoveTool(drawer)` | The drag handle, unless `drag_handle` says the whole drawer is one; the tool, or null |
@@ -173,6 +180,7 @@ element, and where there may be several, an array.
173
180
  | `ge.refreshToolbar()` | The toolbar built again, from what the feature plugins' `toolbar`s hold now: for a plugin whose buttons come and go. The category shown with `toolbar_groups`, the preview and the source view stay as they are. Does nothing before the toolbar is first built, and after `destroy` |
174
181
  | `ge.getSelected()` | The node the sidebar shows, as the public method gives it: `null` without the `'sidebar'` panel |
175
182
  | `ge.getActiveTarget()` | The column or section the toolbar adds to, as the public method gives it: `null` without `active_target` |
183
+ | `ge.setSelected(node)` / `ge.setActiveTarget(node)` | Choose the node the sidebar shows, or the column or section the toolbar adds to: the public methods without their warnings. `true` when it took, `false` for a node that is not one, or without the `'sidebar'` panel or `active_target`. A plugin that puts the choice back after it remade the canvas does not know the node is still one |
176
184
  | `ge.panelOpen()` | Whether a settings panel is open in a popover, an offcanvas or a modal, or one is unfolded inline in a drawer, or the editor's dialog or delete confirmation is up. The sidebar does not count: it is always there |
177
185
  | `ge.contains(node)` | Whether a node is this editor's: in its canvas, its toolbar, its settings panels (the sidebar too, when it has it), its dialog or its confirmation |
178
186
  | `ge.toolbarItems(name)` | An array of the toolbar buttons the feature plugin `name` declared, for showing and hiding them |
@@ -384,7 +392,13 @@ rather than remembering anything about it.
384
392
  button. `hidden`, a boolean or a function called whenever the toolbar is
385
393
  built, builds the button hidden: a plugin that shows and hides its buttons
386
394
  gives it, so a toolbar built again - `ge.refreshToolbar()`, `setLocale` -
387
- shows them as they are. With `inColumn: true` what it makes belongs in a column, as a
395
+ shows them as they are. With **`action(button)`** in place of `create` the
396
+ button is not a block to add: a click calls `action`, which makes nothing and
397
+ asks nothing of the active target, and the button is not carried onto the
398
+ canvas. **`disabled`**, a boolean or a function, builds it disabled, as
399
+ `hidden` does; afterwards the plugin gives its buttons - `ge.toolbarItems(name)` -
400
+ the `disabled` class and `aria-disabled="true"`, and a click on one with the
401
+ class does nothing. With `inColumn: true` what it makes belongs in a column, as a
388
402
  container or a text does: a click puts it in a row and a column of its own
389
403
  at the end of the canvas, and a drop anywhere but in a column does the
390
404
  same where it was dropped.
@@ -34,6 +34,10 @@
34
34
  // tinyMCE 7 is GPL-2.0-or-later: 'gpl' accepts that licence.
35
35
  // A commercial licence passes its own key here instead.
36
36
  tinymce: { config: { license_key: 'gpl' } },
37
+ // A click in a column or a section makes it the target, which
38
+ // turns on Ctrl+C and Ctrl+V. clipboard: { shortcuts: false }
39
+ // would leave the keys to the browser
40
+ active_target: true,
37
41
  };
38
42
 
39
43
  ['#myGrid', '#otherGrid'].forEach(function(selector) {
@@ -46,6 +50,12 @@
46
50
  document.getElementById('log').textContent = 'Pasted a ' + e.detail.kind + ' into ' + e.currentTarget.id;
47
51
  }
48
52
  });
53
+
54
+ // A copy says where it came from: 'tool' or 'shortcut'
55
+ grid.addEventListener('grideditor:after-copy', function(e) {
56
+ document.getElementById('log').textContent = 'Copied a ' + e.detail.kind + ' from ' + e.currentTarget.id +
57
+ (e.detail.source === 'shortcut' ? ' with Ctrl+C' : ' with its tool');
58
+ });
49
59
  });
50
60
  });
51
61
  </script>
@@ -70,6 +80,18 @@
70
80
  <i class="bi bi-clipboard-plus"></i> on the right: click it, or
71
81
  drag it to where the copy should land.
72
82
  </p>
83
+ <p class="text-body-secondary">
84
+ With the keyboard: click a column or a section - its background or
85
+ its drawer, not a text, which opens the text for writing - and it
86
+ becomes the target, with a dashed outline. <kbd>Ctrl</kbd>+<kbd>C</kbd>
87
+ (<kbd>⌘</kbd>+<kbd>C</kbd> on a Mac) copies it, and
88
+ <kbd>Ctrl</kbd>+<kbd>V</kbd> pastes what is copied into it, where
89
+ it fits: a row, a text, a container or an element at the end of a
90
+ column, a row into a section, a section after the block the target
91
+ is in. While you are writing in a text, or have text selected, the
92
+ keys copy and paste the text as usual. Escape, or a click on the
93
+ canvas outside any column, clears the target.
94
+ </p>
73
95
  <p class="text-body-secondary">
74
96
  What is copied is kept in the browser, so it can be pasted in the
75
97
  other editor, after a reload, or in this page open in another tab.
@@ -22,6 +22,7 @@
22
22
  <script src="../dist/plugins/grideditor.tabs.min.js"></script>
23
23
  <script src="../dist/plugins/grideditor.accordion.min.js"></script>
24
24
  <script src="../dist/plugins/grideditor.popup.min.js"></script>
25
+ <script src="../dist/plugins/grideditor.offcanvas.min.js"></script>
25
26
  <script src="../dist/plugins/grideditor.card.min.js"></script>
26
27
  <script src="../dist/plugins/grideditor.carousel.min.js"></script>
27
28
 
@@ -89,9 +90,9 @@
89
90
  A container holds regions, and every region is an ordinary
90
91
  one: rows, columns, content areas and elements nest inside
91
92
  it exactly as they do at the top level. The five buttons in
92
- the toolbar add a tabs container, an accordion, a popup, a
93
- carousel and a card &mdash; the first four hold a pane each, a card
94
- holds one region and an editable header.
93
+ the toolbar add a tabs container, an accordion, a popup, an
94
+ offcanvas, a carousel and a card &mdash; tabs, accordion and carousel
95
+ hold a pane each, the others hold one region.
95
96
  </p>
96
97
  <p class="form-text">
97
98
  While editing, a popup is unfolded in place and an
@@ -101,7 +102,9 @@
101
102
  asked to do any of it, so there is no backdrop and no focus
102
103
  trap to fight with. Double click a tab or item label to
103
104
  rename it, and a card&rsquo;s header and footer the same
104
- way. The row after the popup shows two levels: tabs inside
105
+ way. The offcanvas under the popup is the same panel
106
+ unfolded, and its gear says where it comes from and from
107
+ which breakpoint it is plain content. The row after it shows two levels: tabs inside
105
108
  an accordion item. A tabs container's gear has a Tabs
106
109
  section for its variant - tabs, pills or underline, full
107
110
  width, alignment, and vertical from a breakpoint up - and
@@ -171,6 +174,21 @@
171
174
  Grid editor leaves the markup alone and writes
172
175
  Bootstrap's attributes onto it in the output.
173
176
  </p>
177
+
178
+ <div data-ge-container="offcanvas" data-ge-offcanvas-id="example-offcanvas">
179
+ <button type="button" class="btn btn-primary ge-offcanvas-trigger" data-ge-offcanvas-target="example-offcanvas">Open the filters</button>
180
+ <div class="offcanvas offcanvas-end" tabindex="-1" id="example-offcanvas" aria-labelledby="example-offcanvas-label">
181
+ <div class="offcanvas-header">
182
+ <h5 class="offcanvas-title" id="example-offcanvas-label">Filters</h5>
183
+ <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
184
+ </div>
185
+ <div class="offcanvas-body">
186
+ <div class="row"><div class="col-lg-12">
187
+ <div class="ge-content" data-ge-content-type="tinymce"><p>An offcanvas slides in from the right, over the page.</p></div>
188
+ </div></div>
189
+ </div>
190
+ </div>
191
+ </div>
174
192
  </div>
175
193
  </div>
176
194
 
@@ -0,0 +1,94 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+
6
+ <!-- CSS dependencies -->
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.css">
9
+
10
+ <!-- Javascript dependencies -->
11
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
12
+
13
+ <!-- Grid editor CSS -->
14
+ <link rel="stylesheet" type="text/css" href="../dist/grideditor.css" />
15
+ <!-- Grid editor javascript -->
16
+ <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
17
+ <script src="../dist/grideditor.min.js"></script>
18
+ <!-- Undo and redo are a plugin of its own, loaded after the editor. The
19
+ others are here so there is a bit of everything to undo. -->
20
+ <script src="../dist/plugins/grideditor.history.min.js"></script>
21
+ <script src="../dist/plugins/grideditor.clipboard.min.js"></script>
22
+ <script src="../dist/plugins/grideditor.tabs.min.js"></script>
23
+ <script src="../dist/plugins/grideditor.elements.min.js"></script>
24
+
25
+ <script>
26
+ document.addEventListener('DOMContentLoaded', function() {
27
+ var grid = document.getElementById('myGrid');
28
+ var log = document.getElementById('log');
29
+
30
+ new GridEditor(grid, {
31
+ new_row_layouts: [[12], [6, 6], [4, 4, 4]],
32
+ content_types: [],
33
+ // What was selected is selected again after an undo
34
+ settings_panel: 'sidebar',
35
+ history: {
36
+ max: 50, // the steps that can be undone
37
+ delay: 500, // a drag or a burst of changes is one step
38
+ },
39
+ });
40
+
41
+ // A step done says where it came from: 'shortcut', 'tool' or 'api'
42
+ ['undo', 'redo'].forEach(function(name) {
43
+ grid.addEventListener('grideditor:after-' + name, function(e) {
44
+ log.textContent = name + ' (' + e.detail.source + ')';
45
+ });
46
+ });
47
+ });
48
+ </script>
49
+
50
+ <title>Grid Editor example - undo and redo</title>
51
+ </head>
52
+ <body>
53
+ <!-- Back to the demo index -->
54
+ <div class="container pt-3">
55
+ <a class="link-secondary" href="index.html"><i class="bi bi-arrow-left"></i> All demos</a>
56
+ </div>
57
+
58
+ <div class="container py-3">
59
+ <h2>Undo and redo</h2>
60
+ <p class="text-body-secondary">
61
+ Delete a row, drag a column to another size, paste something, change
62
+ an id in the panel: each is a step. <kbd>Ctrl</kbd>+<kbd>Z</kbd>
63
+ (<kbd>⌘</kbd>+<kbd>Z</kbd> on a Mac) goes back one, and
64
+ <kbd>Ctrl</kbd>+<kbd>Y</kbd> or <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Z</kbd>
65
+ comes forward again, as do the <i class="bi bi-arrow-counterclockwise"></i>
66
+ and <i class="bi bi-arrow-clockwise"></i> buttons on the right of
67
+ the toolbar. A change made after going back ends what could be redone.
68
+ </p>
69
+ <p class="text-body-secondary">
70
+ While you are writing in a text or a field, the keys undo what you
71
+ typed there, as usual; it becomes a step when you are done. The
72
+ column or row selected in the sidebar is selected again after a step.
73
+ <span id="log" class="fw-semibold"></span>
74
+ </p>
75
+
76
+ <div id="myGrid">
77
+ <div class="row">
78
+ <div class="col-lg-6">
79
+ <h3>Change this page</h3>
80
+ <p>Then undo it.</p>
81
+ </div>
82
+ <div class="col-lg-6">
83
+ <p>Delete this column, or the whole row.</p>
84
+ </div>
85
+ </div>
86
+ <div class="row">
87
+ <div class="col-lg-4"><p>One.</p></div>
88
+ <div class="col-lg-4"><p>Two.</p></div>
89
+ <div class="col-lg-4"><p>Three.</p></div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </body>
94
+ </html>
@@ -103,7 +103,7 @@
103
103
  <strong>Containers</strong>
104
104
  <code class="text-body-secondary">containers.html</code>
105
105
  </div>
106
- <span class="text-body-secondary">Tabs, accordions, popups and cards, two levels deep, and the export opened as a real page</span>
106
+ <span class="text-body-secondary">Tabs, accordions, popups, offcanvases and cards, two levels deep, and the export opened as a real page</span>
107
107
  </a>
108
108
 
109
109
  <a class="list-group-item list-group-item-action" href="elements.html">
@@ -178,6 +178,14 @@
178
178
  <span class="text-body-secondary">Copying a row, a column or a container and pasting it somewhere else, in this editor or another</span>
179
179
  </a>
180
180
 
181
+ <a class="list-group-item list-group-item-action" href="history.html">
182
+ <div class="d-flex justify-content-between">
183
+ <strong>Undo and redo</strong>
184
+ <code class="text-body-secondary">history.html</code>
185
+ </div>
186
+ <span class="text-body-secondary">The history plugin: Ctrl+Z and Ctrl+Y, and buttons on the toolbar, over what the editor changes</span>
187
+ </a>
188
+
181
189
  <a class="list-group-item list-group-item-action" href="wrap_content.html">
182
190
  <div class="d-flex justify-content-between">
183
191
  <strong>Wrapped content</strong>
package/package.json CHANGED
@@ -32,7 +32,7 @@
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  },
35
- "version": "9.0.0-beta.2",
35
+ "version": "9.0.0-beta.4",
36
36
  "main": "dist/grideditor.js",
37
37
  "module": "dist/grideditor.esm.js",
38
38
  "types": "dist/grideditor.d.ts",