children which are what color the text
+ code = dash_dcc.wait_for_element("code[data-highlighted='yes']")
+ assert len(code.find_elements_by_tag_name("span")) == 2
+
+ dash_dcc.find_element("#md-trigger").click()
+
+ code = dash_dcc.wait_for_element("code[data-highlighted='yes']")
+ assert len(code.find_elements_by_tag_name("span")) == 3
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/misc/test_persistence.py b/components/dash-core-components-refresh/tests/integration/misc/test_persistence.py
new file mode 100644
index 0000000000..243fb95dc8
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/misc/test_persistence.py
@@ -0,0 +1,176 @@
+# -*- coding: utf-8 -*-
+import json
+from selenium.webdriver.common.keys import Keys
+
+from dash import Dash, Input, Output, dcc_refresh as dcc, html
+
+
+def test_msps001_basic_persistence(dash_dcc):
+ app = Dash(__name__)
+
+ app.layout = html.Div(
+ [
+ dcc.Checklist(
+ id="checklist",
+ options=[
+ {"label": "Slow 🐢", "value": "🐢"},
+ {"label": "Fast 🏎️", "value": "🏎️"},
+ {"label": "Faster 🚀", "value": "🚀"},
+ ],
+ value=["🏎️"],
+ persistence=True,
+ ),
+ dcc.DatePickerRange(
+ id="datepickerrange",
+ start_date="2017-08-21",
+ end_date="2024-04-08",
+ start_date_id="start_date",
+ end_date_id="end_date",
+ initial_visible_month="2019-05-01",
+ persistence=True,
+ ),
+ dcc.DatePickerSingle(
+ id="datepickersingle", date="2019-01-01", persistence=True
+ ),
+ dcc.Dropdown(
+ id="dropdownsingle",
+ options=[
+ {"label": "One 1️⃣", "value": "1️⃣"},
+ {"label": "Two 2️⃣", "value": "2️⃣"},
+ {"label": "Three 3️⃣", "value": "3️⃣"},
+ ],
+ value="2️⃣",
+ persistence=True,
+ ),
+ dcc.Dropdown(
+ id="dropdownmulti",
+ options=[
+ {"label": "Four 4️⃣", "value": "4️⃣"},
+ {"label": "Five 5️⃣", "value": "5️⃣"},
+ {"label": "Six 6️⃣", "value": "6️⃣"},
+ ],
+ value=["4️⃣"],
+ multi=True,
+ persistence=True,
+ ),
+ dcc.Input(id="input", value="yes", persistence=True),
+ dcc.RadioItems(
+ id="radioitems",
+ options=[
+ {"label": "Red", "value": "r"},
+ {"label": "Green", "value": "g"},
+ {"label": "Blue", "value": "b"},
+ ],
+ value="b",
+ persistence=True,
+ ),
+ dcc.RangeSlider(
+ id="rangeslider", min=0, max=10, step=1, value=[3, 7], persistence=True
+ ),
+ dcc.Slider(id="slider", min=20, max=30, step=1, value=25, persistence=True),
+ dcc.Tabs(
+ id="tabs",
+ children=[
+ dcc.Tab(label="Eh?", children="Tab A", value="A"),
+ dcc.Tab(label="Bee", children="Tab B", value="B"),
+ dcc.Tab(label="Sea", children="Tab C", value="C"),
+ ],
+ value="A",
+ persistence=True,
+ ),
+ dcc.Textarea(id="textarea", value="knock knock", persistence=True),
+ html.Div(id="settings"),
+ ]
+ )
+
+ @app.callback(
+ Output("settings", "children"),
+ [
+ Input("checklist", "value"),
+ Input("datepickerrange", "start_date"),
+ Input("datepickerrange", "end_date"),
+ Input("datepickersingle", "date"),
+ Input("dropdownsingle", "value"),
+ Input("dropdownmulti", "value"),
+ Input("input", "value"),
+ Input("radioitems", "value"),
+ Input("rangeslider", "value"),
+ Input("slider", "value"),
+ Input("tabs", "value"),
+ Input("textarea", "value"),
+ ],
+ )
+ def make_output(*args):
+ return json.dumps(args)
+
+ initial_settings = [
+ ["🏎️"],
+ "2017-08-21",
+ "2024-04-08",
+ "2019-01-01",
+ "2️⃣",
+ ["4️⃣"],
+ "yes",
+ "b",
+ [3, 7],
+ 25,
+ "A",
+ "knock knock",
+ ]
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#settings", json.dumps(initial_settings))
+
+ dash_dcc.find_element("#checklist label:last-child input").click() # 🚀
+
+ dash_dcc.select_date_range("datepickerrange", day_range=(4,))
+ dash_dcc.select_date_range("datepickerrange", day_range=(14,), start_first=False)
+
+ dash_dcc.find_element("#datepickersingle input").click()
+ dash_dcc.select_date_single("datepickersingle", day="20")
+
+ dash_dcc.find_element("#dropdownsingle .Select-input input").send_keys(
+ "one" + Keys.ENTER
+ )
+
+ dash_dcc.find_element("#dropdownmulti .Select-input input").send_keys(
+ "six" + Keys.ENTER
+ )
+
+ dash_dcc.find_element("#input").send_keys(" maybe")
+
+ dash_dcc.find_element("#radioitems label:first-child input").click() # red
+
+ range_slider = dash_dcc.find_element("#rangeslider")
+ dash_dcc.click_at_coord_fractions(range_slider, 0.5, 0.25) # 5
+ dash_dcc.click_at_coord_fractions(range_slider, 0.8, 0.25) # 8
+
+ slider = dash_dcc.find_element("#slider")
+ dash_dcc.click_at_coord_fractions(slider, 0.2, 0.25) # 22
+
+ dash_dcc.find_element("#tabs .tab:last-child").click() # C
+
+ dash_dcc.find_element("#textarea").send_keys(Keys.ENTER + "who's there?")
+
+ edited_settings = [
+ ["🏎️", "🚀"],
+ "2019-05-04",
+ "2019-05-14",
+ "2019-01-20",
+ "1️⃣",
+ ["4️⃣", "6️⃣"],
+ "yes maybe",
+ "r",
+ [5, 8],
+ 22,
+ "C",
+ "knock knock\nwho's there?",
+ ]
+
+ dash_dcc.wait_for_text_to_equal("#settings", json.dumps(edited_settings))
+
+ # now reload the page - all of these settings should persist
+ dash_dcc.wait_for_page()
+ dash_dcc.wait_for_text_to_equal("#settings", json.dumps(edited_settings))
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/misc/test_platter.py b/components/dash-core-components-refresh/tests/integration/misc/test_platter.py
new file mode 100644
index 0000000000..593fe399bd
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/misc/test_platter.py
@@ -0,0 +1,78 @@
+# -*- coding: utf-8 -*-
+import pytest
+
+from selenium.webdriver.common.keys import Keys
+from selenium.common.exceptions import WebDriverException
+
+
+def test_mspl001_dcc_components_platter(platter_app, dash_dcc):
+
+ dash_dcc.start_server(platter_app)
+
+ dash_dcc.wait_for_element("#waitfor")
+
+ # wait for Graph to be ready
+ dash_dcc.wait_for_element("#graph .main-svg")
+
+ dash_dcc.percy_snapshot("gallery")
+
+ dash_dcc.find_element("#dropdown .Select-input input").send_keys("北")
+ dash_dcc.percy_snapshot("gallery - chinese character")
+
+ text_input = dash_dcc.find_element("#textinput")
+ assert (
+ text_input.get_attribute("type") == "text"
+ ), "the default input type should be text"
+
+ text_input.send_keys("HODOR")
+
+ with pytest.raises(WebDriverException):
+ dash_dcc.find_element("#disabled-textinput").send_keys("RODOH")
+
+ dash_dcc.percy_snapshot("gallery - text input")
+
+ # DatePickerSingle and DatePickerRange test
+ # for issue with datepicker when date value is `None`
+
+ def reset_input(elem):
+ elem.send_keys(len(elem.get_attribute("value")) * Keys.BACKSPACE)
+ elem.send_keys("1997-05-03")
+
+ dt_input_1 = dash_dcc.find_element("#dt-single-no-date-value #date")
+ dt_input_1.click()
+ dash_dcc.percy_snapshot(
+ "gallery - DatePickerSingle's datepicker "
+ "when no date value and no initial month specified"
+ )
+ reset_input(dt_input_1)
+
+ dt_input_2 = dash_dcc.find_element("#dt-single-no-date-value-init-month #date")
+ dash_dcc.find_element("label").click()
+ dt_input_2.click()
+ dash_dcc.percy_snapshot(
+ "gallery - DatePickerSingle's datepicker "
+ "when no date value, but initial month is specified"
+ )
+ reset_input(dt_input_2)
+
+ dt_input_3 = dash_dcc.find_element("#dt-range-no-date-values #endDate")
+ dash_dcc.find_element("label").click()
+ dt_input_3.click()
+ dash_dcc.percy_snapshot(
+ "gallery - DatePickerRange's datepicker "
+ "when neither start date nor end date "
+ "nor initial month is specified"
+ )
+ reset_input(dt_input_3)
+
+ dt_input_4 = dash_dcc.find_element("#dt-range-no-date-values-init-month #endDate")
+ dash_dcc.find_element("label").click()
+ dt_input_4.click()
+ dash_dcc.percy_snapshot(
+ "gallery - DatePickerRange's datepicker "
+ "when neither start date nor end date is specified, "
+ "but initial month is"
+ )
+ reset_input(dt_input_4)
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/sliders/assets/transform.js b/components/dash-core-components-refresh/tests/integration/sliders/assets/transform.js
new file mode 100644
index 0000000000..0b68f9a23d
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/sliders/assets/transform.js
@@ -0,0 +1,4 @@
+window.dccFunctions = window.dccFunctions || {};
+window.dccFunctions.transformTooltip = function(value) {
+ return "Transformed " + value
+}
diff --git a/components/dash-core-components-refresh/tests/integration/sliders/test_sliders.py b/components/dash-core-components-refresh/tests/integration/sliders/test_sliders.py
new file mode 100644
index 0000000000..39d3426c0f
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/sliders/test_sliders.py
@@ -0,0 +1,665 @@
+from multiprocessing import Lock
+from dash import Dash, Input, Output, dcc_refresh as dcc, html
+
+
+def test_slsl001_always_visible_slider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ dcc.Slider(
+ id="slider",
+ min=0,
+ max=20,
+ step=1,
+ value=5,
+ tooltip={"always_visible": True},
+ ),
+ html.Div(id="out"),
+ ]
+ )
+
+ @app.callback(Output("out", "children"), [Input("slider", "value")])
+ def update_output(value):
+ return f"You have selected {value}"
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#out", "You have selected 5")
+
+ slider = dash_dcc.find_element("#slider")
+ dash_dcc.click_at_coord_fractions(slider, 0.5, 0.25)
+ dash_dcc.wait_for_text_to_equal("#out", "You have selected 10")
+ dash_dcc.click_at_coord_fractions(slider, 0.75, 0.25)
+ dash_dcc.wait_for_text_to_equal("#out", "You have selected 15")
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl002_always_visible_rangeslider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ style={"width": "400px"},
+ children=[
+ dcc.RangeSlider(
+ id="rangeslider",
+ min=0,
+ max=20,
+ step=1,
+ value=[5, 15],
+ tooltip={"always_visible": True},
+ ),
+ html.Div(id="out"),
+ ],
+ )
+
+ @app.callback(Output("out", "children"), [Input("rangeslider", "value")])
+ def update_output(rng):
+ return f"You have selected {rng[0]}-{rng[1]}"
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#out", "You have selected 5-15")
+
+ slider = dash_dcc.find_element("#rangeslider")
+ dash_dcc.click_at_coord_fractions(slider, 0.15, 0.25)
+ dash_dcc.wait_for_text_to_equal("#out", "You have selected 2-15")
+ dash_dcc.click_at_coord_fractions(slider, 0.5, 0.25)
+ dash_dcc.wait_for_text_to_equal("#out", "You have selected 2-10")
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl003_out_of_range_marks_slider(dash_dcc):
+
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [dcc.Slider(min=0, max=5, marks={i: f"Label {i}" for i in range(-1, 10)})]
+ )
+
+ dash_dcc.start_server(app)
+
+ assert len(dash_dcc.find_elements("span.rc-slider-mark-text")) == 6
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl004_out_of_range_marks_rangeslider(dash_dcc):
+
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [dcc.RangeSlider(min=0, max=5, marks={i: f"Label {i}" for i in range(-1, 10)})]
+ )
+
+ dash_dcc.start_server(app)
+
+ assert len(dash_dcc.find_elements("span.rc-slider-mark-text")) == 6
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl005_slider_tooltip(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Div(
+ [
+ html.Div(
+ dcc.Slider(
+ min=0,
+ max=100,
+ value=65,
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ html.Div(
+ dcc.Slider(
+ min=0,
+ max=100,
+ value=65,
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ html.Div(
+ dcc.Slider(
+ min=0,
+ max=100,
+ value=65,
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ html.Div(
+ dcc.Slider(
+ min=0,
+ max=100,
+ value=65,
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ html.Div(
+ dcc.Slider(
+ id="test-slider",
+ min=0,
+ max=100,
+ value=65,
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ ],
+ style=dict(maxHeight=300, overflowX="scroll", width=400),
+ )
+ ]
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#test-slider")
+ dash_dcc.percy_snapshot(
+ "slider-make sure tooltips are only visible if parent slider is visible"
+ )
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl006_rangeslider_tooltip(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Div(
+ [
+ html.Div(
+ dcc.RangeSlider(
+ min=0,
+ max=100,
+ value=[0, 65],
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100, marginTop=25),
+ ),
+ html.Div(
+ dcc.RangeSlider(
+ min=0,
+ max=100,
+ value=[0, 65],
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ html.Div(
+ dcc.RangeSlider(
+ min=0,
+ max=100,
+ value=[0, 65],
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ html.Div(
+ dcc.RangeSlider(
+ min=0,
+ max=100,
+ value=[0, 65],
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ html.Div(
+ dcc.RangeSlider(
+ id="test-slider",
+ min=0,
+ max=100,
+ value=[0, 65],
+ tooltip={"always_visible": True, "placement": "top"},
+ ),
+ style=dict(height=100),
+ ),
+ ],
+ style=dict(
+ maxHeight=300,
+ overflowX="scroll",
+ backgroundColor="#edf9f7",
+ width=400,
+ ),
+ )
+ ]
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#test-slider")
+ dash_dcc.percy_snapshot("slsl006- dcc.RangeSlider tooltip position")
+
+
+def test_slsl007_drag_value_slider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ dcc.Slider(
+ id="slider",
+ min=0,
+ max=20,
+ step=1,
+ value=5,
+ tooltip={"always_visible": True},
+ ),
+ html.Div(id="out-value"),
+ html.Div(id="out-drag-value"),
+ ]
+ )
+
+ @app.callback(Output("out-drag-value", "children"), [Input("slider", "drag_value")])
+ def update_output1(value):
+ return f"You have dragged {value}"
+
+ @app.callback(Output("out-value", "children"), [Input("slider", "value")])
+ def update_output2(value):
+ return f"You have selected {value}"
+
+ dash_dcc.start_server(app)
+ slider = dash_dcc.find_element("#slider")
+
+ dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 5")
+ dash_dcc.wait_for_text_to_equal("#out-drag-value", "You have dragged 5")
+
+ dash_dcc.click_and_hold_at_coord_fractions(slider, 0.25, 0.25)
+ dash_dcc.move_to_coord_fractions(slider, 0.75, 0.25)
+ dash_dcc.wait_for_text_to_equal("#out-drag-value", "You have dragged 15")
+ dash_dcc.move_to_coord_fractions(slider, 0.5, 0.25)
+ dash_dcc.wait_for_text_to_equal("#out-drag-value", "You have dragged 10")
+ dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 5")
+ dash_dcc.release()
+ dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 10")
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl008_drag_value_rangeslider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ dcc.RangeSlider(
+ id="slider",
+ min=0,
+ max=20,
+ step=1,
+ value=(5, 15),
+ tooltip={"always_visible": True},
+ ),
+ html.Div(id="out-value"),
+ html.Div(id="out-drag-value"),
+ ]
+ )
+
+ @app.callback(Output("out-drag-value", "children"), [Input("slider", "drag_value")])
+ def update_output1(value):
+ value = value or (None, None)
+ return f"You have dragged {value[0]}-{value[1]}"
+
+ @app.callback(Output("out-value", "children"), [Input("slider", "value")])
+ def update_output2(value):
+ return f"You have selected {value[0]}-{value[1]}"
+
+ dash_dcc.start_server(app)
+ slider = dash_dcc.find_element("#slider")
+
+ dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 5-15")
+ dash_dcc.wait_for_text_to_equal("#out-drag-value", "You have dragged 5-15")
+
+ dash_dcc.click_and_hold_at_coord_fractions(slider, 0.25, 0.25)
+ dash_dcc.move_to_coord_fractions(slider, 0.5, 0.25)
+ dash_dcc.wait_for_text_to_equal("#out-drag-value", "You have dragged 10-15")
+ dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 5-15")
+ dash_dcc.release()
+ dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 10-15")
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl009_loading_state(dash_dcc):
+ lock = Lock()
+
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Button(id="test-btn"),
+ html.Label(id="test-div", children=["Horizontal Slider"]),
+ dcc.Slider(
+ id="horizontal-slider",
+ min=0,
+ max=9,
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ value=5,
+ ),
+ ]
+ )
+
+ @app.callback(Output("horizontal-slider", "value"), [Input("test-btn", "n_clicks")])
+ def user_delayed_value(n_clicks):
+ with lock:
+ return 5
+
+ with lock:
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element('#horizontal-slider[data-dash-is-loading="true"]')
+
+ dash_dcc.wait_for_element('#horizontal-slider:not([data-dash-is-loading="true"])')
+
+ with lock:
+ dash_dcc.wait_for_element("#test-btn").click()
+ dash_dcc.wait_for_element('#horizontal-slider[data-dash-is-loading="true"]')
+
+ dash_dcc.wait_for_element('#horizontal-slider:not([data-dash-is-loading="true"])')
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl010_range_loading_state(dash_dcc):
+ lock = Lock()
+
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Button(id="test-btn"),
+ html.Label(id="test-div", children=["Horizontal Range Slider"]),
+ dcc.RangeSlider(
+ id="horizontal-range-slider",
+ min=0,
+ max=9,
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ value=[4, 6],
+ ),
+ ]
+ )
+
+ @app.callback(
+ Output("horizontal-range-slider", "value"), [Input("test-btn", "n_clicks")]
+ )
+ def delayed_value(children):
+ with lock:
+ return [4, 6]
+
+ with lock:
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element(
+ '#horizontal-range-slider[data-dash-is-loading="true"]'
+ )
+
+ dash_dcc.wait_for_element(
+ '#horizontal-range-slider:not([data-dash-is-loading="true"])'
+ )
+
+ with lock:
+ dash_dcc.wait_for_element("#test-btn").click()
+ dash_dcc.wait_for_element(
+ '#horizontal-range-slider[data-dash-is-loading="true"]'
+ )
+
+ dash_dcc.wait_for_element(
+ '#horizontal-range-slider:not([data-dash-is-loading="true"])'
+ )
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl011_horizontal_slider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Label("Horizontal Slider"),
+ dcc.Slider(
+ id="horizontal-slider",
+ min=0,
+ max=9,
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ value=5,
+ ),
+ ]
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#horizontal-slider")
+ dash_dcc.percy_snapshot("horizontal slider")
+
+ dash_dcc.wait_for_element('#horizontal-slider div[role="slider"]').click()
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl012_vertical_slider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Label("Vertical Slider"),
+ dcc.Slider(
+ id="vertical-slider",
+ min=0,
+ max=9,
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ value=5,
+ vertical=True,
+ ),
+ ],
+ style={"height": "500px"},
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#vertical-slider")
+ dash_dcc.percy_snapshot("vertical slider")
+
+ dash_dcc.wait_for_element('#vertical-slider div[role="slider"]').click()
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl013_horizontal_range_slider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Label("Horizontal Range Slider"),
+ dcc.RangeSlider(
+ id="horizontal-range-slider",
+ min=0,
+ max=9,
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ value=[4, 6],
+ ),
+ ]
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#horizontal-range-slider")
+ dash_dcc.percy_snapshot("horizontal range slider")
+
+ dash_dcc.wait_for_element(
+ '#horizontal-range-slider div.rc-slider-handle-1[role="slider"]'
+ ).click()
+ dash_dcc.wait_for_element(
+ '#horizontal-range-slider div.rc-slider-handle-2[role="slider"]'
+ ).click()
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl014_vertical_range_slider(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Label("Vertical Range Slider"),
+ dcc.RangeSlider(
+ id="vertical-range-slider",
+ min=0,
+ max=9,
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ value=[4, 6],
+ vertical=True,
+ ),
+ ],
+ style={"height": "500px"},
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#vertical-range-slider")
+ dash_dcc.percy_snapshot("vertical range slider")
+
+ dash_dcc.wait_for_element(
+ '#vertical-range-slider div.rc-slider-handle-1[role="slider"]'
+ ).click()
+ dash_dcc.wait_for_element(
+ '#vertical-range-slider div.rc-slider-handle-2[role="slider"]'
+ ).click()
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl015_range_slider_step_none(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Label("Steps = Marks Slider"),
+ dcc.Slider(
+ id="none-step-slider",
+ min=0,
+ max=6,
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ step=None,
+ value=4.6,
+ vertical=False,
+ ),
+ ],
+ style={"height": "500px"},
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#none-step-slider")
+ dash_dcc.percy_snapshot("none step slider")
+
+ dash_dcc.wait_for_element(
+ '#none-step-slider div.rc-slider-handle[aria-valuenow="5"]'
+ )
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl015_range_slider_no_min_max(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Label("No Min or Max Slider"),
+ dcc.Slider(
+ id="no-min-max-step-slider",
+ marks={i: f"Label {i}" if i == 1 else str(i) for i in range(1, 6)},
+ step=None,
+ value=5,
+ vertical=False,
+ ),
+ ],
+ style={"height": "500px"},
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#no-min-max-step-slider")
+ dash_dcc.percy_snapshot("no-min-max step slider")
+
+ dash_dcc.wait_for_element(
+ '#no-min-max-step-slider div.rc-slider-handle[aria-valuemax="5"]'
+ )
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_sls016_sliders_format_tooltips(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ dcc.Slider(
+ value=34,
+ min=20,
+ max=100,
+ id="slider",
+ tooltip={
+ "template": "Custom tooltip: {value}",
+ "always_visible": True,
+ "style": {"padding": "8px"},
+ },
+ ),
+ dcc.RangeSlider(
+ value=[48, 60],
+ min=20,
+ max=100,
+ id="range-slider",
+ tooltip={"template": "Custom tooltip: {value}", "always_visible": True},
+ ),
+ dcc.Slider(
+ min=20,
+ max=100,
+ id="slider-transform",
+ tooltip={"always_visible": True, "transform": "transformTooltip"},
+ ),
+ ],
+ style={"padding": "12px", "marginTop": "48px"},
+ )
+
+ dash_dcc.start_server(app)
+ # dash_dcc.wait_for_element("#slider")
+
+ dash_dcc.wait_for_text_to_equal(
+ "#slider .rc-slider-tooltip-content", "Custom tooltip: 34"
+ )
+ dash_dcc.wait_for_text_to_equal(
+ "#range-slider .rc-slider-tooltip-content", "Custom tooltip: 48"
+ )
+ dash_dcc.wait_for_text_to_equal(
+ "#range-slider > div:nth-child(1) > div:last-child .rc-slider-tooltip-content",
+ "Custom tooltip: 60",
+ )
+ dash_dcc.wait_for_style_to_equal(
+ "#slider .rc-slider-tooltip-inner > div", "padding", "8px"
+ )
+ dash_dcc.wait_for_text_to_equal(
+ "#slider-transform .rc-slider-tooltip-content", "Transformed 20"
+ )
+
+ dash_dcc.percy_snapshot("sliders-format-tooltips")
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl017_marks_limit_500(dash_dcc):
+ """Test that slider works with exactly 500 marks"""
+ app = Dash(__name__)
+ marks_500 = {str(i): f"Mark {i}" for i in range(500)}
+ app.layout = html.Div(
+ [
+ dcc.Slider(id="slider", min=0, max=499, marks=marks_500, value=250),
+ html.Div(id="output"),
+ ]
+ )
+
+ @app.callback(Output("output", "children"), [Input("slider", "value")])
+ def update_output(value):
+ return f"Selected: {value}"
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#output", "Selected: 250")
+
+ # No warnings should be logged for 500 marks
+ assert dash_dcc.get_logs() == []
+
+
+def test_slsl018_marks_limit_exceeded(dash_dcc):
+ """Test behavior when marks exceed 500 limit"""
+ app = Dash(__name__)
+ marks_501 = {str(i): f"Mark {i}" for i in range(501)}
+ app.layout = html.Div(
+ [
+ dcc.Slider(id="slider", min=0, max=500, marks=marks_501, value=250),
+ html.Div(id="output"),
+ ]
+ )
+
+ @app.callback(Output("output", "children"), [Input("slider", "value")])
+ def update_output(value):
+ return f"Selected: {value}"
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#output", "Selected: 250")
+
+ # Check that warning is logged
+ logs = dash_dcc.get_logs()
+ assert len(logs) > 0
+ warning_found = any("Too many marks" in log["message"] for log in logs)
+ assert warning_found, "Expected warning about too many marks not found in logs"
diff --git a/components/dash-core-components-refresh/tests/integration/sliders/test_sliders_shorthands.py b/components/dash-core-components-refresh/tests/integration/sliders/test_sliders_shorthands.py
new file mode 100644
index 0000000000..d9f452b07d
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/sliders/test_sliders_shorthands.py
@@ -0,0 +1,197 @@
+from dash import Dash, dcc_refresh as dcc, html
+import numpy as np
+import math
+import time
+
+
+def test_slsh001_rangeslider_shorthand_props(dash_dcc):
+ NUMBERS = [10 * N for N in np.arange(1, 2, 0.5)]
+ # TEST_RANGES = []
+ LAYOUT = []
+ TEST_CASES = []
+
+ for n in NUMBERS:
+ TEST_CASES.extend(
+ [
+ [n, n * 1.5, abs(n * 1.5 - n) / 5],
+ [-n, 0, n / 10],
+ [-n, n, n / 10],
+ [-1.5 * n, -1 * n, n / 7],
+ ]
+ )
+
+ for t in TEST_CASES:
+ min, max, steps = t
+ marks = {
+ i: f"Label {i}" if i == 1 else str(i)
+ for i in range(math.ceil(min), math.floor(max))
+ }
+
+ LAYOUT.extend(
+ [
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.Slider(min, max),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.RangeSlider(min, max),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}, {steps}",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.Slider(min, max, steps),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}, {steps}",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.RangeSlider(min, max, steps),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}, {steps}, value={min + steps}",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.Slider(min, max, steps, value=min + steps),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}, {steps}, value=[{min + steps},{min + steps * 3}]",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.RangeSlider(
+ min, max, steps, value=[min + steps, min + steps * 3]
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}, {steps}, value={min + steps}, marks={marks}",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.Slider(
+ min,
+ max,
+ steps,
+ value=min + steps,
+ marks=marks,
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}, {steps},value=[{min + steps},{min + steps * 3}], marks={marks}",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.RangeSlider(
+ min,
+ max,
+ steps,
+ value=[min + steps, min + steps * 3],
+ marks=marks,
+ ),
+ ]
+ ),
+ html.Div(
+ [
+ html.Div(
+ f"{min} - {max}, {steps},value=[{min + steps},{min + steps * 3}], marks=None",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ dcc.RangeSlider(
+ min,
+ max,
+ steps,
+ value=[min + steps, min + steps * 3],
+ marks=None,
+ ),
+ ]
+ ),
+ ]
+ )
+
+ app = Dash(__name__)
+ app.layout = html.Div(LAYOUT)
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element(".rc-slider")
+ dash_dcc.percy_snapshot("slsh001 - test_slsh001_rangeslider_shorthand_props", True)
+
+
+def test_slsh002_sliders_marks_si_unit_format(dash_dcc):
+
+ LAYOUT = []
+
+ # Showing SI Units
+ LAYOUT.extend(
+ [
+ html.Div(
+ "Testing SI units",
+ style={"marginBottom": 10, "marginTop": 30},
+ ),
+ ]
+ )
+
+ for n in range(-20, 20):
+ min = 0
+ max = pow(10, n)
+
+ LAYOUT.extend(
+ [
+ html.Div(
+ [
+ html.B(
+ f"min={min}, max={max}(=10^{n})",
+ style={"marginBottom": 15, "marginTop": 25},
+ ),
+ (
+ html.Div(
+ "(Known issue: Slider does not seem to work for precision below 10^(-6))"
+ )
+ if n <= -6
+ else None
+ ),
+ html.Div("value is undefined"),
+ dcc.Slider(min, max),
+ dcc.RangeSlider(min, max),
+ html.Div(f"value=0.4 * 10^{n}"),
+ dcc.Slider(min, max, value=0.4 * max),
+ dcc.RangeSlider(min, max, value=[0.2 * max, 0.4 * max]),
+ html.Div(f"value=0.5 * 10^{n}"),
+ dcc.Slider(min, max, value=0.5 * max),
+ dcc.RangeSlider(min, max, value=[0.2 * max, 0.5 * max]),
+ ]
+ )
+ ]
+ )
+
+ app = Dash(__name__)
+ app.layout = html.Div(LAYOUT)
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element(".rc-slider")
+ time.sleep(2)
+ dash_dcc.percy_snapshot("slsh002 - test_slsh002_sliders_marks_si_unit_format", True)
diff --git a/components/dash-core-components-refresh/tests/integration/store/conftest.py b/components/dash-core-components-refresh/tests/integration/store/conftest.py
new file mode 100644
index 0000000000..26e989002d
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/store/conftest.py
@@ -0,0 +1,76 @@
+import sys
+import json
+import pytest
+import uuid
+from dash import Dash, Input, State, Output, dcc_refresh as dcc, html
+from dash.exceptions import PreventUpdate
+
+
+UUID = f"store-test-{uuid.uuid4().hex}"
+
+
+@pytest.fixture(scope="module")
+def store_app():
+ app = Dash(__name__)
+ app.uuid = UUID
+ app.layout = html.Div(
+ [
+ dcc.Store(id="memory", storage_type="memory", data=app.uuid),
+ dcc.Store(id="local", storage_type="local"),
+ dcc.Store(id="session", storage_type="session"),
+ html.Button("click me", id="btn"),
+ html.Button("clear data", id="clear-btn"),
+ html.Div(id="output"),
+ ]
+ )
+
+ @app.callback(
+ Output("output", "children"),
+ [Input("memory", "modified_timestamp")],
+ [State("memory", "data")],
+ )
+ def write_memory(modified_ts, data):
+ if data is None:
+ return ""
+ return json.dumps(data)
+
+ @app.callback(
+ [
+ Output("local", "clear_data"),
+ Output("memory", "clear_data"),
+ Output("session", "clear_data"),
+ ],
+ [Input("clear-btn", "n_clicks")],
+ )
+ def on_clear(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+ return True, True, True
+
+ @app.callback(
+ [Output("memory", "data"), Output("local", "data"), Output("session", "data")],
+ [Input("btn", "n_clicks")],
+ )
+ def on_click(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+ return ({"n_clicks": n_clicks},) * 3
+
+ yield app
+
+
+@pytest.fixture(scope="session")
+def csv_5mb():
+ import mimesis
+
+ buf, chunks = None, []
+ limit = 5 * 1024 * 1024
+ while sys.getsizeof(buf) <= limit:
+ g = mimesis.Generic()
+ chunk = "\n".join(
+ (f"{g.person.full_name()},{g.person.email()}" for _ in range(10000))
+ )
+ chunks.append(chunk)
+ buf = "".join(chunks)
+
+ yield buf[len(chunk) : limit]
diff --git a/components/dash-core-components-refresh/tests/integration/store/test_component_props.py b/components/dash-core-components-refresh/tests/integration/store/test_component_props.py
new file mode 100644
index 0000000000..4d286f8b7f
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/store/test_component_props.py
@@ -0,0 +1,206 @@
+import json
+import time
+from pytest import approx
+from dash import Dash, Input, State, Output, dcc_refresh as dcc, html
+from dash.exceptions import PreventUpdate
+import dash.testing.wait as wait
+
+
+def test_stcp001_clear_data_on_all_types(store_app, dash_dcc):
+ dash_dcc.start_server(store_app)
+
+ assert dash_dcc.wait_for_contains_text("#output", store_app.uuid)
+
+ dash_dcc.multiple_click("#btn", 3)
+ wait.until(lambda: dash_dcc.get_local_storage() == {"n_clicks": 3}, timeout=1)
+
+ # button click sets clear_data=True on all type of stores
+ dash_dcc.find_element("#clear-btn").click()
+
+ dash_dcc.wait_for_text_to_equal("#output", "")
+
+ assert (
+ not dash_dcc.find_element("#output").text
+ and not dash_dcc.get_local_storage()
+ and not dash_dcc.get_session_storage()
+ ), "set clear_data=True should clear all data in three storage types"
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_stcp002_modified_ts(store_app, dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ dcc.Store(id="initial-storage", storage_type="session"),
+ html.Button("set-init-storage", id="set-init-storage"),
+ html.Div(id="init-output"),
+ ]
+ )
+
+ @app.callback(
+ Output("initial-storage", "data"),
+ [Input("set-init-storage", "n_clicks")],
+ )
+ def on_init(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+ return "initialized"
+
+ @app.callback(
+ Output("init-output", "children"),
+ [Input("initial-storage", "modified_timestamp")],
+ [State("initial-storage", "data")],
+ )
+ def init_output(ts, data):
+ return json.dumps({"data": data, "ts": ts})
+
+ dash_dcc.start_server(app)
+
+ dash_dcc.find_element("#set-init-storage").click()
+ # the python ts ends at seconds while javascript one ends at ms
+ ts = float(time.time() * 1000)
+
+ wait.until(
+ lambda: "initialized" in dash_dcc.find_element("#init-output").text, timeout=3
+ )
+
+ output_data = json.loads(dash_dcc.find_element("#init-output").text)
+
+ assert (
+ output_data.get("data") == "initialized"
+ ), "the data should be the text set in on_init"
+ assert ts == approx(
+ output_data.get("ts"), abs=40
+ ), "the modified_timestamp should be updated right after the click action"
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_stcp003_initial_falsy(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Div(
+ [
+ storage_type,
+ dcc.Store(
+ storage_type=storage_type, id="zero-" + storage_type, data=0
+ ),
+ dcc.Store(
+ storage_type=storage_type,
+ id="false-" + storage_type,
+ data=False,
+ ),
+ dcc.Store(
+ storage_type=storage_type, id="null-" + storage_type, data=None
+ ),
+ dcc.Store(
+ storage_type=storage_type, id="empty-" + storage_type, data=""
+ ),
+ ]
+ )
+ for storage_type in ("memory", "local", "session")
+ ],
+ id="content",
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#content", "memory\nlocal\nsession")
+
+ for storage_type in ("local", "session"):
+ getter = getattr(dash_dcc, f"get_{storage_type}_storage")
+ assert getter("zero-" + storage_type) == 0, storage_type
+ assert getter("false-" + storage_type) is False, storage_type
+ assert getter("null-" + storage_type) is None, storage_type
+ assert getter("empty-" + storage_type) == "", storage_type
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_stcp004_remount_store_component(dash_dcc):
+ app = Dash(__name__, suppress_callback_exceptions=True)
+
+ content = html.Div(
+ [
+ dcc.Store(id="memory", storage_type="memory"),
+ dcc.Store(id="local", storage_type="local"),
+ dcc.Store(id="session", storage_type="session"),
+ html.Button("click me", id="btn"),
+ html.Button("clear data", id="clear-btn"),
+ html.Div(id="output"),
+ ]
+ )
+
+ app.layout = html.Div([html.Button("start", id="start"), html.Div(id="content")])
+
+ @app.callback(Output("content", "children"), [Input("start", "n_clicks")])
+ def start(n):
+ return content if n else "init"
+
+ @app.callback(
+ Output("output", "children"),
+ [
+ Input("memory", "modified_timestamp"),
+ Input("local", "modified_timestamp"),
+ Input("session", "modified_timestamp"),
+ ],
+ [State("memory", "data"), State("local", "data"), State("session", "data")],
+ )
+ def write_memory(tsm, tsl, tss, datam, datal, datas):
+ return json.dumps([datam, datal, datas])
+
+ @app.callback(
+ [
+ Output("local", "clear_data"),
+ Output("memory", "clear_data"),
+ Output("session", "clear_data"),
+ ],
+ [Input("clear-btn", "n_clicks")],
+ )
+ def on_clear(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+ return True, True, True
+
+ @app.callback(
+ [Output("memory", "data"), Output("local", "data"), Output("session", "data")],
+ [Input("btn", "n_clicks")],
+ )
+ def on_click(n_clicks):
+ return ({"n_clicks": n_clicks},) * 3
+
+ dash_dcc.start_server(app)
+
+ dash_dcc.wait_for_text_to_equal("#content", "init")
+
+ dash_dcc.find_element("#start").click()
+ dash_dcc.wait_for_text_to_equal(
+ "#output", '[{"n_clicks": null}, {"n_clicks": null}, {"n_clicks": null}]'
+ )
+
+ dash_dcc.find_element("#btn").click()
+ dash_dcc.wait_for_text_to_equal(
+ "#output", '[{"n_clicks": 1}, {"n_clicks": 1}, {"n_clicks": 1}]'
+ )
+
+ dash_dcc.find_element("#clear-btn").click()
+ dash_dcc.wait_for_text_to_equal("#output", "[null, null, null]")
+
+ dash_dcc.find_element("#btn").click()
+ dash_dcc.wait_for_text_to_equal(
+ "#output", '[{"n_clicks": 2}, {"n_clicks": 2}, {"n_clicks": 2}]'
+ )
+
+ # now remount content components
+ dash_dcc.find_element("#start").click()
+ dash_dcc.wait_for_text_to_equal(
+ "#output", '[{"n_clicks": null}, {"n_clicks": null}, {"n_clicks": null}]'
+ )
+
+ dash_dcc.find_element("#btn").click()
+ dash_dcc.wait_for_text_to_equal(
+ "#output", '[{"n_clicks": 1}, {"n_clicks": 1}, {"n_clicks": 1}]'
+ )
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/store/test_data_lifecycle.py b/components/dash-core-components-refresh/tests/integration/store/test_data_lifecycle.py
new file mode 100644
index 0000000000..e3d5d7da17
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/store/test_data_lifecycle.py
@@ -0,0 +1,44 @@
+import dash.testing.wait as wait
+
+
+def test_stdl001_data_lifecycle_with_different_condition(store_app, dash_dcc):
+ dash_dcc.start_server(store_app)
+
+ nclicks = 10
+ dash_dcc.multiple_click("#btn", nclicks)
+
+ dash_dcc.wait_for_text_to_equal("#output", f'{{"n_clicks": {nclicks}}}')
+ assert dash_dcc.get_local_storage() == {
+ "n_clicks": nclicks
+ }, "local storage should contain the same click nums"
+ assert dash_dcc.get_session_storage() == {
+ "n_clicks": nclicks
+ }, "session storage should contain the same click nums"
+
+ dash_dcc.driver.refresh()
+ dash_dcc.wait_for_text_to_equal("#output", f'"{store_app.uuid}"')
+ assert dash_dcc.get_local_storage() == {"n_clicks": nclicks}
+ assert dash_dcc.get_session_storage() == {"n_clicks": nclicks}
+
+ dash_dcc.open_new_tab()
+ dash_dcc.toggle_window() # switch to the new tab
+ assert dash_dcc.get_local_storage() == {
+ "n_clicks": nclicks
+ }, "local storage should be persistent"
+ dash_dcc.wait_for_text_to_equal("#output", f'"{store_app.uuid}"')
+
+ dash_dcc.multiple_click("#btn", 2)
+ wait.until(lambda: dash_dcc.get_session_storage() == {"n_clicks": 2}, timeout=1)
+ assert (
+ '"n_clicks": 2' in dash_dcc.wait_for_element("#output").text
+ ), "memory storage should reflect to the new clicks"
+
+ dash_dcc.driver.close()
+ dash_dcc.switch_window()
+ assert dash_dcc.get_local_storage() == {"n_clicks": 2}
+ dash_dcc.wait_for_text_to_equal("#output", f'"{store_app.uuid}"')
+ assert dash_dcc.get_session_storage() == {
+ "n_clicks": nclicks
+ }, "session storage should be specific per browser tab window"
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/store/test_store_data.py b/components/dash-core-components-refresh/tests/integration/store/test_store_data.py
new file mode 100644
index 0000000000..7d5e52c929
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/store/test_store_data.py
@@ -0,0 +1,164 @@
+import sys
+import json
+import hashlib
+import itertools
+import pytest
+from dash import Dash, Input, Output, State, dcc_refresh as dcc, html
+from dash.exceptions import PreventUpdate
+
+
+def test_stda001_data_types(dash_dcc):
+ app = Dash(__name__)
+
+ types = [
+ ("str", "hello"),
+ ("number", 1),
+ ("dict", {"data": [2, 3, None]}),
+ ("list", [5, -6, 700000, 1e-12]),
+ ("null", None),
+ ("bool", True),
+ ("bool", False),
+ ("empty-dict", {}),
+ ]
+ data_types = list(
+ itertools.chain(*itertools.combinations(types, 2))
+ ) + [ # No combinations as it add much test time.
+ ("list-dict-1", [1, 2, {"data": [55, 66, 77], "dummy": "dum"}]),
+ ("list-dict-2", [1, 2, {"data": [111, 99, 88]}]),
+ ("dict-3", {"a": 1, "c": 1}),
+ ("dict-2", {"a": 1, "b": None}),
+ ]
+
+ app.layout = html.Div(
+ [
+ html.Div(id="output"),
+ html.Button("click", id="click"),
+ dcc.Store(id="store"),
+ ]
+ )
+
+ @app.callback(
+ Output("output", "children"),
+ [Input("store", "modified_timestamp")],
+ [State("store", "data")],
+ )
+ def on_data(ts, data):
+ if ts is None:
+ raise PreventUpdate
+ return json.dumps(data)
+
+ @app.callback(Output("store", "data"), [Input("click", "n_clicks")])
+ def on_click(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+ return data_types[n_clicks - 1][1]
+
+ dash_dcc.start_server(app)
+
+ button = dash_dcc.wait_for_element("#click")
+ for data_type in data_types:
+ button.click()
+ dash_dcc.wait_for_text_to_equal("#output", json.dumps(data_type[1]))
+
+ assert dash_dcc.get_logs() == []
+
+
+def test_stda002_nested_data(dash_dcc):
+ app = Dash(__name__)
+
+ nested = {"nested": {"nest": "much"}}
+ nested_list = dict(my_list=[1, 2, 3])
+
+ app.layout = html.Div(
+ [
+ dcc.Store(id="store", storage_type="local"),
+ html.Button("set object as key", id="obj-btn"),
+ html.Button("set list as key", id="list-btn"),
+ html.Output(id="output"),
+ ]
+ )
+
+ @app.callback(
+ Output("store", "data"),
+ [
+ Input("obj-btn", "n_clicks_timestamp"),
+ Input("list-btn", "n_clicks_timestamp"),
+ ],
+ )
+ def on_obj_click(obj_ts, list_ts):
+ if obj_ts is None and list_ts is None:
+ raise PreventUpdate
+
+ # python 3 got the default props bug. plotly/dash#396
+ if (obj_ts and not list_ts) or obj_ts > list_ts:
+ return nested
+ else:
+ return nested_list
+
+ @app.callback(
+ Output("output", "children"),
+ [Input("store", "modified_timestamp")],
+ [State("store", "data")],
+ )
+ def on_ts(ts, data):
+ if ts is None:
+ raise PreventUpdate
+ return json.dumps(data)
+
+ dash_dcc.start_server(app)
+
+ obj_btn = dash_dcc.wait_for_element("#obj-btn")
+ list_btn = dash_dcc.find_element("#list-btn")
+
+ obj_btn.click()
+ dash_dcc.wait_for_text_to_equal("#output", json.dumps(nested))
+ # it would of crashed the app before adding the recursive check.
+
+ list_btn.click()
+ dash_dcc.wait_for_text_to_equal("#output", json.dumps(nested_list))
+
+ assert dash_dcc.get_logs() == []
+
+
+@pytest.mark.skipif(
+ sys.version_info < (3, 6),
+ reason="tests requires dependency only available in 3.6+",
+)
+@pytest.mark.parametrize("storage_type", ("memory", "local", "session"))
+def test_stda003_large_data_size(storage_type, csv_5mb, dash_dcc):
+ def fingerprint(data):
+ return hashlib.sha1(data.encode("utf-8")).hexdigest()
+
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ dcc.Store(id=storage_type, storage_type=storage_type),
+ html.Button("big data", id="btn"),
+ html.Div(id="out"),
+ ]
+ )
+
+ @app.callback(
+ Output("out", "children"),
+ [Input(storage_type, "modified_timestamp")],
+ [State(storage_type, "data")],
+ )
+ def update_output(mts, data):
+ if data is None:
+ return "nil"
+ return fingerprint(data)
+
+ @app.callback(Output(storage_type, "data"), [Input("btn", "n_clicks")])
+ def on_click(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+ return csv_5mb
+
+ dash_dcc.start_server(app)
+
+ assert dash_dcc.find_element("#out").text == "nil"
+
+ dash_dcc.find_element("#btn").click()
+ dash_dcc.wait_for_text_to_equal("#out", fingerprint(csv_5mb))
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/tab/test_tabs.py b/components/dash-core-components-refresh/tests/integration/tab/test_tabs.py
new file mode 100644
index 0000000000..a87ff16b73
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/tab/test_tabs.py
@@ -0,0 +1,162 @@
+from dash import Dash, Input, Output, dcc_refresh as dcc, html
+
+
+def test_tabs001_in_vertical_mode(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ dcc.Tabs(
+ id="tabs",
+ value="tab-3",
+ children=[
+ dcc.Tab(
+ label="Tab one",
+ value="tab-1",
+ id="tab-1",
+ children=[html.Div("Tab One Content")],
+ ),
+ dcc.Tab(
+ label="Tab two",
+ value="tab-2",
+ id="tab-2",
+ children=[html.Div("Tab Two Content")],
+ ),
+ dcc.Tab(
+ label="Tab three",
+ value="tab-3",
+ id="tab-3",
+ children=[html.Div("Tab Three Content")],
+ ),
+ ],
+ vertical=True,
+ ),
+ html.Div(id="tabs-content"),
+ ]
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#tab-3", "Tab three")
+ dash_dcc.percy_snapshot("Core Tabs - vertical mode")
+ assert dash_dcc.get_logs() == []
+
+
+def test_tabs002_without_children(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.H1("Dash Tabs component demo"),
+ dcc.Tabs(
+ id="tabs",
+ value="tab-2",
+ children=[
+ dcc.Tab(label="Tab one", value="tab-1", id="tab-1"),
+ dcc.Tab(label="Tab two", value="tab-2", id="tab-2"),
+ ],
+ ),
+ html.Div(id="tabs-content"),
+ ]
+ )
+
+ @app.callback(
+ Output("tabs-content", "children"),
+ [Input("tabs", "value")],
+ )
+ def render_content(tab):
+ if tab == "tab-1":
+ return html.Div([html.H3("Test content 1")], id="test-tab-1")
+ elif tab == "tab-2":
+ return html.Div([html.H3("Test content 2")], id="test-tab-2")
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#tabs-content", "Test content 2")
+ dash_dcc.percy_snapshot("Core initial tab - tab 2")
+
+ dash_dcc.wait_for_element("#tab-1").click()
+ dash_dcc.wait_for_text_to_equal("#tabs-content", "Test content 1")
+ assert dash_dcc.get_logs() == []
+
+
+def test_tabs003_without_children_undefined(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.H1("Dash Tabs component demo"),
+ dcc.Tabs(id="tabs", value="tab-1"),
+ html.Div(id="tabs-content"),
+ ],
+ id="app",
+ )
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_element("#tabs-content")
+ assert dash_dcc.find_element("#app").text == "Dash Tabs component demo"
+ assert dash_dcc.find_element(".tab-content").get_property("innerHTML") == ""
+ assert dash_dcc.find_element("#tabs").get_property("innerHTML") == ""
+ assert dash_dcc.find_element("#tabs-content").get_property("innerHTML") == ""
+ assert dash_dcc.get_logs() == []
+
+
+def test_tabs004_without_value(dash_dcc):
+ app = Dash(__name__)
+
+ app.layout = html.Div(
+ [
+ html.H1("Dash Tabs component demo"),
+ dcc.Tabs(
+ id="tabs-without-value",
+ children=[
+ dcc.Tab(label="Tab One", value="tab-1"),
+ dcc.Tab(label="Tab Two", value="tab-2"),
+ ],
+ ),
+ html.Div(id="tabs-content"),
+ ]
+ )
+
+ @app.callback(
+ Output("tabs-content", "children"), [Input("tabs-without-value", "value")]
+ )
+ def render_content(tab):
+ if tab == "tab-1":
+ return html.H3("Default selected Tab content 1")
+ elif tab == "tab-2":
+ return html.H3("Tab content 2")
+
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#tabs-content", "Default selected Tab content 1")
+ assert dash_dcc.get_logs() == []
+
+
+def test_tabs005_disabled(dash_dcc):
+ app = Dash(__name__, assets_folder="../../assets")
+ app.layout = html.Div(
+ [
+ html.H1("Dash Tabs component with disabled tab demo"),
+ dcc.Tabs(
+ id="tabs-example",
+ value="tab-2",
+ children=[
+ dcc.Tab(
+ label="Disabled Tab",
+ value="tab-1",
+ id="tab-1",
+ className="test-custom-tab",
+ disabled=True,
+ ),
+ dcc.Tab(
+ label="Active Tab",
+ value="tab-2",
+ id="tab-2",
+ className="test-custom-tab",
+ ),
+ ],
+ ),
+ html.Div(id="tabs-content-example"),
+ ]
+ )
+
+ dash_dcc.start_server(app)
+
+ dash_dcc.wait_for_element("#tab-2")
+ dash_dcc.wait_for_element(".tab--disabled")
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/tab/test_tabs_with_graphs.py b/components/dash-core-components-refresh/tests/integration/tab/test_tabs_with_graphs.py
new file mode 100644
index 0000000000..838d07aa58
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/tab/test_tabs_with_graphs.py
@@ -0,0 +1,225 @@
+from dash import Dash, Input, Output, dcc_refresh as dcc, html
+from dash.exceptions import PreventUpdate
+import json
+import os
+import pytest
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support import expected_conditions as EC
+from selenium.webdriver.support.ui import WebDriverWait
+import time
+
+
+@pytest.mark.parametrize("is_eager", [True, False])
+def test_tagr001_graph_does_not_resize_in_tabs(dash_dcc, is_eager):
+ app = Dash(__name__, eager_loading=is_eager)
+ app.layout = html.Div(
+ [
+ html.H1("Dash Tabs component demo"),
+ dcc.Tabs(
+ id="tabs-example",
+ value="tab-1-example",
+ children=[
+ dcc.Tab(label="Tab One", value="tab-1-example", id="tab-1"),
+ dcc.Tab(label="Tab Two", value="tab-2-example", id="tab-2"),
+ dcc.Tab(
+ label="Tab Three",
+ value="tab-3-example",
+ id="tab-3",
+ disabled=True,
+ disabled_className="disabled-tab",
+ ),
+ ],
+ ),
+ html.Div(id="tabs-content-example"),
+ ]
+ )
+
+ @app.callback(
+ Output("tabs-content-example", "children"),
+ [Input("tabs-example", "value")],
+ )
+ def render_content(tab):
+ if tab == "tab-1-example":
+ return html.Div(
+ [
+ html.H3("Tab content 1"),
+ dcc.Graph(
+ id="graph-1-tabs",
+ figure={
+ "data": [{"x": [1, 2, 3], "y": [3, 1, 2], "type": "bar"}]
+ },
+ ),
+ ]
+ )
+ elif tab == "tab-2-example":
+ return html.Div(
+ [
+ html.H3("Tab content 2"),
+ dcc.Graph(
+ id="graph-2-tabs",
+ figure={
+ "data": [{"x": [1, 2, 3], "y": [5, 10, 6], "type": "bar"}]
+ },
+ ),
+ ]
+ )
+
+ dash_dcc.start_server(app)
+
+ tab_one = dash_dcc.wait_for_element("#tab-1")
+ tab_two = dash_dcc.wait_for_element("#tab-2")
+
+ # wait for disabled tab with custom className
+ dash_dcc.wait_for_element("#tab-3.disabled-tab")
+
+ WebDriverWait(dash_dcc.driver, 10).until(
+ EC.element_to_be_clickable((By.ID, "tab-2"))
+ )
+
+ # wait for Graph to be ready
+ WebDriverWait(dash_dcc.driver, 10).until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-1-tabs .main-svg"))
+ )
+
+ is_eager = "eager" if is_eager else "lazy"
+
+ dash_dcc.percy_snapshot(
+ f"Tabs with Graph - initial (graph should not resize) ({is_eager})"
+ )
+ tab_two.click()
+
+ # wait for Graph to be ready
+ WebDriverWait(dash_dcc.driver, 10).until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-2-tabs .main-svg"))
+ )
+
+ dash_dcc.percy_snapshot(
+ f"Tabs with Graph - clicked tab 2 (graph should not resize) ({is_eager})"
+ )
+
+ WebDriverWait(dash_dcc.driver, 10).until(
+ EC.element_to_be_clickable((By.ID, "tab-1"))
+ )
+
+ tab_one.click()
+
+ # wait for Graph to be loaded after clicking
+ WebDriverWait(dash_dcc.driver, 10).until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-1-tabs .main-svg"))
+ )
+
+ dash_dcc.percy_snapshot(
+ f"Tabs with Graph - clicked tab 1 (graph should not resize) ({is_eager})"
+ )
+
+ assert dash_dcc.get_logs() == []
+
+
+@pytest.mark.parametrize("is_eager", [True, False])
+def test_tagr002_tabs_render_without_selected(dash_dcc, is_eager):
+ app = Dash(__name__, eager_loading=is_eager)
+
+ menu = html.Div([html.Div("one", id="one"), html.Div("two", id="two")])
+
+ tabs_one = html.Div(
+ [dcc.Tabs([dcc.Tab(dcc.Graph(id="graph-one"), label="tab-one-one")])],
+ id="tabs-one",
+ style={"display": "none"},
+ )
+
+ tabs_two = html.Div(
+ [dcc.Tabs([dcc.Tab(dcc.Graph(id="graph-two"), label="tab-two-one")])],
+ id="tabs-two",
+ style={"display": "none"},
+ )
+
+ app.layout = html.Div([menu, tabs_one, tabs_two])
+
+ for i in ("one", "two"):
+
+ @app.callback(Output(f"tabs-{i}", "style"), [Input(i, "n_clicks")])
+ def on_click_update_tabs(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+
+ if n_clicks % 2 == 1:
+ return {"display": "block"}
+ return {"display": "none"}
+
+ @app.callback(Output(f"graph-{i}", "figure"), [Input(i, "n_clicks")])
+ def on_click_update_graph(n_clicks):
+ if n_clicks is None:
+ raise PreventUpdate
+
+ return {
+ "data": [{"x": [1, 2, 3, 4], "y": [4, 3, 2, 1]}],
+ "layout": {"width": 700, "height": 450},
+ }
+
+ dash_dcc.start_server(app)
+
+ button_one = dash_dcc.wait_for_element("#one")
+ button_two = dash_dcc.wait_for_element("#two")
+
+ button_one.click()
+
+ # wait for tabs to be loaded after clicking
+ WebDriverWait(dash_dcc.driver, 10).until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-one .main-svg"))
+ )
+
+ is_eager = "eager" if is_eager else "lazy"
+
+ time.sleep(1)
+ dash_dcc.percy_snapshot(f"Tabs-1 rendered ({is_eager})")
+
+ button_two.click()
+
+ # wait for tabs to be loaded after clicking
+ WebDriverWait(dash_dcc.driver, 10).until(
+ EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-two .main-svg"))
+ )
+
+ time.sleep(1)
+ dash_dcc.percy_snapshot(f"Tabs-2 rendered ({is_eager})")
+
+ # do some extra tests while we're here
+ # and have access to Graph and plotly.js
+ check_graph_config_shape(dash_dcc)
+
+ assert dash_dcc.get_logs() == []
+
+
+def check_graph_config_shape(dash_dcc):
+ config_schema = dash_dcc.driver.execute_script(
+ "return Plotly.PlotSchema.get().config;"
+ )
+ with open(os.path.join(dcc.__path__[0], "metadata.json")) as meta:
+ graph_meta = json.load(meta)["src/components/Graph.react.js"]
+ config_prop_shape = graph_meta["props"]["config"]["type"]["value"]
+
+ ignored_config = [
+ "setBackground",
+ "showSources",
+ "logging",
+ "globalTransforms",
+ "notifyOnLogging",
+ "role",
+ "typesetMath",
+ ]
+
+ def crawl(schema, props):
+ for prop_name in props:
+ assert prop_name in schema
+
+ for item_name, item in schema.items():
+ if item_name in ignored_config:
+ continue
+
+ assert item_name in props
+ if "valType" not in item:
+ crawl(item, props[item_name]["value"])
+
+ crawl(config_schema, config_prop_shape)
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/test_title_props.py b/components/dash-core-components-refresh/tests/integration/test_title_props.py
new file mode 100644
index 0000000000..6bd62aa5b9
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/test_title_props.py
@@ -0,0 +1,85 @@
+# -*- coding: UTF-8 -*-
+
+from dash.testing import wait
+from dash import Dash, Input, Output, dcc_refresh as dcc, html
+
+
+def test_ddot001_dropdown_radioitems_checklist_option_title(dash_dcc):
+ app = Dash(__name__)
+
+ options = [
+ {"label": "New York City", "value": "NYC"},
+ {"label": "Montréal", "value": "MTL"},
+ {"label": "San Francisco", "value": "SF"},
+ ]
+
+ app.layout = html.Div(
+ [
+ dcc.Input(
+ id="title_input",
+ type="text",
+ placeholder="Enter a title for New York City",
+ ),
+ dcc.Dropdown(id="dropdown_1", options=options, multi=True, value="NYC"),
+ dcc.Dropdown(id="dropdown_2", options=options, multi=False, value="NYC"),
+ dcc.Checklist(
+ id="checklist_1",
+ options=options,
+ value=["NYC"],
+ labelClassName="Select-value-label",
+ ),
+ dcc.RadioItems(
+ id="radioitems_1",
+ options=options,
+ value="NYC",
+ labelClassName="Select-value-label",
+ ),
+ ]
+ )
+
+ ids = ["dropdown_1", "dropdown_2", "checklist_1", "radioitems_1"]
+
+ for id in ids:
+
+ @app.callback(Output(id, "options"), [Input("title_input", "value")])
+ def add_title_to_option(title):
+ return [
+ {"label": "New York City", "title": title, "value": "NYC"},
+ {"label": "Montréal", "value": "MTL"},
+ {"label": "San Francisco", "value": "SF"},
+ ]
+
+ dash_dcc.start_server(app)
+
+ elements = [
+ dash_dcc.wait_for_element("#dropdown_1 .Select-value"),
+ dash_dcc.wait_for_element("#dropdown_2 .Select-value"),
+ dash_dcc.wait_for_element("#checklist_1 .Select-value-label"),
+ dash_dcc.wait_for_element("#radioitems_1 .Select-value-label"),
+ ]
+
+ component_title_input = dash_dcc.wait_for_element("#title_input")
+
+ # Empty string title ('') (default for no title)
+
+ for element in elements:
+ wait.until(lambda: element.get_attribute("title") == "", 3)
+
+ component_title_input.send_keys("The Big Apple")
+
+ for element in elements:
+ wait.until(lambda: element.get_attribute("title") == "The Big Apple", 3)
+
+ dash_dcc.clear_input(component_title_input)
+
+ component_title_input.send_keys("Gotham City?")
+
+ for element in elements:
+ wait.until(lambda: element.get_attribute("title") == "Gotham City?", 3)
+
+ dash_dcc.clear_input(component_title_input)
+
+ for element in elements:
+ wait.until(lambda: element.get_attribute("title") == "", 3)
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/tooltip/test_tooltip.py b/components/dash-core-components-refresh/tests/integration/tooltip/test_tooltip.py
new file mode 100644
index 0000000000..9ab7155cd8
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/tooltip/test_tooltip.py
@@ -0,0 +1,89 @@
+from multiprocessing import Lock
+from selenium.webdriver.common.action_chains import ActionChains
+from dash.testing.wait import until
+
+from dash import Dash, Input, Output, dcc_refresh as dcc, html, no_update
+
+
+def test_ttbs001_canonical_behavior(dash_dcc):
+ lock = Lock()
+
+ loading_text = "Waiting for Godot"
+
+ fig = dict(
+ data=[
+ dict(
+ x=[11, 22, 33], y=[333, 222, 111], mode="markers", marker=dict(size=40)
+ )
+ ],
+ layout=dict(width=400, height=400, margin=dict(l=100, r=100, t=100, b=100)),
+ )
+ app = Dash(__name__)
+
+ app.layout = html.Div(
+ className="container",
+ children=[
+ dcc.Graph(id="graph", figure=fig, clear_on_unhover=True),
+ dcc.Tooltip(id="graph-tooltip", loading_text=loading_text),
+ ],
+ style=dict(position="relative"),
+ )
+
+ # This callback is executed very quickly
+ app.clientside_callback(
+ """
+ function show_tooltip(hoverData) {
+ if(!hoverData) {
+ return [false, dash_clientside.no_update];
+ }
+ var pt = hoverData.points[0];
+ return [true, pt.bbox];
+ }
+ """,
+ Output("graph-tooltip", "show"),
+ Output("graph-tooltip", "bbox"),
+ Input("graph", "hoverData"),
+ )
+
+ # This callback is executed after 1s to simulate a long-running process
+ @app.callback(
+ Output("graph-tooltip", "children"),
+ Input("graph", "hoverData"),
+ )
+ def update_tooltip_content(hoverData):
+ if hoverData is None:
+ return no_update
+
+ with lock:
+ # Display the x0 and y0 coordinate
+ bbox = hoverData["points"][0]["bbox"]
+ return [
+ html.P(f"x0={bbox['x0']}, y0={bbox['y0']}"),
+ ]
+
+ dash_dcc.start_server(app)
+
+ until(lambda: not dash_dcc.find_element("#graph-tooltip").is_displayed(), 3)
+
+ elem = dash_dcc.find_element("#graph .nsewdrag")
+
+ with lock:
+ # hover on the center of the graph
+ ActionChains(dash_dcc.driver).move_to_element_with_offset(
+ elem, elem.size["width"] / 2, elem.size["height"] / 2
+ ).click().perform()
+ dash_dcc.wait_for_text_to_equal("#graph-tooltip", loading_text)
+
+ dash_dcc.wait_for_contains_text("#graph-tooltip", "x0=")
+ tt_text = dash_dcc.find_element("#graph-tooltip").text
+ coords = [float(part.split("=")[1]) for part in tt_text.split(",")]
+ assert 175 < coords[0] < 185, "x0 is about 200 minus half a marker size"
+ assert 175 < coords[1] < 185, "y0 is about 200 minus half a marker size"
+
+ elem = dash_dcc.find_element("#graph .nsewdrag")
+
+ ActionChains(dash_dcc.driver).move_to_element_with_offset(
+ elem, 5, elem.size["height"] - 5
+ ).perform()
+
+ until(lambda: not dash_dcc.find_element("#graph-tooltip").is_displayed(), 3)
diff --git a/components/dash-core-components-refresh/tests/integration/upload/test_children_accept_any_component.py b/components/dash-core-components-refresh/tests/integration/upload/test_children_accept_any_component.py
new file mode 100644
index 0000000000..30d9ab4a36
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/upload/test_children_accept_any_component.py
@@ -0,0 +1,59 @@
+import time
+from dash import Dash, dcc_refresh as dcc, html
+
+
+def test_upca001_upload_children_gallery(dash_dcc):
+ app = Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Div(id="waitfor"),
+ html.Label("Empty"),
+ dcc.Upload(),
+ html.Label("Button"),
+ dcc.Upload(html.Button("Upload File")),
+ html.Label("Text"),
+ dcc.Upload("Upload File"),
+ html.Label("Link"),
+ dcc.Upload(html.A("Upload File")),
+ html.Label("Style"),
+ dcc.Upload(
+ ["Drag and Drop or ", html.A("Select a File")],
+ style={
+ "width": "100%",
+ "height": "60px",
+ "lineHeight": "60px",
+ "borderWidth": "1px",
+ "borderStyle": "dashed",
+ "borderRadius": "5px",
+ "textAlign": "center",
+ },
+ ),
+ dcc.Upload(
+ "upload",
+ disabled=True,
+ className_disabled="upload-disabled",
+ id="upload",
+ ),
+ dcc.Upload("upload", disabled=True, id="upload-no-className"),
+ ]
+ )
+ dash_dcc.start_server(app)
+ time.sleep(0.5)
+ dash_dcc.percy_snapshot("upca001 children gallery")
+
+ first_child = dash_dcc.find_element("#upload").find_element_by_css_selector(
+ ":first-child"
+ )
+ # Check that there is no default style since className is specified
+ style = first_child.get_attribute("style")
+ assert "opacity: 0.5" not in style
+
+ first_child = dash_dcc.find_element(
+ "#upload-no-className"
+ ).find_element_by_css_selector(":first-child")
+
+ # Check that there is default style since no className is specified
+ style = first_child.get_attribute("style")
+ assert "opacity: 0.5" in style
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/upload/test_upload_different_file_types.py b/components/dash-core-components-refresh/tests/integration/upload/test_upload_different_file_types.py
new file mode 100644
index 0000000000..7d6e28d915
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/upload/test_upload_different_file_types.py
@@ -0,0 +1,94 @@
+import io
+import base64
+import os
+import pytest
+import pandas as pd
+
+from dash import Dash, Input, Output, dcc_refresh as dcc, html
+
+from dash.dash_table import DataTable
+
+
+pre_style = {"whiteSpace": "pre-wrap", "wordBreak": "break-all"}
+
+
+def load_table(filetype, payload):
+ df = (
+ pd.read_csv(io.StringIO(base64.b64decode(payload).decode("utf-8")))
+ if filetype == "csv"
+ else pd.read_excel(io.BytesIO(base64.b64decode(payload)))
+ )
+
+ return html.Div(
+ DataTable(
+ data=df.to_dict("records"),
+ columns=[{"id": i} for i in ["city", "country"]],
+ )
+ )
+
+
+def load_data_by_type(filetype, contents):
+ children = []
+ _type, payload = contents.split(",")
+ if filetype in {"csv", "xlsx", "xls"}:
+ children = [load_table(filetype, payload)]
+ elif filetype in {"png", "svg"}:
+ children = [html.Img(src=contents)]
+
+ children += [
+ html.Hr(),
+ html.Div("Raw Content", id="raw-title"),
+ html.Pre(payload, style=pre_style),
+ ]
+ return html.Div(children)
+
+
+@pytest.mark.parametrize("filetype", ("csv", "xlsx", "xls", "png", "svg"))
+def test_upft001_test_upload_with_different_file_types(filetype, dash_dcc):
+
+ filepath = os.path.join(
+ os.path.dirname(__file__),
+ "upload-assets",
+ f"upft001.{filetype}",
+ )
+
+ app = Dash(__name__)
+
+ app.layout = html.Div(
+ [
+ html.Div(filepath, id="waitfor"),
+ html.Div(
+ id="upload-div",
+ children=dcc.Upload(
+ id="upload",
+ children=html.Div(["Drag and Drop or ", html.A("Select a File")]),
+ style={
+ "width": "100%",
+ "height": "60px",
+ "lineHeight": "60px",
+ "borderWidth": "1px",
+ "borderStyle": "dashed",
+ "borderRadius": "5px",
+ "textAlign": "center",
+ },
+ ),
+ ),
+ html.Div(id="output"),
+ html.Div(DataTable(data=[{}]), style={"display": "none"}),
+ ]
+ )
+
+ @app.callback(Output("output", "children"), [Input("upload", "contents")])
+ def update_output(contents):
+ if contents is not None:
+ return load_data_by_type(filetype, contents)
+
+ dash_dcc.start_server(app)
+
+ upload_div = dash_dcc.wait_for_element("#upload-div input[type=file]")
+ upload_div.send_keys(filepath)
+
+ dash_dcc.wait_for_text_to_equal("#raw-title", "Raw Content")
+ dash_dcc.percy_snapshot("Core" + filepath)
+
+ assert dash_dcc.get_logs() == []
diff --git a/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.png b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.png
new file mode 100644
index 0000000000..05e65d590a
Binary files /dev/null and b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.png differ
diff --git a/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.svg b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.svg
new file mode 100644
index 0000000000..490f576fde
--- /dev/null
+++ b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.xls b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.xls
new file mode 100644
index 0000000000..cb431169e2
Binary files /dev/null and b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.xls differ
diff --git a/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.xlsx b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.xlsx
new file mode 100644
index 0000000000..7c6fa8c9a7
Binary files /dev/null and b/components/dash-core-components-refresh/tests/integration/upload/upload-assets/upft001.xlsx differ
diff --git a/components/dash-core-components-refresh/vignettes/dash-core-components.Rmd b/components/dash-core-components-refresh/vignettes/dash-core-components.Rmd
new file mode 100644
index 0000000000..b336551f16
--- /dev/null
+++ b/components/dash-core-components-refresh/vignettes/dash-core-components.Rmd
@@ -0,0 +1,1753 @@
+---
+title: "Dash Core Components"
+output: rmarkdown::html_vignette
+vignette: >
+ %\VignetteIndexEntry{Dash Core Components}
+ %\VignetteEngine{knitr::rmarkdown}
+ %\VignetteEncoding{UTF-8}
+---
+
+```{r, include = FALSE}
+knitr::opts_chunk$set(
+ collapse = TRUE,
+ comment = "#>"
+)
+```
+
+Dash ships with supercharged components for interactive user interfaces. A core set of components, written and maintained by the Dash team, is available in the `dashCoreComponents` package. The source is on GitHub at [plotly/dash-core-components](https://github.com/plotly/dash-core-components).
+
+Please visit our online documentation, which is interactive and frequently updated: https://dashr.plotly.com.
+
+### Dropdown Component
+
+__*Default Dropdown*__
+
+An example of a default dropdown without any extra properties.
+
+```r
+dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ value=list("MTL", "NYC"),
+ multi=TRUE
+)
+```
+
+__*Multi-Value Dropdown*__
+
+A dropdown component with the `multi` property set to `TRUE` will allow the user to select more than one value at a time.
+
+```r
+dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ value="MTL",
+ id="my-dropdown"
+)
+```
+
+__*Disable Search*__
+
+The `searchable` property is set to `TRUE` by default on all Dropdown components. To prevent searching the dropdown value, just set the `searchable` property to `FALSE`. Try searching for 'New York' on this dropdown below and compare it to the other dropdowns on the page to see the difference.
+
+```r
+dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ searchable = FALSE
+)
+```
+
+__*Dropdown Clear*__
+
+The `clearable` property is set to `TRUE` by default on all Dropdown components. To prevent the clearing of the selected dropdown value, just set the clearable property to `FALSE`:
+
+```r
+dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ value = "MTL",
+ clearable = FALSE
+)
+```
+
+__*Placeholder Text*__
+
+The placeholder property allows you to define default text shown when no value is selected.
+
+```r
+dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ placeholder="Select a city"
+)
+```
+
+__*Disable Dropdown*__
+
+To disable the dropdown just set `disabled=TRUE`.
+
+```r
+dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ disabled=TRUE
+)
+```
+
+__*Disable Options*__
+
+To disable a particular option inside the dropdown menu, set the disabled property in the options.
+
+```r
+dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC", "disabled" = TRUE),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF", "disabled" = TRUE)
+ )
+)
+```
+
+### Slider Component
+
+__*Simple Slider Example*__
+
+An example of a basic slider tied to a callback.
+
+```r
+library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccSlider(
+ id='my-slider',
+ min=0,
+ max=20,
+ step=0.5,
+ value=10
+ ),
+ htmlDiv(id='slider-output-container')
+ )
+ )
+)
+
+app$callback(
+ output(id = 'slider-output-container', property = 'children'),
+ params=list(input(id = 'my-slider', property = 'value')),
+ function(value) {
+ sprintf("you have selected %0.1f", value)
+ })
+
+app$run_server()
+```
+
+__*Marks and Steps*__
+
+If slider marks are defined and step is set to `NULL` then the slider will only be able to select values that have been predefined by the marks. marks is a list where the keys represent the numerical values and the values represent their labels.
+
+```r
+dccSlider(
+ min=0,
+ max=10,
+ marks = list(
+ "0" = "0 °F",
+ "3" = "3 °F",
+ "5" = "5 °F",
+ "7.65" = "7.65 °F",
+ "10" = "10 °F"
+ ),
+ value=5
+)
+```
+
+By default, `included=TRUE`, meaning the rail trailing the handle will be highlighted. To have the handle act as a discrete value set `included=FALSE`. To style `marks`, include a style css attribute alongside the list value.
+
+```r
+dccSlider(
+ min=0,
+ max=100,
+ value = 65,
+ marks = list(
+ "0" = list("label" = "0 °C", "style" = list("color" = "#77b0b1")),
+ "26" = list("label" = "26 °C"),
+ "37" = list("label" = "37 °C"),
+ "100" = list("label" = "100 °C", "style" = list("color" = "#FF4500"))
+ )
+)
+```
+
+__*Non-Linear Slider and Updatemode*__
+
+```r
+library(dash)
+
+transform_value = function(value){
+ return(10**value)
+}
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccSlider(
+ id='slider-updatemode',
+ marks=unlist(lapply(list(1:4), function(x){10**x})),
+ max=3,
+ value=2,
+ step=0.01,
+ updatemode='drag'
+ ),
+ htmlDiv(id='updatemode-output-container', style=list('margin-top' = 20))
+ )
+ )
+)
+
+app$callback(
+ output(id = 'updatemode-output-container', property='children'),
+ params=list(input(id='slider-updatemode', property='value')),
+ function(value) {
+ sprintf('Linear Value: %g | Log Value: %0.2f', value, transform_value(value))
+ })
+
+app$run_server()
+```
+
+### RangeSlider Component
+
+__*Simple RangeSlider Example*__
+
+An example of a basic RangeSlider tied to a callback.
+
+```r
+library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccRangeSlider(
+ id='my-range-slider',
+ min=0,
+ max=20,
+ step=0.5,
+ value=list(5, 15)
+ ),
+ htmlDiv(id='output-container-range-slider')
+ )
+ )
+)
+
+app$callback(
+ output(id = 'output-container-range-slider', property='children'),
+ params=list(input(id='my-range-slider', property='value')),
+ function(value) {
+ sprintf('You have selected [%0.1f, %0.1f]', value[1], value[2])
+ })
+
+app$run_server()
+```
+
+__*Marks and Steps*__
+
+If slider marks are defined and step is set to `NULL` then the slider will only be able to select values that have been predefined by the marks.
+
+```r
+dccRangeSlider(
+ min=0,
+ max=10,
+ marks=list(
+ "0" = "0 °F",
+ "3" = "3 °F",
+ "5" = "5 °F",
+ "7.65" = "7.65 °F",
+ "10" = "10 °F"
+ ),
+ value=list(3, 7.65)
+)
+```
+
+__*Included and Styling Marks*__
+
+By default, `included=TRUE`, meaning the rail trailing the handle will be highlighted. To have the handle act as a discrete value set `included=FALSE`. To style marks, include a style css attribute alongside the key value.
+
+```r
+library(dash)
+
+dccRangeSlider(
+ min=0,
+ max=100,
+ marks = list(
+ "0" = list("label" = "0 °C", "style" = list("color" = "#77b0b1")),
+ "26" = list("label" = "26 °C"),
+ "37" = list("label" = "37 °C"),
+ "100" = list("label" = "100°C" , "style" = list("color" = "#FF4500"))
+ )
+)
+```
+
+__*Multiple Handles*__
+
+To create multiple handles just define more values for `value`!
+
+```r
+dccRangeSlider(
+ min=0,
+ max=30,
+ value=list(1, 3, 4, 5, 12, 17)
+)
+```
+
+__*Crossing Handles*__
+
+If `allowCross=FALSE`, the handles will not be allowed to cross over each other.
+
+```r
+dccRangeSlider(
+ min=0,
+ max=30,
+ value=list(10,15),
+ allowCross = FALSE
+)
+```
+
+__*Non-Linear Slider and Updatemode*__
+
+Create a logarithmic slider by setting `marks` to be logarithmic and adjusting the slider's output value in the callbacks. The `updatemode` property allows us to determine when we want a callback to be triggered. The following example has `updatemode='drag'` which means a callback is triggered everytime the handle is moved. Contrast the callback output with the first example on this page to see the difference.
+
+```r
+library(dash)
+
+transform_value = function(value){
+ return(10 ** value)
+}
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccRangeSlider(
+ id='non-linear-range-slider',
+ marks=unlist(lapply(list(1:4), function(x){10**x})),
+ max=3,
+ value=list(0.1, 2),
+ dots=FALSE,
+ step=0.01,
+ updatemode='drag'
+ ),
+ htmlDiv(id='output-container-range-slider-non-linear', style=list('margin-top' = 20))
+ )
+))
+
+app$callback(
+ output(id = 'output-container-range-slider-non-linear', property='children'),
+ params=list(input(id='non-linear-range-slider', property='value')),
+ function(value) {
+ transformed_value = lapply(value, transform_value)
+ sprintf('Linear Value: %g, Log Value: [%0.2f, %0.2f]', value[2],transformed_value[1], transformed_value[2])
+ })
+
+app$run_server()
+```
+
+### Input Component
+
+__*Update Value on Keypress*__
+
+```
+library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccInput(id='input-1-keypress', type='text', value='Montreal'),
+ dccInput(id='input-2-keypress', type='text', value='Canada'),
+ htmlDiv(id='output-keypress1')
+ )
+ )
+)
+
+app$callback(
+ output(id = 'output-keypress1', property='children'),
+ params=list(input(id='input-1-keypress', property='value'),
+ input(id='input-2-keypress', property='value')),
+ function(input1, input2) {
+ sprintf('Input 1 is %s and input 2 is %s', input1,input2)
+ })
+
+app$run_server()
+```
+
+__*Update Value on Enter/Blur*__
+
+`dccInput` has properties `n_submit`, which updates when the enter button is pressed, and `n_blur`, which updates when the component loses focus (e.g. tab is pressed or the user clicks outside of the input field).
+
+```r
+library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccInput(id='input-1-submit', type='text', value='Montreal'),
+ dccInput(id='input-2-submit', type='text', value='Canada'),
+ htmlDiv(id='output-keypress')
+ )
+ ))
+
+app$callback(
+ output(id = 'output-keypress', property='children'),
+ params=list(input(id='input-1-submit', property='n_submit'),
+ input(id = 'input-1-submit', property = 'n_blur'),
+ input(id='input-2-submit', property='n_submit'),
+ input(id = 'input-2-submit', property = 'n_blur'),
+ state('input-1-submit', 'value'),
+ state('input-2-submit', 'value')),
+ function(ns1, nb1, ns2, nb2, input1, input2) {
+ sprintf('Input 1 is %s and input 2 is %s', input1, input2)
+ })
+
+app$run_server()
+```
+
+### Textarea Component
+
+```r
+library(dash)
+
+dccTextarea(
+ placeholder = "Enter a value...",
+ value = "This is a TextArea component",
+ style = list("width" = "100%")
+)
+```
+
+### Checklist Component
+
+```r
+library(dash)
+
+dccChecklist(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value=list("MTL", "SF")
+)
+```
+
+```r
+library(dash)
+
+dccChecklist(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value=list("MTL", "SF"),
+ labelStyle = list("display" = "inline-block")
+)
+```
+
+### RadioItems Component
+
+```r
+library(dash)
+
+dccRadioItems(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value = "MTL"
+)
+```
+
+```r
+library(dash)
+
+dccRadioItems(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value = "MTL",
+ labelStyle = list("display" = "inline-block")
+)
+```
+
+### Button Component
+
+__*Button Basic Example*__
+
+An example of a default button without any extra properties and `n_clicks` in the callback. `n_clicks` is an integer that represents that number of times the button has been clicked. Note that the original value is `NULL`.
+
+```r
+library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ htmlDiv(dccInput(id="input-box", type="text")),
+ htmlButton("Submit", id="button"),
+ htmlDiv(id="output-container-button",
+ children="Enter a value and press submit")
+ )
+ )
+)
+
+app$callback(
+ output = list(id = "output-container-button", property = 'children'),
+ params=list(input(id = "button", property = "n_clicks"),
+ input(id = "input-box", property = "value")),
+ function(n_clicks, value) {
+ sprintf("The input value was \'%s\' and the button has been clicked \'%s\' times", value, n_clicks)
+ })
+
+app$run_server()
+```
+
+__*Button with n_clicks_timestamp*__
+
+This example utilizes the `n_clicks_timestamp` property, which returns an integer representation of time. This is useful for determining when the button was last clicked.
+
+```r
+library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ htmlButton('Button 1', id='btn-1', n_clicks_timestamp=0),
+ htmlButton('Button 2', id='btn-2', n_clicks_timestamp=0),
+ htmlButton('Button 3', id='btn-3', n_clicks_timestamp=0),
+ htmlDiv(id='container-button-timestamp')
+ )
+ )
+)
+
+app$callback(
+ output(id = 'container-button-timestamp', property = 'children'),
+ params = list(input(id = 'btn-1', property = 'n_clicks_timestamp'),
+ input(id = 'btn-2', property = 'n_clicks_timestamp'),
+ input(id = 'btn-3', property ='n_clicks_timestamp')),
+ function(btn1, btn2, btn3){
+ if((btn1) > (btn2) & (btn1) > (btn3)){
+ msg = 'Button 1 was most recently clicked'
+ } else if((btn2) > (btn1) & (btn2) > (btn3)){
+ msg = 'Button 2 was most recently clicked'
+ } else if((btn3) > (btn1) & (btn3) > (btn2)){
+ msg = 'Button 3 was most recently clicked'
+ }
+ else{
+ msg = 'None of the buttons have been clicked yet'
+ }
+ return(htmlDiv(list(
+ htmlDiv(sprintf('btn1: %s', format(btn1, scientific = FALSE))),
+ htmlDiv(sprintf('btn2: %s', format(btn2, scientific = FALSE))),
+ htmlDiv(sprintf('btn3: %s', format(btn3, scientific = FALSE))),
+ htmlDiv(msg))
+ ))
+ }
+)
+
+app$run_server()
+```
+
+### DatePickerSingle Component
+
+__*Calendar Clear and Portals*__
+
+When the `clearable` property is set to `TRUE` the component will be rendered with a small 'x' that will remove all selected dates when selected.
+
+The `DatePickerSingle` component supports two different portal types, one being a full screen portal (`with_full_screen_portal`) and another being a simple screen overlay, like the one shown below (`with_portal`).
+
+```r
+library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccDatePickerSingle(
+ id='my-date-picker-single',
+ min_date_allowed=as.Date('1995-8-5'),
+ max_date_allowed=as.Date('2017-9-19'),
+ initial_visible_month=as.Date('2017-8-5'),
+ date = as.POSIXct("2017-08-25 23:59:59", tz = "GMT")
+ ),
+ htmlDiv(id='output-container-date-picker-single')
+ )
+ )
+)
+
+app$callback(
+ output = list(id='output-container-date-picker-single', property = 'children'),
+ params = list(input(id = 'my-date-picker-single', property = 'date')),
+ function(date){
+ if(is.null(date) == FALSE){
+ date = format(as.Date(date), format = '%B %d,%Y')
+ sprintf('You have selected: %s', date)
+ }
+ }
+)
+```
+
+__*Month and Display Format*__
+
+The `display_format` property determines how selected dates are displayed in the `DatePickerRange` component. The `month_format` property determines how calendar headers are displayed when the calendar is opened.
+
+Both of these properties are configured through strings that utilize a combination of any of the following tokens.
+
+| String Token | Example | Description |
+|:-------------|:-----------------|:-------------------------------------------------------|
+| `YYYY` | `2014` | 4 or 2 digit year |
+| `YY` | `14` | 2 digit year |
+| `Y` | `-25` | Year with any number of digits and sign |
+| `Q` | `1..4` | Quarter of year. Sets month to first month in quarter. |
+| `M MM` | `1..12` | Month number |
+| `MMM MMMM` | `Jan..December` | Month name |
+| `D DD` | `1..31` | Day of month |
+| `Do` | `1st..31st` | Day of month with ordinal |
+| `DDD DDDD` | `1..365` | Day of year |
+| `X` | `1410715640.579` | Unix timestamp |
+| `X` | `1410715640579` | Unix ms timestamp |
+
+__*Display Format Examples*__
+
+You can utilize any permutation of the string tokens shown in the table above to change how selected dates are displayed in the `DatePickerRange` component.
+
+```r
+dccDatePickerSingle(
+ date="2017-06-21",
+ display_format="MMM Do, YY"
+)
+
+# Displays "Jun 21st, 17""
+```
+
+```r
+dccDatePickerSingle(
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format="M-D-Y-Q"
+)
+# Displays "6-21-2017-2"
+```
+
+```r
+dccDatePickerSingle(
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format="MMMM Y, DD"
+)
+# Displays "June 2017, 21"
+```
+
+```r
+dccDatePickerSingle(
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format="X"
+)
+# Displays "1498017600"
+```
+
+__*Month Format Examples*__
+
+Similar to the `display_format`, you can set `month_format` to any permutation of the string tokens shown in the table above to change how calendar titles are displayed in the `DatePickerRange` component.
+
+```r
+dccDatePickerSingle(
+ month_format="MMM Do, YY",
+ placeholder="MMM Do, YY",
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+```
+
+```r
+dccDatePickerSingle(
+ month_format="M-D-Y-Q",
+ placeholder="M-D-Y-Q",
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+```
+
+```r
+dccDatePickerSingle(
+ month_format="MMMM Y",
+ placeholder="MMMM Y",
+ date=format(as.Date("2020-2-29"), format = "%Y,%m,%d")
+)
+# Displays "02/29/2020"
+```
+
+__*Vertical Calendar and Placeholder Text*__
+
+The `DatePickerRange` component can be rendered in two orientations, either horizontally or vertically. If `calendar_orientation` is set to 'vertical', it will be rendered vertically and will default to 'horizontal' if not defined.
+
+The `start_date_placeholder_text` and `end_date_placeholder_text` define the grey default text defined in the calendar input boxes when no date is selected.
+
+```r
+dccDatePickerSingle(
+ calendar_orientation="vertical",
+ placeholder="Select a date",
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+```
+
+__*Calendar Clear and Portals*__
+
+When the `clearable` property is set to `TRUE` the component will be rendered with a small 'x' that will remove all selected dates when selected.
+
+The `DatePickerSingle` component supports two different portal types, one being a full screen portal (`with_full_screen_portal`) and another being a simple screen overlay, like the one shown below (`with_portal`).
+
+```r
+
+dccDatePickerSingle(
+ clearable=TRUE,
+ with_portal=TRUE,
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+```
+
+__*Right to Left Calendars and First Day of Week*__
+
+When the `is_RTL` property is set to `TRUE` the calendar will be rendered from right to left.
+
+The `first_day_of_week` property allows you to define which day of the week will be set as the first day of the week. In the example below, Tuesday is the first day of the week.
+
+```r
+dccDatePickerSingle(
+ is_RTL=TRUE,
+ first_day_of_week=3,
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+ )
+# Displays "06/21/2017"
+```
+
+### DatePickerRange Component
+
+__*Simple DatePickerRange Example*__
+
+This is a simple example of a `DatePickerRange` component tied to a callback. The `min_date_allowed` and `max_date_allowed` properties define the minimum and maximum selectable dates on the calendar while `initial_visible_month` defines the calendar month that is first displayed when the `DatePickerRange` component is opened.
+
+```r
+library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccDatePickerRange(
+ id='my-date-picker-single',
+ min_date_allowed=as.Date('1995-8-5'),
+ max_date_allowed=as.Date('2017-9-19'),
+ initial_visible_month=as.Date('2017-8-5'),
+ end_date = as.Date('2017-8-5')
+ ),
+ htmlDiv(id='output-container-date-picker-range')
+ )
+ )
+)
+
+app$callback(
+ output = list(id='output-container-date-picker-range', property = 'children'),
+ params = list(input(id = 'my-date-picker-range', property = 'start_date'),
+ input(id = 'my-date-picker-range', property = 'end_date')),
+ function(start_date, end_date){
+ string_prefix = 'You have selected: '
+
+ if(is.null(start_date) == FALSE){
+ start_date = format(as.Date(start_date), format = '%B %d,%Y')
+ string_prefix = paste(string_prefix, 'Start Date ', start_date, sep = "")
+ }
+ if(is.null(end_date) == FALSE){
+ end_date = format(as.Date(end_date), format = '%B %d,%Y')
+ string_prefix = paste(string_prefix, 'End Date: ', end_date, sep = "")
+
+ }
+ if(nchar(string_prefix) == nchar('You have selected: ')){
+ return('Select a date to see it displayed here')
+ }
+ else{
+ return(string_prefix)
+ }
+ }
+
+)
+
+app$run_server()
+```
+
+__*Month and Display Format*__
+
+The `display_format` property determines how selected dates are displayed in the `DatePickerRange` component. The `month_format` property determines how calendar headers are displayed when the calendar is opened.
+
+Both of these properties are configured through strings that utilize a combination of any of the following tokens.
+
+| String Token | Example | Description |
+|:-------------|:-----------------|:-------------------------------------------------------|
+| `YYYY` | `2014` | 4 or 2 digit year |
+| `YY` | `14` | 2 digit year |
+| `Y` | `-25` | Year with any number of digits and sign |
+| `Q` | `1..4` | Quarter of year. Sets month to first month in quarter. |
+| `M MM` | `1..12` | Month number |
+| `MMM MMMM` | `Jan..December` | Month name |
+| `D DD` | `1..31` | Day of month |
+| `Do` | `1st..31st` | Day of month with ordinal |
+| `DDD DDDD` | `1..365` | Day of year |
+| `X` | `1410715640.579` | Unix timestamp |
+| `X` | `1410715640579` | Unix ms timestamp |
+
+__*Display Format Examples*__
+
+You can utilize any permutation of the string tokens shown in the table above to change how selected dates are displayed in the `DatePickerRange` component.
+
+```r
+dccDatePickerRange(
+ end_date = as.POSIXct("2017-6-21 23:59:59", tz = "GMT"),
+ display_format = "MMM Do, YY",
+ start_date_placeholder_text = "MMM Do, YY"
+)
+# Displays "Jun 21st, 17"
+```
+
+```r
+dccDatePickerRange(
+ end_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format = "M-D-Y-Q",
+ start_date_placeholder_text = "M-D-Y-Q"
+)
+# Displays "6-21-2017-2"
+```
+
+```r
+dccDatePickerRange(
+ end_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format = "MMMM Y, DD",
+ start_date_placeholder_text = "MMMM Y, DD"
+)
+# Displays "June 2017, 21"
+```
+
+```r
+dccDatePickerRange(
+ end_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format = "X",
+ start_date_placeholder_text = "X"
+)
+# Displays "1498017600"
+```
+
+__*Month Format Examples*__
+
+Similar to the `display_format`, you can set `month_format` to any permutation of the string tokens shown in the table above to change how calendar titles are displayed in the `DatePickerRange` component.
+
+```r
+dccDatePickerRange(
+ display_format = "MMM Do, YY",
+ start_date_placeholder_text = "MMM Do, YY",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "Jun 21st, 17"
+```
+
+```r
+dccDatePickerRange(
+ display_format = "M-D-Y-Q",
+ start_date_placeholder_text = "M-D-Y-Q",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "6-21-2017-2"
+```
+
+```
+dccDatePickerRange(
+ display_format = "MMMM Y",
+ start_date_placeholder_text = "MMMM Y",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "June 2017"
+```
+
+```
+dccDatePickerRange(
+ display_format = "X",
+ start_date_placeholder_text = "X",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "1498017600"
+```
+
+__*Vertical Calendar and Placeholder Text*__
+
+The `DatePickerRange` component can be rendered in two orientations, either horizontally or vertically. If `calendar_orientation` is set to 'vertical', it will be rendered vertically and will default to 'horizontal' if not defined.
+
+The `start_date_placeholder_text` and `end_date_placeholder_text` define the grey default text defined in the calendar input boxes when no date is selected.
+
+```r
+dccDatePickerRange(
+ start_date_placeholder_text = "Start Period",
+ end_date_placeholder_text = "End Period",
+ calendar_orientation = "vertical",
+)
+```
+
+__*Minimum Nights, Calendar Clear, and Portals*__
+
+The `minimum_nights property` defines the number of nights that must be in between the range of two selected dates.
+
+When the `clearable` property is set to `TRUE` the component will be rendered with a small 'x' that will remove all selected dates when selected.
+
+The `DatePickerRange` component supports two different portal types, one being a full screen portal (`with_full_screen_portal`) and another being a simple screen overlay, like the one shown below (`with_portal`).
+
+```r
+dccDatePickerRange(
+ minimum_nights = 5,
+ clearable = TRUE,
+ with_portal = TRUE,
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+```
+
+__*Right to Left Calendars and First Day of Week*__
+
+When the `is_RTL` property is set to `TRUE` the calendar will be rendered from right to left.
+
+The `first_day_of_week` property allows you to define which day of the week will be set as the first day of the week. In the example below, Tuesday is the first day of the week.
+
+```r
+dccDatePickerRange(
+ is_RTL = TRUE,
+ first_day_of_week = 3,
+ start_date= format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+```
+
+### Markdown Component
+
+__*Syntax Guide*__
+
+These examples are based on the [GitHub Markdown Guide](https://guides.github.com/features/mastering-markdown/). The Dash Markdown component uses the [CommonMark](http://commonmark.org/) specification of Markdown.
+
+
+__*Headers*__
+
+```r
+library(dash)
+
+dccMarkdown('
+
+# This is an tag
+
+## This is an tag
+
+###### This is an tag)
+```
+
+```r
+library(dash)
+
+dccMarkdown('
+
+ *This text will be italic*
+
+ _This will also be italic_
+
+
+ **This text will be bold**
+
+ __This will also be bold__
+
+ _You **can** combine them_
+'
+)
+```
+
+__*Lists*__
+
+*Unordered*
+
+```r
+library(dash)
+
+dccMarkdown('
+* Item 1
+* Item 2
+ * Item 2a
+ * Item 2b
+')
+```
+
+__*Block Quotes*__
+
+```r
+library(dash)
+
+dccMarkdown('
+>
+> Block quotes are used to highlight text.
+>
+')
+```
+
+__*Links*__
+
+```r
+library(dash)
+
+dccMarkdown('
+[Dash User Guide](https://dash.plotly.com/)')
+```
+
+__*Inline Code*__
+
+Any block of text surrounded by ` ` will rendered as inline-code.
+
+```r
+library(dash)
+
+dccMarkdown('
+Inline code snippet = `TRUE` ')
+```
+
+### Interactive Tables
+
+The `dashHtmlComponents` package exposes all of the HTML tags. This includes the `Table`, `Tr`, and `Tbody` tags that can be used to create an HTML table. See Create Your First Dash App, Part 1 for an example. Dash provides an interactive DataTable as part of the `dash-table` project. This table includes built-in filtering, row-selection, editing, and sorting.
+
+### Upload Component
+
+The Dash upload component allows your app's viewers to upload files, like excel spreadsheets or images, into your application. Your Dash app can access the contents of an upload by listening to the `contents` property of the dccUpload component. `contents` is a base64 encoded string that contains the files contents, no matter what type of file: text files, images, zip files, excel spreadsheets, etc.
+
+Here's an example that parses CSV or Excel files and displays the results in a table. Note that this example uses the `DataTable` from the `dash-table` project.
+
+```r
+library(dashHtmlComponents)
+library(dash)
+library(anytime)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccUpload(
+ id='upload-data',
+ children=htmlDiv(list(
+ 'Drag and Drop or ',
+ htmlA('Select Files')
+ )),
+ style=list(
+ 'width'= '100%',
+ 'height'= '60px',
+ 'lineHeight'= '60px',
+ 'borderWidth'= '1px',
+ 'borderStyle'= 'dashed',
+ 'borderRadius'= '5px',
+ 'textAlign'= 'center',
+ 'margin'= '10px'
+ ),
+ # Allow multiple files to be uploaded
+ multiple=TRUE
+ ),
+ htmlDiv(id='output-data-upload')
+)))
+
+parse_contents = function(contents, filename, date){
+ content_type = strsplit(contents, ",")
+ content_string = strsplit(contents, ",")
+ decoded = base64_dec(content_string)
+
+ if('csv' %in% filename){
+ df = read.csv(utf8::as_utf8(decoded))
+ } else if('xls' %in% filename){
+ df = read.table(decoded, encoding = 'bytes')
+ } else{
+ return(htmlDiv(list(
+ 'There was an error processing this file.'
+ )))
+ }
+
+ return(htmlDiv(list(
+ htmlH5(filename),
+ htmlH6(anytime(date)),
+ dashDataTable(df_to_list('records'),columns = lapply(colnames(df), function(x){list('name' = x, 'id' = x)})),
+ htmlHr(),
+ htmlDiv('Raw Content'),
+ htmlPre(paste(substr(toJSON(contents), 1, 100), "..."), style=list(
+ 'whiteSpace'= 'pre-wrap',
+ 'wordBreak'= 'break-all'
+ ))
+ )))
+}
+
+app$callback(
+ output = list(id='output-data-upload', property = 'children'),
+ params = list(input(id = 'upload-data', property = 'contents'),
+ state(id = 'upload-data', property = 'filename'),
+ state(id = 'upload-data', property = 'last_modified')),
+ function(list_of_contents, list_of_names, list_of_dates){
+ if(is.null(list_of_contents) == FALSE){
+ children = lapply(1:length(list_of_contents), function(x){
+ parse_content(list_of_contents[[x]], list_of_names[[x]], list_of_dates[[x]])
+ })
+
+ }
+ return(children)
+ })
+
+app$run_server()
+```
+
+This next example responds to image uploads by displaying them in the app with the `htmlImg` component.
+
+```r
+library(dashHtmlComponents)
+library(dash)
+library(anytime)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccUpload(
+ id='upload-image',
+ children=htmlDiv(list(
+ 'Drag and Drop or ',
+ htmlA('Select Files')
+ )),
+ style=list(
+ 'width'= '100%',
+ 'height'= '60px',
+ 'lineHeight'= '60px',
+ 'borderWidth'= '1px',
+ 'borderStyle'= 'dashed',
+ 'borderRadius'= '5px',
+ 'textAlign'= 'center',
+ 'margin'= '10px'
+ ),
+ # Allow multiple files to be uploaded
+ multiple=TRUE
+ ),
+ htmlDiv(id='output-image-upload')
+)))
+
+parse_content = function(contents, filename, date){
+ return(htmlDiv(list(
+ htmlH5(filename),
+ htmlH6(anytime(date)),
+ htmlImg(src=contents),
+ htmlHr(),
+ htmlDiv('Raw Content'),
+ htmlPre(paste(substr(toJSON(contents), 1, 100), "..."), style=list(
+ 'whiteSpace'= 'pre-wrap',
+ 'wordBreak'= 'break-all'
+ ))
+ )))
+}
+
+app$callback(
+ output = list(id='output-image-upload', property = 'children'),
+ params = list(input(id = 'upload-image', property = 'contents'),
+ state(id = 'upload-image', property = 'filename'),
+ state(id = 'upload-image', property = 'last_modified')),
+ function(list_of_contents, list_of_names, list_of_dates){
+ if(is.null(list_of_contents) == FALSE){
+ children = lapply(1:length(list_of_contents), function(x){
+ parse_content(list_of_contents[[x]], list_of_names[[x]], list_of_dates[[x]])
+ })
+ } else{
+
+ }
+ return(children)
+ }
+)
+
+app$run_server()
+```
+
+The `children` attribute of the `Upload` component accepts any Dash component. Clicking on the children element will trigger the upload action, as will dragging and dropping files. Here are a few different ways that you could style the upload component using standard dash components.
+
+```
+library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccUpload(htmlButton('Upload File')),
+
+ htmlHr(),
+
+ dccUpload(htmlA('Upload File')),
+
+ htmlHr(),
+
+ dccUpload(list(
+ 'Drag and Drop or ',
+ htmlA('Select a File')
+ ), style=list(
+ 'width'= '100%',
+ 'height'= '60px',
+ 'lineHeight'= '60px',
+ 'borderWidth'= '1px',
+ 'borderStyle'= 'dashed',
+ 'borderRadius'= '5px',
+ 'textAlign'= 'center'
+ ))
+ )))
+
+app$run_server()
+```
+
+### Tabs Component
+
+The Tabs and Tab components can be used to create tabbed sections in your app. The `Tab` component controls the style and value of the individual tab and the `Tabs` component hold a collection of Tab components.
+
+* Method 1. Content as Callback
+* Method 2. Content as Tab children
+* Styling the Tabs component
+ * with CSS classes
+ * with inline styles
+ * with props
+
+__*Method 1. Content as Callback*__
+
+Attach a callback to the Tabs `value` prop and update a container's `children` property in your callback.
+
+```r
+library(dashHtmlComponents)
+library(dash)
+
+utils <- new.env()
+source('dashr/utils.R', local=utils)
+
+examples <- list(
+ button = utils$LoadExampleCode('dashr/chapters/dash-core-components/Button/examples/button.R'),
+ tabs = utils$LoadExampleCode('dashr/chapters/dash-core-components/Tabs/examples/tabs.R')
+)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ htmlH1('Dash Tabs component demo'),
+ dccTabs(id="tabs-example", value='tab-1-example', children=list(
+ dccTab(label='Tab One', value='tab-1-example'),
+ dccTab(label='Tab Two', value='tab-2-example')
+ )),
+ htmlDiv(id='tabs-content-example')
+)))
+
+app$callback(
+ output = list(id='tabs-content-example', property = 'children'),
+ params = list(input(id = 'tabs-example', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1-example'){
+ return(htmlDiv(list(
+ htmlH3('Tab content 1'),
+ dccGraph(
+ id='graph-1-tabs',
+ figure=list(
+ 'data' = list(list(
+ 'x' = c(1, 2, 3),
+ 'y' = c(3, 1, 2),
+ 'type' = 'bar'
+ ))
+ )
+ )
+ )))
+ }
+
+ else if(tab == 'tab-2-example'){
+ return(htmlDiv(list(
+ htmlH3('Tab content 2'),
+ dccGraph(
+ id='graph-2-tabs',
+ figure=list(
+ 'data' = list(list(
+ 'x' = c(1, 2, 3),
+ 'y' = c(5, 10, 6),
+ 'type' = 'bar'
+ ))
+ )
+ )
+ )))
+ }
+ }
+
+
+
+)
+
+app$run_server()
+```
+
+__*Method 2. Content as Tab Children*__
+
+Instead of displaying the content through a callback, you can embed the content directly as the `children` property in the `Tab` component:
+
+```r
+app$layout(htmlDiv(list(
+ dccTabs(id="tabs", children=list(
+ dccTab(label='Tab one', children=list(
+ htmlDiv(list(
+ dccGraph(
+ id='example-graph',
+ figure=list(
+ 'data'= list(
+ list('x'= c(1, 2, 3), 'y'= c(4, 1, 2),
+ 'type'= 'bar', 'name'= 'SF'),
+ list('x'= c(1, 2, 3), 'y'= c(2, 4, 5),
+ 'type'= 'bar', 'name'= 'Montréal')
+ )
+ )
+ )
+ ))
+ )),
+ dccTab(label='Tab two', children=list(
+ dccGraph(
+ id='example-graph-1',
+ figure=list(
+ 'data'= list(
+ list('x'= c(1, 2, 3), 'y'= c(1, 4, 1),
+ 'type'= 'bar', 'name'= 'SF'),
+ list('x'= c(1, 2, 3), 'y'= c(1, 2, 3),
+ 'type'= 'bar', 'name'= 'Montréal')
+ )
+ )
+ )
+ )),
+ dccTab(label='Tab three', children=list(
+ dccGraph(
+ id='example-graph-2',
+ figure=list(
+ 'data'= list(
+ list('x'= list(1, 2, 3), 'y'= list(2, 4, 3),
+ 'type'= 'bar', 'name'= 'SF'),
+ list('x'= list(1, 2, 3), 'y'= list(5, 4, 3),
+ 'type'= 'bar', 'name'= 'Montréal')
+ )
+ )
+ )
+ ))
+ ))
+ )))
+
+app$run_server()
+```
+
+Note that this method has a drawback: it requires that you compute the children property for each individual tab upfront and send all of the tab's content over the network at once. The callback method allows you to compute the tab's content on the fly (that is, when the tab is clicked).
+
+__*Styling the Tabs component*__
+
+*With CSS classes*
+
+Styling the Tabs (and Tab) component can either be done using CSS classes by providing your own to the `className` property:
+
+```r
+library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccTabs(
+ id="tabs-with-classes",
+ value='tab-2',
+ parent_className='custom-tabs',
+ className='custom-tabs-container',
+ children=list(
+ dccTab(
+ label='Tab one',
+ value='tab-1',
+ className='custom-tab',
+ selected_className='custom-tab--selected'
+ ),
+ dccTab(
+ label='Tab two',
+ value='tab-2',
+ className='custom-tab',
+ selected_className='custom-tab--selected'
+ ),
+ dccTab(
+ label='Tab three, multiline',
+ value='tab-3', className='custom-tab',
+ selected_className='custom-tab--selected'
+ ),
+ dccTab(
+ label='Tab four',
+ value='tab-4',
+ className='custom-tab',
+ selected_className='custom-tab--selected'
+ )
+ )),
+ htmlDiv(id='tabs-content-classes')
+ )))
+
+app$callback(
+ output = list(id='tabs-content-classes', property = 'children'),
+ params = list(input(id = 'tabs-with-classes', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 1'))
+ ))
+ } else if(tab == 'tab-2'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 2'))
+ ))
+ } else if(tab == 'tab-3'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 3'))
+ ))
+ } else if(tab == 'tab-4'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 4'))
+ ))
+ }
+ }
+)
+
+app$run_server()
+```
+
+Notice how the container of the Tabs can be styled as well by supplying a class to the `parent_className` prop, which we use here to draw a border below it, positioning the actual Tabs (with padding) more in the center. We also added `display: flex` and `justify-content: center` to the regular `Tab` components, so that labels with multiple lines will not break the flow of the text. The corresponding CSS file (`assets/tabs.css`) looks like this. Save the file in an `assets` folder (it can be named anything you want). Dash will automatically include this CSS when the app is loaded.
+
+```
+.custom-tabs-container { width: 85%; } .custom-tabs { border-top-left-radius: 3px; background-color: #f9f9f9; padding: 0px 24px; border-bottom: 1px solid #d6d6d6; }
+
+.custom-tab { color:#586069; border-top-left-radius: 3px; border-top: 3px solid transparent !important; border-left: 0px !important; border-right: 0px !important; border-bottom: 0px !important; background-color: #fafbfc; padding: 12px !important; font-family: "system-ui"; display: flex !important; align-items: center; justify-content: center; } .custom-tab--selected { color: black; box-shadow: 1px 1px 0px white; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; border-top: 3px solid #e36209 !important; }
+```
+
+*With inline styles*
+
+An alternative to providing CSS classes is to provide style dictionaries directly:
+
+```r
+library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+tabs_styles = list(
+ 'height'= '44px'
+)
+tab_style = list(
+ 'borderBottom'= '1px solid #d6d6d6',
+ 'padding'= '6px',
+ 'fontWeight'= 'bold'
+)
+
+tab_selected_style = list(
+ 'borderTop'= '1px solid #d6d6d6',
+ 'borderBottom'= '1px solid #d6d6d6',
+ 'backgroundColor'= '#119DFF',
+ 'color'= 'white',
+ 'padding'= '6px'
+)
+
+app$layout(htmlDiv(list(
+ dccTabs(id="tabs-styled-with-inline", value='tab-1', children=list(
+ dccTab(label='Tab 1', value='tab-1', style=tab_style, selected_style=tab_selected_style),
+ dccTab(label='Tab 2', value='tab-2', style=tab_style, selected_style=tab_selected_style),
+ dccTab(label='Tab 3', value='tab-3', style=tab_style, selected_style=tab_selected_style),
+ dccTab(label='Tab 4', value='tab-4', style=tab_style, selected_style=tab_selected_style)
+ ), style=tabs_styles),
+ htmlDiv(id='tabs-content-inline')
+ )))
+
+app$callback(
+ output = list(id='tabs-content-inline', property = 'children'),
+ params = list(input(id = 'tabs-styled-with-inline', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 1'))
+ ))
+ } else if(tab == 'tab-2'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 2'))
+ ))
+ } else if(tab == 'tab-3'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 3'))
+ ))
+ } else if(tab == 'tab-4'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 4'))
+ ))
+ }
+ }
+)
+
+app$run_server()
+```
+
+Lastly, you can set the colors of the Tabs components in the `color` prop, by specifying the "border", "primary", and "background" colors in a dict. Make sure you set them all, if you are using them!
+
+```r
+library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccTabs(id="tabs-styled-with-props", value='tab-1', children=list(
+ dccTab(label='1', value='tab-1'),
+ dccTab(label='2', value='tab-2')
+ ), colors=list(
+ "border"= "white",
+ "primary"= "gold",
+ "background"= "cornsilk"
+ )),
+ htmlDiv(id='tabs-content-props')
+ )))
+
+app$callback(
+ output = list(id='tabs-content-props', property = 'children'),
+ params = list(input(id = 'tabs-styled-with-props', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 1'))
+ ))
+ } else if(tab == 'tab-2'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 2'))
+ ))
+ }
+ })
+
+app$run_server()
+```
+
+### Graph Component
+
+Customize the [Plotly.js config options](https://plotly.com/javascript/configuration-options/) of your graph using the `config` property. The example below uses the `showSendToCloud` and `plotlyServerURL` options include a save button in the modebar of the graph which exports the figure to URL specified by `plotlyServerURL`.
+
+```r
+app$layout(htmlDiv(list(
+ dccDropdown(
+ id='my-dropdown1',
+ options=list(
+ list('label'= 'New York City', 'value'= 'NYC'),
+ list('label'= 'Montreal', 'value'= 'MTL'),
+ list('label'= 'San Francisco', 'value'= 'SF')
+ ),
+ value='NYC'
+ ),
+ dccGraph(
+ id='graph1',
+ config=list(
+ 'showSendToCloud'= TRUE,
+ 'plotlyServerURL'= 'https=//plotly.com'
+ )
+ )
+)))
+
+
+app$callback(
+ output = list(id='graph1', property='figure'),
+ params = list(input(id='my-dropdown1', property='value')),
+ function(value) {
+ y_list = list(
+ 'NYC'= list(4,2,3),
+ 'MTL'= list(1, 2, 4),
+ 'SF'= list(5, 3, 6)
+ )
+ return(list(
+ 'data' = list(
+ list(
+ 'type'= 'scatter',
+ 'y'= c(unlist(y_list[value]))
+ )),
+ 'layout'= list(
+ 'title'= value
+ )
+ ))
+
+ }
+)
+
+app$run_server()
+```
+
+### ConfirmDialog Component
+
+ConfirmDialog is used to display the browser's native 'confirm' modal, with an optional message and two buttons ('OK' and 'Cancel').
+
+```r
+library(dash)
+
+confirm <- dccConfirmDialog(
+ id = "confirm",
+ message = "Danger danger! Are you sure you want to continue?"
+)
+```
+
+This ConfirmDialog can be used in conjunction with buttons when the user is performing an action that should require an extra step of verification.
+
+```r
+library(dash)
+
+app = Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccConfirmDialog(
+ id='confirm',
+ message='Danger danger! Are you sure you want to continue?'
+ ),
+
+ dccDropdown(
+ options=lapply(list('Safe', 'Danger!!'),function(x){list('label'= x, 'value'= x)}),
+ id='dropdown'
+ ),
+ htmlDiv(id='output-confirm1')
+ )
+ )
+)
+
+app$callback(
+ output = list(id = 'confirm', property = 'displayed'),
+ params=list(input(id = 'dropdown', property = 'value')),
+ function(value){
+ if(value == 'Danger!!'){
+ return(TRUE)}
+ else{
+ return(FALSE)}
+ })
+
+
+app$callback(
+ output = list(id = 'output-confirm1', property = 'children'),
+ params=list(input(id = 'confirm', property = 'submit_n_clicks')),
+ function(n_clicks, value) {
+ if(length(submit_n_clicks) == FALSE){
+ sprintf('It wasnt easy but we did it %s', str(submit_n_clicks))
+ }
+ })
+
+app$run_server()
+```
+
+There is also a `dccConfirmDialogProvider`, it will automatically wrap a child component to send a `dccConfirmDialog` when clicked.
+
+```r
+confirm <- dccConfirmDialogProvider(
+ children = htmlButton("Click Me"),
+ id = "danger-danger",
+ message = "Danger danger! Are you sure you want to continue?"
+)
+```
+
+### Store Component
+
+The store component can be used to keep data in the visitor's browser. The data is scoped to the user accessing the page. Three types of storage (`storage_type` prop):
+
+* `memory`: default, keep the data as long the page is not refreshed.
+* `local`: keep the data until it is manually cleared.
+* `session`: keep the data until the browser/tab closes. *For local/session, the data is serialized as json when stored.*
+
+```r
+library(dash)
+
+store <- dccStore(id = "my-store", data = list("my-data" = "data"))
+```
+
+### Loading Component
+
+Here’s a simple example that wraps the outputs for a couple of `Input` components in the `Loading` component. As you can see, you can define the type of spinner you would like to show (refer to the reference table below for all possible types of spinners). You can modify other attributes as well, such as `fullscreen=TRUE` if you would like the spinner to be displayed fullscreen. Notice that, the Loading component traverses all of it's children to find a loading state, as demonstrated in the second callback, so that even nested children will get picked up.
+
+```
+library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(
+ children=list(
+ htmlH3("Edit text input to see loading state"),
+ dccInput(id="input-1", value='Input triggers local spinner'),
+ dccLoading(id="loading-1", children=list(htmlDiv(id="loading-output-1")), type="default"),
+ htmlDiv(
+ list(
+ dccInput(id="input-2", value='Input triggers nested spinner'),
+ dccLoading(
+ id="loading-2",
+ children=list(htmlDiv(list(htmlDiv(id="loading-output-2")))),
+ type="circle"
+ )
+ )
+ )
+ )
+))
+
+app$callback(
+ output = list(id='loading-output-1', property = 'children'),
+ params = list(input(id = 'input-1', property = 'value')),
+ function(value){
+ Sys.sleep(1)
+ return(value)
+ }
+)
+
+
+app$callback(
+ output = list(id='loading-output-2', property = 'children'),
+ params = list(input(id = 'input-2', property = 'value')),
+ function(value){
+ Sys.sleep(1)
+ return(value)
+ }
+)
+
+
+app$run_server()
+```
+
+### Location Component
+
+The location component represents the location bar in your web browser. Through its `href`, `pathname`, `search` and `hash` properties you can access different portions of your app's url. For example, given the url `http://127.0.0.1:8050/page-2?a=test#quiz`:
+
+* `href` = `'http://127.0.0.1:8050/page-2?a=test#quiz'`
+* `pathname` = `'/page-2'`
+* `search` = `'?a=test'`
+* `hash` = `'#quiz'`
diff --git a/components/dash-core-components-refresh/vignettes/dash-core-components.html b/components/dash-core-components-refresh/vignettes/dash-core-components.html
new file mode 100644
index 0000000000..638291e35b
--- /dev/null
+++ b/components/dash-core-components-refresh/vignettes/dash-core-components.html
@@ -0,0 +1,1865 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Dash Core Components
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Dash Core Components
+
+
+
+
Dash ships with supercharged components for interactive user interfaces. A core set of components, written and maintained by the Dash team, is available in the dashCoreComponents
package. The source is on GitHub at plotly/dash-core-components.
+Please visit our online documentation, which is interactive and frequently updated: https://dashr.plotly.com.
+
+
Dropdown Component
+
Default Dropdown
+
An example of a default dropdown without any extra properties.
+
dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ value=list("MTL", "NYC"),
+ multi=TRUE
+)
+
Multi-Value Dropdown
+
A dropdown component with the multi
property set to TRUE
will allow the user to select more than one value at a time.
+
dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ value="MTL",
+ id="my-dropdown"
+)
+
Disable Search
+
The searchable
property is set to TRUE
by default on all Dropdown components. To prevent searching the dropdown value, just set the searchable
property to FALSE
. Try searching for ‘New York’ on this dropdown below and compare it to the other dropdowns on the page to see the difference.
+
dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ searchable = FALSE
+)
+
Dropdown Clear
+
The clearable
property is set to TRUE
by default on all Dropdown components. To prevent the clearing of the selected dropdown value, just set the clearable property to FALSE
:
+
dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ value = "MTL",
+ clearable = FALSE
+)
+
Placeholder Text
+
The placeholder property allows you to define default text shown when no value is selected.
+
dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ placeholder="Select a city"
+)
+
Disable Dropdown
+
To disable the dropdown just set disabled=TRUE
.
+
dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC"),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF")
+ ),
+ disabled=TRUE
+)
+
Disable Options
+
To disable a particular option inside the dropdown menu, set the disabled property in the options.
+
dccDropdown(
+ options=list(
+ list(label="New York City", value="NYC", "disabled" = TRUE),
+ list(label="Montréal", value="MTL"),
+ list(label="San Francisco", value="SF", "disabled" = TRUE)
+ )
+)
+
+
+
Slider Component
+
Simple Slider Example
+
An example of a basic slider tied to a callback.
+
library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccSlider(
+ id='my-slider',
+ min=0,
+ max=20,
+ step=0.5,
+ value=10
+ ),
+ htmlDiv(id='slider-output-container')
+ )
+ )
+)
+
+app$callback(
+ output(id = 'slider-output-container', property = 'children'),
+ params=list(input(id = 'my-slider', property = 'value')),
+ function(value) {
+ sprintf("you have selected %0.1f", value)
+ })
+
+app$run_server()
+
Marks and Steps
+
If slider marks are defined and step is set to NULL
then the slider will only be able to select values that have been predefined by the marks. marks is a list where the keys represent the numerical values and the values represent their labels.
+
dccSlider(
+ min=0,
+ max=10,
+ marks = list(
+ "0" = "0 °F",
+ "3" = "3 °F",
+ "5" = "5 °F",
+ "7.65" = "7.65 °F",
+ "10" = "10 °F"
+ ),
+ value=5
+)
+
By default, included=TRUE
, meaning the rail trailing the handle will be highlighted. To have the handle act as a discrete value set included=FALSE
. To style marks
, include a style css attribute alongside the list value.
+
dccSlider(
+ min=0,
+ max=100,
+ value = 65,
+ marks = list(
+ "0" = list("label" = "0 °C", "style" = list("color" = "#77b0b1")),
+ "26" = list("label" = "26 °C"),
+ "37" = list("label" = "37 °C"),
+ "100" = list("label" = "100 °C", "style" = list("color" = "#FF4500"))
+ )
+)
+
Non-Linear Slider and Updatemode
+
library(dash)
+
+transform_value = function(value){
+ return(10**value)
+}
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccSlider(
+ id='slider-updatemode',
+ marks=unlist(lapply(list(1:4), function(x){10**x})),
+ max=3,
+ value=2,
+ step=0.01,
+ updatemode='drag'
+ ),
+ htmlDiv(id='updatemode-output-container', style=list('margin-top' = 20))
+ )
+ )
+)
+
+app$callback(
+ output(id = 'updatemode-output-container', property='children'),
+ params=list(input(id='slider-updatemode', property='value')),
+ function(value) {
+ sprintf('Linear Value: %g | Log Value: %0.2f', value, transform_value(value))
+ })
+
+app$run_server()
+
+
+
RangeSlider Component
+
Simple RangeSlider Example
+
An example of a basic RangeSlider tied to a callback.
+
library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccRangeSlider(
+ id='my-range-slider',
+ min=0,
+ max=20,
+ step=0.5,
+ value=list(5, 15)
+ ),
+ htmlDiv(id='output-container-range-slider')
+ )
+ )
+)
+
+app$callback(
+ output(id = 'output-container-range-slider', property='children'),
+ params=list(input(id='my-range-slider', property='value')),
+ function(value) {
+ sprintf('You have selected [%0.1f, %0.1f]', value[1], value[2])
+ })
+
+app$run_server()
+
Marks and Steps
+
If slider marks are defined and step is set to NULL
then the slider will only be able to select values that have been predefined by the marks.
+
dccRangeSlider(
+ min=0,
+ max=10,
+ marks=list(
+ "0" = "0 °F",
+ "3" = "3 °F",
+ "5" = "5 °F",
+ "7.65" = "7.65 °F",
+ "10" = "10 °F"
+ ),
+ value=list(3, 7.65)
+)
+
Included and Styling Marks
+
By default, included=TRUE
, meaning the rail trailing the handle will be highlighted. To have the handle act as a discrete value set included=FALSE
. To style marks, include a style css attribute alongside the key value.
+
library(dash)
+
+dccRangeSlider(
+ min=0,
+ max=100,
+ marks = list(
+ "0" = list("label" = "0 °C", "style" = list("color" = "#77b0b1")),
+ "26" = list("label" = "26 °C"),
+ "37" = list("label" = "37 °C"),
+ "100" = list("label" = "100°C" , "style" = list("color" = "#FF4500"))
+ )
+)
+
Multiple Handles
+
To create multiple handles just define more values for value
!
+
dccRangeSlider(
+ min=0,
+ max=30,
+ value=list(1, 3, 4, 5, 12, 17)
+)
+
Crossing Handles
+
If allowCross=FALSE
, the handles will not be allowed to cross over each other.
+
dccRangeSlider(
+ min=0,
+ max=30,
+ value=list(10,15),
+ allowCross = FALSE
+)
+
Non-Linear Slider and Updatemode
+
Create a logarithmic slider by setting marks
to be logarithmic and adjusting the slider’s output value in the callbacks. The updatemode
property allows us to determine when we want a callback to be triggered. The following example has updatemode='drag'
which means a callback is triggered everytime the handle is moved. Contrast the callback output with the first example on this page to see the difference.
+
library(dash)
+
+transform_value = function(value){
+ return(10 ** value)
+}
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccRangeSlider(
+ id='non-linear-range-slider',
+ marks=unlist(lapply(list(1:4), function(x){10**x})),
+ max=3,
+ value=list(0.1, 2),
+ dots=FALSE,
+ step=0.01,
+ updatemode='drag'
+ ),
+ htmlDiv(id='output-container-range-slider-non-linear', style=list('margin-top' = 20))
+ )
+))
+
+app$callback(
+ output(id = 'output-container-range-slider-non-linear', property='children'),
+ params=list(input(id='non-linear-range-slider', property='value')),
+ function(value) {
+ transformed_value = lapply(value, transform_value)
+ sprintf('Linear Value: %g, Log Value: [%0.2f, %0.2f]', value[2],transformed_value[1], transformed_value[2])
+ })
+
+app$run_server()
+
+
+
Input Component
+
Update Value on Keypress
+
library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccInput(id='input-1-keypress', type='text', value='Montreal'),
+ dccInput(id='input-2-keypress', type='text', value='Canada'),
+ htmlDiv(id='output-keypress1')
+ )
+ )
+)
+
+app$callback(
+ output(id = 'output-keypress1', property='children'),
+ params=list(input(id='input-1-keypress', property='value'),
+ input(id='input-2-keypress', property='value')),
+ function(input1, input2) {
+ sprintf('Input 1 is %s and input 2 is %s', input1,input2)
+ })
+
+app$run_server()
+
Update Value on Enter/Blur
+
dccInput
has properties n_submit
, which updates when the enter button is pressed, and n_blur
, which updates when the component loses focus (e.g. tab is pressed or the user clicks outside of the input field).
+
library(dash)
+
+app <- Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccInput(id='input-1-submit', type='text', value='Montreal'),
+ dccInput(id='input-2-submit', type='text', value='Canada'),
+ htmlDiv(id='output-keypress')
+ )
+ ))
+
+app$callback(
+ output(id = 'output-keypress', property='children'),
+ params=list(input(id='input-1-submit', property='n_submit'),
+ input(id = 'input-1-submit', property = 'n_blur'),
+ input(id='input-2-submit', property='n_submit'),
+ input(id = 'input-2-submit', property = 'n_blur'),
+ state('input-1-submit', 'value'),
+ state('input-2-submit', 'value')),
+ function(ns1, nb1, ns2, nb2, input1, input2) {
+ sprintf('Input 1 is %s and input 2 is %s', input1, input2)
+ })
+
+app$run_server()
+
+
+
Textarea Component
+
library(dash)
+
+dccTextarea(
+ placeholder = "Enter a value...",
+ value = "This is a TextArea component",
+ style = list("width" = "100%")
+)
+
+
+
Checklist Component
+
library(dash)
+
+dccChecklist(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value=list("MTL", "SF")
+)
+
library(dash)
+
+dccChecklist(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value=list("MTL", "SF"),
+ labelStyle = list("display" = "inline-block")
+)
+
+
+
RadioItems Component
+
library(dash)
+
+dccRadioItems(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value = "MTL"
+)
+
library(dash)
+
+dccRadioItems(
+ options=list(
+ list("label" = "New York City", "value" = "NYC"),
+ list("label" = "Montréal", "value" = "MTL"),
+ list("label" = "San Francisco", "value" = "SF")
+ ),
+ value = "MTL",
+ labelStyle = list("display" = "inline-block")
+)
+
+
+
+
DatePickerSingle Component
+
Calendar Clear and Portals
+
When the clearable
property is set to TRUE
the component will be rendered with a small ‘x’ that will remove all selected dates when selected.
+
The DatePickerSingle
component supports two different portal types, one being a full screen portal (with_full_screen_portal
) and another being a simple screen overlay, like the one shown below (with_portal
).
+
library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccDatePickerSingle(
+ id='my-date-picker-single',
+ min_date_allowed=as.Date('1995-8-5'),
+ max_date_allowed=as.Date('2017-9-19'),
+ initial_visible_month=as.Date('2017-8-5'),
+ date = as.POSIXct("2017-08-25 23:59:59", tz = "GMT")
+ ),
+ htmlDiv(id='output-container-date-picker-single')
+ )
+ )
+)
+
+app$callback(
+ output = list(id='output-container-date-picker-single', property = 'children'),
+ params = list(input(id = 'my-date-picker-single', property = 'date')),
+ function(date){
+ if(is.null(date) == FALSE){
+ date = format(as.Date(date), format = '%B %d,%Y')
+ sprintf('You have selected: %s', date)
+ }
+ }
+)
+
Month and Display Format
+
The display_format
property determines how selected dates are displayed in the DatePickerRange
component. The month_format
property determines how calendar headers are displayed when the calendar is opened.
+
Both of these properties are configured through strings that utilize a combination of any of the following tokens.
+
+
+
+
+
+
+YYYY |
+2014 |
+4 or 2 digit year |
+
+
+YY |
+14 |
+2 digit year |
+
+
+Y |
+-25 |
+Year with any number of digits and sign |
+
+
+Q |
+1..4 |
+Quarter of year. Sets month to first month in quarter. |
+
+
+M MM |
+1..12 |
+Month number |
+
+
+MMM MMMM |
+Jan..December |
+Month name |
+
+
+D DD |
+1..31 |
+Day of month |
+
+
+Do |
+1st..31st |
+Day of month with ordinal |
+
+
+DDD DDDD |
+1..365 |
+Day of year |
+
+
+X |
+1410715640.579 |
+Unix timestamp |
+
+
+X |
+1410715640579 |
+Unix ms timestamp |
+
+
+
+
Display Format Examples
+
You can utilize any permutation of the string tokens shown in the table above to change how selected dates are displayed in the DatePickerRange
component.
+
dccDatePickerSingle(
+ date="2017-06-21",
+ display_format="MMM Do, YY"
+)
+
+# Displays "Jun 21st, 17""
+
dccDatePickerSingle(
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format="M-D-Y-Q"
+)
+# Displays "6-21-2017-2"
+
dccDatePickerSingle(
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format="MMMM Y, DD"
+)
+# Displays "June 2017, 21"
+
dccDatePickerSingle(
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format="X"
+)
+# Displays "1498017600"
+
Month Format Examples
+
Similar to the display_format
, you can set month_format
to any permutation of the string tokens shown in the table above to change how calendar titles are displayed in the DatePickerRange
component.
+
dccDatePickerSingle(
+ month_format="MMM Do, YY",
+ placeholder="MMM Do, YY",
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+
dccDatePickerSingle(
+ month_format="M-D-Y-Q",
+ placeholder="M-D-Y-Q",
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+
dccDatePickerSingle(
+ month_format="MMMM Y",
+ placeholder="MMMM Y",
+ date=format(as.Date("2020-2-29"), format = "%Y,%m,%d")
+)
+# Displays "02/29/2020"
+
Vertical Calendar and Placeholder Text
+
The DatePickerRange
component can be rendered in two orientations, either horizontally or vertically. If calendar_orientation
is set to ‘vertical’, it will be rendered vertically and will default to ‘horizontal’ if not defined.
+
The start_date_placeholder_text
and end_date_placeholder_text
define the grey default text defined in the calendar input boxes when no date is selected.
+
dccDatePickerSingle(
+ calendar_orientation="vertical",
+ placeholder="Select a date",
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+
Calendar Clear and Portals
+
When the clearable
property is set to TRUE
the component will be rendered with a small ‘x’ that will remove all selected dates when selected.
+
The DatePickerSingle
component supports two different portal types, one being a full screen portal (with_full_screen_portal
) and another being a simple screen overlay, like the one shown below (with_portal
).
+
+dccDatePickerSingle(
+ clearable=TRUE,
+ with_portal=TRUE,
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "06/21/2017"
+
Right to Left Calendars and First Day of Week
+
When the is_RTL
property is set to TRUE
the calendar will be rendered from right to left.
+
The first_day_of_week
property allows you to define which day of the week will be set as the first day of the week. In the example below, Tuesday is the first day of the week.
+
dccDatePickerSingle(
+ is_RTL=TRUE,
+ first_day_of_week=3,
+ date=format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+ )
+# Displays "06/21/2017"
+
+
+
DatePickerRange Component
+
Simple DatePickerRange Example
+
This is a simple example of a DatePickerRange
component tied to a callback. The min_date_allowed
and max_date_allowed
properties define the minimum and maximum selectable dates on the calendar while initial_visible_month
defines the calendar month that is first displayed when the DatePickerRange
component is opened.
+
library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccDatePickerRange(
+ id='my-date-picker-single',
+ min_date_allowed=as.Date('1995-8-5'),
+ max_date_allowed=as.Date('2017-9-19'),
+ initial_visible_month=as.Date('2017-8-5'),
+ end_date = as.Date('2017-8-5')
+ ),
+ htmlDiv(id='output-container-date-picker-range')
+ )
+ )
+)
+
+app$callback(
+ output = list(id='output-container-date-picker-range', property = 'children'),
+ params = list(input(id = 'my-date-picker-range', property = 'start_date'),
+ input(id = 'my-date-picker-range', property = 'end_date')),
+ function(start_date, end_date){
+ string_prefix = 'You have selected: '
+
+ if(is.null(start_date) == FALSE){
+ start_date = format(as.Date(start_date), format = '%B %d,%Y')
+ string_prefix = paste(string_prefix, 'Start Date ', start_date, sep = "")
+ }
+ if(is.null(end_date) == FALSE){
+ end_date = format(as.Date(end_date), format = '%B %d,%Y')
+ string_prefix = paste(string_prefix, 'End Date: ', end_date, sep = "")
+
+ }
+ if(nchar(string_prefix) == nchar('You have selected: ')){
+ return('Select a date to see it displayed here')
+ }
+ else{
+ return(string_prefix)
+ }
+ }
+
+)
+
+app$run_server()
+
Month and Display Format
+
The display_format
property determines how selected dates are displayed in the DatePickerRange
component. The month_format
property determines how calendar headers are displayed when the calendar is opened.
+
Both of these properties are configured through strings that utilize a combination of any of the following tokens.
+
+
+
+
+
+
+YYYY |
+2014 |
+4 or 2 digit year |
+
+
+YY |
+14 |
+2 digit year |
+
+
+Y |
+-25 |
+Year with any number of digits and sign |
+
+
+Q |
+1..4 |
+Quarter of year. Sets month to first month in quarter. |
+
+
+M MM |
+1..12 |
+Month number |
+
+
+MMM MMMM |
+Jan..December |
+Month name |
+
+
+D DD |
+1..31 |
+Day of month |
+
+
+Do |
+1st..31st |
+Day of month with ordinal |
+
+
+DDD DDDD |
+1..365 |
+Day of year |
+
+
+X |
+1410715640.579 |
+Unix timestamp |
+
+
+X |
+1410715640579 |
+Unix ms timestamp |
+
+
+
+
Display Format Examples
+
You can utilize any permutation of the string tokens shown in the table above to change how selected dates are displayed in the DatePickerRange
component.
+
dccDatePickerRange(
+ end_date = as.POSIXct("2017-6-21 23:59:59", tz = "GMT"),
+ display_format = "MMM Do, YY",
+ start_date_placeholder_text = "MMM Do, YY"
+)
+# Displays "Jun 21st, 17"
+
dccDatePickerRange(
+ end_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format = "M-D-Y-Q",
+ start_date_placeholder_text = "M-D-Y-Q"
+)
+# Displays "6-21-2017-2"
+
dccDatePickerRange(
+ end_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format = "MMMM Y, DD",
+ start_date_placeholder_text = "MMMM Y, DD"
+)
+# Displays "June 2017, 21"
+
dccDatePickerRange(
+ end_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d"),
+ display_format = "X",
+ start_date_placeholder_text = "X"
+)
+# Displays "1498017600"
+
Month Format Examples
+
Similar to the display_format
, you can set month_format
to any permutation of the string tokens shown in the table above to change how calendar titles are displayed in the DatePickerRange
component.
+
dccDatePickerRange(
+ display_format = "MMM Do, YY",
+ start_date_placeholder_text = "MMM Do, YY",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "Jun 21st, 17"
+
dccDatePickerRange(
+ display_format = "M-D-Y-Q",
+ start_date_placeholder_text = "M-D-Y-Q",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "6-21-2017-2"
+
dccDatePickerRange(
+ display_format = "MMMM Y",
+ start_date_placeholder_text = "MMMM Y",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "June 2017"
+
dccDatePickerRange(
+ display_format = "X",
+ start_date_placeholder_text = "X",
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+# Displays "1498017600"
+
Vertical Calendar and Placeholder Text
+
The DatePickerRange
component can be rendered in two orientations, either horizontally or vertically. If calendar_orientation
is set to ‘vertical’, it will be rendered vertically and will default to ‘horizontal’ if not defined.
+
The start_date_placeholder_text
and end_date_placeholder_text
define the grey default text defined in the calendar input boxes when no date is selected.
+
dccDatePickerRange(
+ start_date_placeholder_text = "Start Period",
+ end_date_placeholder_text = "End Period",
+ calendar_orientation = "vertical",
+)
+
Minimum Nights, Calendar Clear, and Portals
+
The minimum_nights property
defines the number of nights that must be in between the range of two selected dates.
+
When the clearable
property is set to TRUE
the component will be rendered with a small ‘x’ that will remove all selected dates when selected.
+
The DatePickerRange
component supports two different portal types, one being a full screen portal (with_full_screen_portal
) and another being a simple screen overlay, like the one shown below (with_portal
).
+
dccDatePickerRange(
+ minimum_nights = 5,
+ clearable = TRUE,
+ with_portal = TRUE,
+ start_date = format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+
Right to Left Calendars and First Day of Week
+
When the is_RTL
property is set to TRUE
the calendar will be rendered from right to left.
+
The first_day_of_week
property allows you to define which day of the week will be set as the first day of the week. In the example below, Tuesday is the first day of the week.
+
dccDatePickerRange(
+ is_RTL = TRUE,
+ first_day_of_week = 3,
+ start_date= format(as.Date("2017-6-21"), format = "%Y,%m,%d")
+)
+
+
+
Markdown Component
+
Syntax Guide
+
These examples are based on the GitHub Markdown Guide. The Dash Markdown component uses the CommonMark specification of Markdown.
+
Headers
+
library(dash)
+
+dccMarkdown('
+
+# This is an <h1> tag
+
+## This is an <h2> tag
+
+###### This is an <h6> tag)
+
library(dash)
+
+dccMarkdown('
+
+ *This text will be italic*
+
+ _This will also be italic_
+
+
+ **This text will be bold**
+
+ __This will also be bold__
+
+ _You **can** combine them_
+'
+)
+
Lists
+
Unordered
+
library(dash)
+
+dccMarkdown('
+* Item 1
+* Item 2
+ * Item 2a
+ * Item 2b
+')
+
Block Quotes
+
library(dash)
+
+dccMarkdown('
+>
+> Block quotes are used to highlight text.
+>
+')
+
Links
+
library(dash)
+
+dccMarkdown('
+[Dash User Guide](https://dash.plotly.com/)')
+
Inline Code
+
Any block of text surrounded by
will rendered as inline-code.
+
library(dash)
+
+dccMarkdown('
+Inline code snippet = `TRUE` ')
+
+
+
Interactive Tables
+
The dashHtmlComponents
package exposes all of the HTML tags. This includes the Table
, Tr
, and Tbody
tags that can be used to create an HTML table. See Create Your First Dash App, Part 1 for an example. Dash provides an interactive DataTable as part of the dash-table
project. This table includes built-in filtering, row-selection, editing, and sorting.
+
+
+
Upload Component
+
The Dash upload component allows your app’s viewers to upload files, like excel spreadsheets or images, into your application. Your Dash app can access the contents of an upload by listening to the contents
property of the dccUpload component. contents
is a base64 encoded string that contains the files contents, no matter what type of file: text files, images, zip files, excel spreadsheets, etc.
+
Here’s an example that parses CSV or Excel files and displays the results in a table. Note that this example uses the DataTable
from the dash-table
project.
+
library(dashHtmlComponents)
+library(dash)
+library(anytime)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccUpload(
+ id='upload-data',
+ children=htmlDiv(list(
+ 'Drag and Drop or ',
+ htmlA('Select Files')
+ )),
+ style=list(
+ 'width'= '100%',
+ 'height'= '60px',
+ 'lineHeight'= '60px',
+ 'borderWidth'= '1px',
+ 'borderStyle'= 'dashed',
+ 'borderRadius'= '5px',
+ 'textAlign'= 'center',
+ 'margin'= '10px'
+ ),
+ # Allow multiple files to be uploaded
+ multiple=TRUE
+ ),
+ htmlDiv(id='output-data-upload')
+)))
+
+parse_contents = function(contents, filename, date){
+ content_type = strsplit(contents, ",")
+ content_string = strsplit(contents, ",")
+ decoded = base64_dec(content_string)
+
+ if('csv' %in% filename){
+ df = read.csv(utf8::as_utf8(decoded))
+ } else if('xls' %in% filename){
+ df = read.table(decoded, encoding = 'bytes')
+ } else{
+ return(htmlDiv(list(
+ 'There was an error processing this file.'
+ )))
+ }
+
+ return(htmlDiv(list(
+ htmlH5(filename),
+ htmlH6(anytime(date)),
+ dashDataTable(df_to_list('records'),columns = lapply(colnames(df), function(x){list('name' = x, 'id' = x)})),
+ htmlHr(),
+ htmlDiv('Raw Content'),
+ htmlPre(paste(substr(toJSON(contents), 1, 100), "..."), style=list(
+ 'whiteSpace'= 'pre-wrap',
+ 'wordBreak'= 'break-all'
+ ))
+ )))
+}
+
+app$callback(
+ output = list(id='output-data-upload', property = 'children'),
+ params = list(input(id = 'upload-data', property = 'contents'),
+ state(id = 'upload-data', property = 'filename'),
+ state(id = 'upload-data', property = 'last_modified')),
+ function(list_of_contents, list_of_names, list_of_dates){
+ if(is.null(list_of_contents) == FALSE){
+ children = lapply(1:length(list_of_contents), function(x){
+ parse_content(list_of_contents[[x]], list_of_names[[x]], list_of_dates[[x]])
+ })
+
+ }
+ return(children)
+ })
+
+app$run_server()
+
This next example responds to image uploads by displaying them in the app with the htmlImg
component.
+
library(dashHtmlComponents)
+library(dash)
+library(anytime)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccUpload(
+ id='upload-image',
+ children=htmlDiv(list(
+ 'Drag and Drop or ',
+ htmlA('Select Files')
+ )),
+ style=list(
+ 'width'= '100%',
+ 'height'= '60px',
+ 'lineHeight'= '60px',
+ 'borderWidth'= '1px',
+ 'borderStyle'= 'dashed',
+ 'borderRadius'= '5px',
+ 'textAlign'= 'center',
+ 'margin'= '10px'
+ ),
+ # Allow multiple files to be uploaded
+ multiple=TRUE
+ ),
+ htmlDiv(id='output-image-upload')
+)))
+
+parse_content = function(contents, filename, date){
+ return(htmlDiv(list(
+ htmlH5(filename),
+ htmlH6(anytime(date)),
+ htmlImg(src=contents),
+ htmlHr(),
+ htmlDiv('Raw Content'),
+ htmlPre(paste(substr(toJSON(contents), 1, 100), "..."), style=list(
+ 'whiteSpace'= 'pre-wrap',
+ 'wordBreak'= 'break-all'
+ ))
+ )))
+}
+
+app$callback(
+ output = list(id='output-image-upload', property = 'children'),
+ params = list(input(id = 'upload-image', property = 'contents'),
+ state(id = 'upload-image', property = 'filename'),
+ state(id = 'upload-image', property = 'last_modified')),
+ function(list_of_contents, list_of_names, list_of_dates){
+ if(is.null(list_of_contents) == FALSE){
+ children = lapply(1:length(list_of_contents), function(x){
+ parse_content(list_of_contents[[x]], list_of_names[[x]], list_of_dates[[x]])
+ })
+ } else{
+
+ }
+ return(children)
+ }
+)
+
+app$run_server()
+
The children
attribute of the Upload
component accepts any Dash component. Clicking on the children element will trigger the upload action, as will dragging and dropping files. Here are a few different ways that you could style the upload component using standard dash components.
+
library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccUpload(htmlButton('Upload File')),
+
+ htmlHr(),
+
+ dccUpload(htmlA('Upload File')),
+
+ htmlHr(),
+
+ dccUpload(list(
+ 'Drag and Drop or ',
+ htmlA('Select a File')
+ ), style=list(
+ 'width'= '100%',
+ 'height'= '60px',
+ 'lineHeight'= '60px',
+ 'borderWidth'= '1px',
+ 'borderStyle'= 'dashed',
+ 'borderRadius'= '5px',
+ 'textAlign'= 'center'
+ ))
+ )))
+
+app$run_server()
+
+
+
Tabs Component
+
The Tabs and Tab components can be used to create tabbed sections in your app. The Tab
component controls the style and value of the individual tab and the Tabs
component hold a collection of Tab components.
+
+- Method 1. Content as Callback
+- Method 2. Content as Tab children
+- Styling the Tabs component
+
+- with CSS classes
+- with inline styles
+- with props
+
+
+
Method 1. Content as Callback
+
Attach a callback to the Tabs value
prop and update a container’s children
property in your callback.
+
library(dashHtmlComponents)
+library(dash)
+
+utils <- new.env()
+source('dashr/utils.R', local=utils)
+
+examples <- list(
+ button = utils$LoadExampleCode('dashr/chapters/dash-core-components/Button/examples/button.R'),
+ tabs = utils$LoadExampleCode('dashr/chapters/dash-core-components/Tabs/examples/tabs.R')
+)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ htmlH1('Dash Tabs component demo'),
+ dccTabs(id="tabs-example", value='tab-1-example', children=list(
+ dccTab(label='Tab One', value='tab-1-example'),
+ dccTab(label='Tab Two', value='tab-2-example')
+ )),
+ htmlDiv(id='tabs-content-example')
+)))
+
+app$callback(
+ output = list(id='tabs-content-example', property = 'children'),
+ params = list(input(id = 'tabs-example', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1-example'){
+ return(htmlDiv(list(
+ htmlH3('Tab content 1'),
+ dccGraph(
+ id='graph-1-tabs',
+ figure=list(
+ 'data' = list(list(
+ 'x' = c(1, 2, 3),
+ 'y' = c(3, 1, 2),
+ 'type' = 'bar'
+ ))
+ )
+ )
+ )))
+ }
+
+ else if(tab == 'tab-2-example'){
+ return(htmlDiv(list(
+ htmlH3('Tab content 2'),
+ dccGraph(
+ id='graph-2-tabs',
+ figure=list(
+ 'data' = list(list(
+ 'x' = c(1, 2, 3),
+ 'y' = c(5, 10, 6),
+ 'type' = 'bar'
+ ))
+ )
+ )
+ )))
+ }
+ }
+
+
+
+)
+
+app$run_server()
+
Method 2. Content as Tab Children
+
Instead of displaying the content through a callback, you can embed the content directly as the children
property in the Tab
component:
+
app$layout(htmlDiv(list(
+ dccTabs(id="tabs", children=list(
+ dccTab(label='Tab one', children=list(
+ htmlDiv(list(
+ dccGraph(
+ id='example-graph',
+ figure=list(
+ 'data'= list(
+ list('x'= c(1, 2, 3), 'y'= c(4, 1, 2),
+ 'type'= 'bar', 'name'= 'SF'),
+ list('x'= c(1, 2, 3), 'y'= c(2, 4, 5),
+ 'type'= 'bar', 'name'= 'Montréal')
+ )
+ )
+ )
+ ))
+ )),
+ dccTab(label='Tab two', children=list(
+ dccGraph(
+ id='example-graph-1',
+ figure=list(
+ 'data'= list(
+ list('x'= c(1, 2, 3), 'y'= c(1, 4, 1),
+ 'type'= 'bar', 'name'= 'SF'),
+ list('x'= c(1, 2, 3), 'y'= c(1, 2, 3),
+ 'type'= 'bar', 'name'= 'Montréal')
+ )
+ )
+ )
+ )),
+ dccTab(label='Tab three', children=list(
+ dccGraph(
+ id='example-graph-2',
+ figure=list(
+ 'data'= list(
+ list('x'= list(1, 2, 3), 'y'= list(2, 4, 3),
+ 'type'= 'bar', 'name'= 'SF'),
+ list('x'= list(1, 2, 3), 'y'= list(5, 4, 3),
+ 'type'= 'bar', 'name'= 'Montréal')
+ )
+ )
+ )
+ ))
+ ))
+ )))
+
+app$run_server()
+
Note that this method has a drawback: it requires that you compute the children property for each individual tab upfront and send all of the tab’s content over the network at once. The callback method allows you to compute the tab’s content on the fly (that is, when the tab is clicked).
+
Styling the Tabs component
+
With CSS classes
+
Styling the Tabs (and Tab) component can either be done using CSS classes by providing your own to the className
property:
+
library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccTabs(
+ id="tabs-with-classes",
+ value='tab-2',
+ parent_className='custom-tabs',
+ className='custom-tabs-container',
+ children=list(
+ dccTab(
+ label='Tab one',
+ value='tab-1',
+ className='custom-tab',
+ selected_className='custom-tab--selected'
+ ),
+ dccTab(
+ label='Tab two',
+ value='tab-2',
+ className='custom-tab',
+ selected_className='custom-tab--selected'
+ ),
+ dccTab(
+ label='Tab three, multiline',
+ value='tab-3', className='custom-tab',
+ selected_className='custom-tab--selected'
+ ),
+ dccTab(
+ label='Tab four',
+ value='tab-4',
+ className='custom-tab',
+ selected_className='custom-tab--selected'
+ )
+ )),
+ htmlDiv(id='tabs-content-classes')
+ )))
+
+app$callback(
+ output = list(id='tabs-content-classes', property = 'children'),
+ params = list(input(id = 'tabs-with-classes', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 1'))
+ ))
+ } else if(tab == 'tab-2'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 2'))
+ ))
+ } else if(tab == 'tab-3'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 3'))
+ ))
+ } else if(tab == 'tab-4'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 4'))
+ ))
+ }
+ }
+)
+
+app$run_server()
+
Notice how the container of the Tabs can be styled as well by supplying a class to the parent_className
prop, which we use here to draw a border below it, positioning the actual Tabs (with padding) more in the center. We also added display: flex
and justify-content: center
to the regular Tab
components, so that labels with multiple lines will not break the flow of the text. The corresponding CSS file (assets/tabs.css
) looks like this. Save the file in an assets
folder (it can be named anything you want). Dash will automatically include this CSS when the app is loaded.
+
.custom-tabs-container { width: 85%; } .custom-tabs { border-top-left-radius: 3px; background-color: #f9f9f9; padding: 0px 24px; border-bottom: 1px solid #d6d6d6; }
+
+.custom-tab { color:#586069; border-top-left-radius: 3px; border-top: 3px solid transparent !important; border-left: 0px !important; border-right: 0px !important; border-bottom: 0px !important; background-color: #fafbfc; padding: 12px !important; font-family: "system-ui"; display: flex !important; align-items: center; justify-content: center; } .custom-tab--selected { color: black; box-shadow: 1px 1px 0px white; border-left: 1px solid lightgrey !important; border-right: 1px solid lightgrey !important; border-top: 3px solid #e36209 !important; }
+
With inline styles
+
An alternative to providing CSS classes is to provide style dictionaries directly:
+
library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+tabs_styles = list(
+ 'height'= '44px'
+)
+tab_style = list(
+ 'borderBottom'= '1px solid #d6d6d6',
+ 'padding'= '6px',
+ 'fontWeight'= 'bold'
+)
+
+tab_selected_style = list(
+ 'borderTop'= '1px solid #d6d6d6',
+ 'borderBottom'= '1px solid #d6d6d6',
+ 'backgroundColor'= '#119DFF',
+ 'color'= 'white',
+ 'padding'= '6px'
+)
+
+app$layout(htmlDiv(list(
+ dccTabs(id="tabs-styled-with-inline", value='tab-1', children=list(
+ dccTab(label='Tab 1', value='tab-1', style=tab_style, selected_style=tab_selected_style),
+ dccTab(label='Tab 2', value='tab-2', style=tab_style, selected_style=tab_selected_style),
+ dccTab(label='Tab 3', value='tab-3', style=tab_style, selected_style=tab_selected_style),
+ dccTab(label='Tab 4', value='tab-4', style=tab_style, selected_style=tab_selected_style)
+ ), style=tabs_styles),
+ htmlDiv(id='tabs-content-inline')
+ )))
+
+app$callback(
+ output = list(id='tabs-content-inline', property = 'children'),
+ params = list(input(id = 'tabs-styled-with-inline', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 1'))
+ ))
+ } else if(tab == 'tab-2'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 2'))
+ ))
+ } else if(tab == 'tab-3'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 3'))
+ ))
+ } else if(tab == 'tab-4'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 4'))
+ ))
+ }
+ }
+)
+
+app$run_server()
+
Lastly, you can set the colors of the Tabs components in the color
prop, by specifying the “border”, “primary”, and “background” colors in a dict. Make sure you set them all, if you are using them!
+
library(dashHtmlComponents)
+library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(list(
+ dccTabs(id="tabs-styled-with-props", value='tab-1', children=list(
+ dccTab(label='1', value='tab-1'),
+ dccTab(label='2', value='tab-2')
+ ), colors=list(
+ "border"= "white",
+ "primary"= "gold",
+ "background"= "cornsilk"
+ )),
+ htmlDiv(id='tabs-content-props')
+ )))
+
+app$callback(
+ output = list(id='tabs-content-props', property = 'children'),
+ params = list(input(id = 'tabs-styled-with-props', property = 'value')),
+ function(tab){
+ if(tab == 'tab-1'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 1'))
+ ))
+ } else if(tab == 'tab-2'){
+ return(htmlDiv(
+ list(htmlH3('Tab content 2'))
+ ))
+ }
+ })
+
+app$run_server()
+
+
+
Graph Component
+
Customize the Plotly.js config options of your graph using the config
property. The example below uses the showSendToCloud
and plotlyServerURL
options include a save button in the modebar of the graph which exports the figure to URL specified by plotlyServerURL
.
+
app$layout(htmlDiv(list(
+ dccDropdown(
+ id='my-dropdown1',
+ options=list(
+ list('label'= 'New York City', 'value'= 'NYC'),
+ list('label'= 'Montreal', 'value'= 'MTL'),
+ list('label'= 'San Francisco', 'value'= 'SF')
+ ),
+ value='NYC'
+ ),
+ dccGraph(
+ id='graph1',
+ config=list(
+ 'showSendToCloud'= TRUE,
+ 'plotlyServerURL'= 'https=//plotly.com'
+ )
+ )
+)))
+
+
+app$callback(
+ output = list(id='graph1', property='figure'),
+ params = list(input(id='my-dropdown1', property='value')),
+ function(value) {
+ y_list = list(
+ 'NYC'= list(4,2,3),
+ 'MTL'= list(1, 2, 4),
+ 'SF'= list(5, 3, 6)
+ )
+ return(list(
+ 'data' = list(
+ list(
+ 'type'= 'scatter',
+ 'y'= c(unlist(y_list[value]))
+ )),
+ 'layout'= list(
+ 'title'= value
+ )
+ ))
+
+ }
+)
+
+app$run_server()
+
+
+
ConfirmDialog Component
+
ConfirmDialog is used to display the browser’s native ‘confirm’ modal, with an optional message and two buttons (‘OK’ and ‘Cancel’).
+
library(dash)
+
+confirm <- dccConfirmDialog(
+ id = "confirm",
+ message = "Danger danger! Are you sure you want to continue?"
+)
+
This ConfirmDialog can be used in conjunction with buttons when the user is performing an action that should require an extra step of verification.
+
library(dash)
+
+app = Dash$new()
+
+app$layout(
+ htmlDiv(
+ list(
+ dccConfirmDialog(
+ id='confirm',
+ message='Danger danger! Are you sure you want to continue?'
+ ),
+
+ dccDropdown(
+ options=lapply(list('Safe', 'Danger!!'),function(x){list('label'= x, 'value'= x)}),
+ id='dropdown'
+ ),
+ htmlDiv(id='output-confirm1')
+ )
+ )
+)
+
+app$callback(
+ output = list(id = 'confirm', property = 'displayed'),
+ params=list(input(id = 'dropdown', property = 'value')),
+ function(value){
+ if(value == 'Danger!!'){
+ return(TRUE)}
+ else{
+ return(FALSE)}
+ })
+
+
+app$callback(
+ output = list(id = 'output-confirm1', property = 'children'),
+ params=list(input(id = 'confirm', property = 'submit_n_clicks')),
+ function(n_clicks, value) {
+ if(length(submit_n_clicks) == FALSE){
+ sprintf('It wasnt easy but we did it %s', str(submit_n_clicks))
+ }
+ })
+
+app$run_server()
+
There is also a dccConfirmDialogProvider
, it will automatically wrap a child component to send a dccConfirmDialog
when clicked.
+
confirm <- dccConfirmDialogProvider(
+ children = htmlButton("Click Me"),
+ id = "danger-danger",
+ message = "Danger danger! Are you sure you want to continue?"
+)
+
+
+
Store Component
+
The store component can be used to keep data in the visitor’s browser. The data is scoped to the user accessing the page. Three types of storage (storage_type
prop):
+
+memory
: default, keep the data as long the page is not refreshed.
+local
: keep the data until it is manually cleared.
+session
: keep the data until the browser/tab closes. For local/session, the data is serialized as json when stored.
+
+
library(dash)
+
+store <- dccStore(id = "my-store", data = list("my-data" = "data"))
+
+
+
Loading Component
+
Here’s a simple example that wraps the outputs for a couple of Input
components in the Loading
component. As you can see, you can define the type of spinner you would like to show (refer to the reference table below for all possible types of spinners). You can modify other attributes as well, such as fullscreen=TRUE
if you would like the spinner to be displayed fullscreen. Notice that, the Loading component traverses all of it’s children to find a loading state, as demonstrated in the second callback, so that even nested children will get picked up.
+
library(dash)
+
+app = Dash$new()
+
+app$layout(htmlDiv(
+ children=list(
+ htmlH3("Edit text input to see loading state"),
+ dccInput(id="input-1", value='Input triggers local spinner'),
+ dccLoading(id="loading-1", children=list(htmlDiv(id="loading-output-1")), type="default"),
+ htmlDiv(
+ list(
+ dccInput(id="input-2", value='Input triggers nested spinner'),
+ dccLoading(
+ id="loading-2",
+ children=list(htmlDiv(list(htmlDiv(id="loading-output-2")))),
+ type="circle"
+ )
+ )
+ )
+ )
+))
+
+app$callback(
+ output = list(id='loading-output-1', property = 'children'),
+ params = list(input(id = 'input-1', property = 'value')),
+ function(value){
+ Sys.sleep(1)
+ return(value)
+ }
+)
+
+
+app$callback(
+ output = list(id='loading-output-2', property = 'children'),
+ params = list(input(id = 'input-2', property = 'value')),
+ function(value){
+ Sys.sleep(1)
+ return(value)
+ }
+)
+
+
+app$run_server()
+
+
+
Location Component
+
The location component represents the location bar in your web browser. Through its href
, pathname
, search
and hash
properties you can access different portions of your app’s url. For example, given the url http://127.0.0.1:8050/page-2?a=test#quiz
:
+
+href
= 'http://127.0.0.1:8050/page-2?a=test#quiz'
+pathname
= '/page-2'
+search
= '?a=test'
+hash
= '#quiz'
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/dash-core-components-refresh/vignettes/dash-core-components.html.asis b/components/dash-core-components-refresh/vignettes/dash-core-components.html.asis
new file mode 100644
index 0000000000..f7db6aabf0
--- /dev/null
+++ b/components/dash-core-components-refresh/vignettes/dash-core-components.html.asis
@@ -0,0 +1,3 @@
+%\VignetteEngine{knitr::knitr}
+%\VignetteIndexEntry{Dash Core Components}
+%\usepackage[utf8]{inputenc}
diff --git a/components/dash-core-components-refresh/webpack.config.js b/components/dash-core-components-refresh/webpack.config.js
new file mode 100644
index 0000000000..baa35f6757
--- /dev/null
+++ b/components/dash-core-components-refresh/webpack.config.js
@@ -0,0 +1,144 @@
+const path = require('path');
+const webpack = require('webpack');
+const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
+const WebpackDashDynamicImport = require('@plotly/webpack-dash-dynamic-import');
+
+const packagejson = require('./package.json');
+
+const dashLibraryName = packagejson.name.replace(/-/g, '_');
+
+module.exports = (env, argv) => {
+
+ let mode;
+
+ const overrides = module.exports || {};
+
+ // if user specified mode flag take that value
+ if (argv && argv.mode) {
+ mode = argv.mode;
+ }
+
+ // else if configuration object is already set (module.exports) use that value
+ else if (overrides.mode) {
+ mode = overrides.mode;
+ }
+
+ // else take webpack default (production)
+ else {
+ mode = 'production';
+ }
+
+ let filename = (overrides.output || {}).filename;
+ if (!filename) {
+ filename = `${dashLibraryName}.js`;
+ }
+
+ const entry = overrides.entry || { main: './src/index.js' };
+
+ const externals = ('externals' in overrides) ? overrides.externals : ({
+ react: 'React',
+ 'react-dom': 'ReactDOM',
+ 'prop-types': 'PropTypes'
+ });
+
+ return {
+ mode,
+ entry,
+ target: ['web', 'es5'],
+ output: {
+ path: path.resolve(__dirname, dashLibraryName),
+ chunkFilename: '[name].js',
+ filename,
+ library: {
+ name: dashLibraryName,
+ type: 'window',
+ }
+ },
+ externals,
+ module: {
+ noParse: /node_modules[\\\/]plotly.js-dist-min/,
+ rules: [
+ {
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader'
+ }
+ },
+ {
+ test: /\.jsx?$/,
+ include: /node_modules[\\\/](react-jsx-parser|highlight[.]js|react-markdown|remark-math|is-plain-obj|color|moment|react-dates|react(-virtualized)?-select)[\\\/]/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ babelrc: false,
+ configFile: false,
+ presets: [
+ '@babel/preset-env'
+ ]
+ }
+ }
+ },
+
+ {
+ test: /\.css$/,
+ use: [
+ {
+ loader: 'style-loader',
+ options: {
+ insert: function insertAtTop(element) {
+ var parent = document.querySelector('head');
+ // eslint-disable-next-line no-underscore-dangle
+ var lastInsertedElement =
+ window._lastElementInsertedByStyleLoader;
+
+ if (!lastInsertedElement) {
+ parent.insertBefore(element, parent.firstChild);
+ } else if (lastInsertedElement.nextSibling) {
+ parent.insertBefore(element, lastInsertedElement.nextSibling);
+ } else {
+ parent.appendChild(element);
+ }
+
+ // eslint-disable-next-line no-underscore-dangle
+ window._lastElementInsertedByStyleLoader = element;
+ }
+ }
+ },
+ {
+ loader: 'css-loader',
+ },
+ ],
+ },
+ ],
+ },
+ optimization: {
+ splitChunks: {
+ name: '[name].js',
+ cacheGroups: {
+ async: {
+ chunks: 'async',
+ minSize: 0,
+ name(module, chunks, cacheGroupKey) {
+ return `${cacheGroupKey}-${chunks[0].name}`;
+ }
+ },
+ shared: {
+ chunks: 'all',
+ minSize: 0,
+ minChunks: 2,
+ name: 'dash_core_components_refresh-shared'
+ }
+ }
+ }
+ },
+ plugins: [
+ new WebpackDashDynamicImport(),
+ new webpack.SourceMapDevToolPlugin({
+ filename: '[file].map',
+ exclude: ['async-plotlyjs', 'async-mathjax']
+ }),
+ new NodePolyfillPlugin()
+ ]
+ }
+};
diff --git a/components/dash-core-components/package-lock.json b/components/dash-core-components/package-lock.json
index 87409bc282..c97af594d8 100644
--- a/components/dash-core-components/package-lock.json
+++ b/components/dash-core-components/package-lock.json
@@ -2107,6 +2107,7 @@
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+ "dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
@@ -2227,6 +2228,7 @@
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "dev": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -2236,6 +2238,7 @@
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "dev": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
@@ -2245,6 +2248,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
@@ -2255,7 +2259,8 @@
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
},
"node_modules/@types/json5": {
"version": "0.0.29",
@@ -2267,6 +2272,7 @@
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
+ "dev": true,
"dependencies": {
"undici-types": "~6.20.0"
}
@@ -2287,6 +2293,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
@@ -2295,22 +2302,26 @@
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
- "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="
+ "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
+ "dev": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
- "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="
+ "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
+ "dev": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
- "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="
+ "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
+ "dev": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -2320,12 +2331,14 @@
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
- "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="
+ "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
+ "dev": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -2337,6 +2350,7 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
+ "dev": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
@@ -2345,6 +2359,7 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
+ "dev": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
@@ -2352,12 +2367,14 @@
"node_modules/@webassemblyjs/utf8": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
- "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="
+ "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
+ "dev": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -2373,6 +2390,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
@@ -2385,6 +2403,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -2396,6 +2415,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -2409,6 +2429,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
+ "dev": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
@@ -2461,12 +2482,14 @@
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
- "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
- "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
},
"node_modules/abort-controller": {
"version": "3.0.0",
@@ -2483,6 +2506,7 @@
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
"integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2541,6 +2565,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dev": true,
"dependencies": {
"ajv": "^8.0.0"
},
@@ -2557,6 +2582,7 @@
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -2571,7 +2597,8 @@
"node_modules/ajv-formats/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
},
"node_modules/ansi-regex": {
"version": "5.0.1",
@@ -3126,7 +3153,8 @@
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
},
"node_modules/buffer-xor": {
"version": "1.0.3",
@@ -3324,6 +3352,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
"integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+ "dev": true,
"engines": {
"node": ">=6.0"
}
@@ -3966,6 +3995,7 @@
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -4166,7 +4196,8 @@
"node_modules/es-module-lexer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
- "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
+ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==",
+ "dev": true
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
@@ -4653,6 +4684,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -4664,6 +4696,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
"engines": {
"node": ">=4.0"
}
@@ -4722,7 +4755,8 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
},
"node_modules/fast-glob": {
"version": "3.3.2",
@@ -5089,7 +5123,8 @@
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true
},
"node_modules/global-cache": {
"version": "1.2.1",
@@ -5132,7 +5167,8 @@
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
},
"node_modules/graphemer": {
"version": "1.4.0",
@@ -6156,6 +6192,7 @@
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "dev": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@@ -6169,6 +6206,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -6177,6 +6215,7 @@
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -6236,7 +6275,8 @@
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
@@ -6341,6 +6381,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+ "dev": true,
"engines": {
"node": ">=6.11.5"
}
@@ -6493,7 +6534,8 @@
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
},
"node_modules/merge2": {
"version": "1.4.1",
@@ -6539,6 +6581,7 @@
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -6547,6 +6590,7 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
@@ -7777,20 +7821,6 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
},
- "node_modules/react": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
- "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
- "peer": true,
- "dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/react-addons-shallow-compare": {
"version": "15.6.3",
"resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.3.tgz",
@@ -7856,21 +7886,6 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
- "node_modules/react-dom": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
- "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
- "peer": true,
- "dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
- },
- "peerDependencies": {
- "react": "^16.14.0"
- }
- },
"node_modules/react-dropzone": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.3.0.tgz",
@@ -8312,6 +8327,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8573,20 +8589,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
- "node_modules/scheduler": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
- "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
- "peer": true,
- "dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- },
"node_modules/schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
@@ -8606,6 +8613,7 @@
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -8621,6 +8629,7 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
@@ -8631,7 +8640,8 @@
"node_modules/schema-utils/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
},
"node_modules/semver": {
"version": "6.3.1",
@@ -8645,6 +8655,7 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+ "dev": true,
"dependencies": {
"randombytes": "^2.1.0"
}
@@ -8794,6 +8805,7 @@
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -8803,6 +8815,7 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9142,6 +9155,7 @@
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -9151,6 +9165,7 @@
"version": "5.39.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
+ "dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -9168,6 +9183,7 @@
"version": "5.3.14",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
+ "dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
@@ -9200,7 +9216,8 @@
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
},
"node_modules/test-exclude": {
"version": "6.0.0",
@@ -9480,7 +9497,8 @@
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "dev": true
},
"node_modules/unherit": {
"version": "1.1.3",
@@ -9638,6 +9656,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
@@ -9733,6 +9752,7 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
"integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==",
+ "dev": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -9745,6 +9765,7 @@
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.0.tgz",
"integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.7",
@@ -9861,6 +9882,7 @@
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
"integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10.13.0"
@@ -9870,6 +9892,7 @@
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@@ -9882,6 +9905,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=10.13.0"
@@ -9894,6 +9918,7 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
@@ -9906,6 +9931,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
"engines": {
"node": ">=4.0"
}
@@ -10574,8 +10600,7 @@
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@babel/plugin-syntax-dynamic-import": {
"version": "7.8.3",
@@ -11538,6 +11563,7 @@
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+ "dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
@@ -11640,6 +11666,7 @@
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "dev": true,
"requires": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -11649,6 +11676,7 @@
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "dev": true,
"requires": {
"@types/eslint": "*",
"@types/estree": "*"
@@ -11657,7 +11685,8 @@
"@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true
},
"@types/istanbul-lib-coverage": {
"version": "2.0.6",
@@ -11667,7 +11696,8 @@
"@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
},
"@types/json5": {
"version": "0.0.29",
@@ -11679,6 +11709,7 @@
"version": "22.13.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
+ "dev": true,
"requires": {
"undici-types": "~6.20.0"
}
@@ -11699,6 +11730,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
+ "dev": true,
"requires": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
@@ -11707,22 +11739,26 @@
"@webassemblyjs/floating-point-hex-parser": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
- "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="
+ "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
+ "dev": true
},
"@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
- "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="
+ "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
+ "dev": true
},
"@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
- "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="
+ "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
+ "dev": true
},
"@webassemblyjs/helper-numbers": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
+ "dev": true,
"requires": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -11732,12 +11768,14 @@
"@webassemblyjs/helper-wasm-bytecode": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
- "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="
+ "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
+ "dev": true
},
"@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
+ "dev": true,
"requires": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -11749,6 +11787,7 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
+ "dev": true,
"requires": {
"@xtuc/ieee754": "^1.2.0"
}
@@ -11757,6 +11796,7 @@
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
+ "dev": true,
"requires": {
"@xtuc/long": "4.2.2"
}
@@ -11764,12 +11804,14 @@
"@webassemblyjs/utf8": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
- "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="
+ "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
+ "dev": true
},
"@webassemblyjs/wasm-edit": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
+ "dev": true,
"requires": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -11785,6 +11827,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
+ "dev": true,
"requires": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
@@ -11797,6 +11840,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
+ "dev": true,
"requires": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
@@ -11808,6 +11852,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
+ "dev": true,
"requires": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
@@ -11821,6 +11866,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
+ "dev": true,
"requires": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
@@ -11830,32 +11876,31 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
"integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@webpack-cli/info": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
"integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@webpack-cli/serve": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
"integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
- "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
+ "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
+ "dev": true
},
"@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
- "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
+ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
+ "dev": true
},
"abort-controller": {
"version": "3.0.0",
@@ -11868,14 +11913,14 @@
"acorn": {
"version": "8.8.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
- "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw=="
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true
},
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"airbnb-prop-types": {
"version": "2.16.0",
@@ -11909,6 +11954,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dev": true,
"requires": {
"ajv": "^8.0.0"
},
@@ -11917,6 +11963,7 @@
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -11927,7 +11974,8 @@
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
}
}
},
@@ -12340,7 +12388,8 @@
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true
},
"buffer-xor": {
"version": "1.0.3",
@@ -12467,7 +12516,8 @@
"chrome-trace-event": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz",
- "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="
+ "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==",
+ "dev": true
},
"cipher-base": {
"version": "1.0.4",
@@ -12989,6 +13039,7 @@
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
+ "dev": true,
"requires": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@@ -13140,7 +13191,8 @@
"es-module-lexer": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz",
- "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w=="
+ "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==",
+ "dev": true
},
"es-object-atoms": {
"version": "1.1.1",
@@ -13320,8 +13372,7 @@
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
"integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"eslint-import-resolver-node": {
"version": "0.3.9",
@@ -13504,6 +13555,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
"requires": {
"estraverse": "^5.2.0"
}
@@ -13511,7 +13563,8 @@
"estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
},
"estree-to-babel": {
"version": "3.2.1",
@@ -13555,7 +13608,8 @@
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
},
"fast-glob": {
"version": "3.3.2",
@@ -13823,7 +13877,8 @@
"glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "dev": true
},
"global-cache": {
"version": "1.2.1",
@@ -13850,7 +13905,8 @@
"graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
},
"graphemer": {
"version": "1.4.0",
@@ -14009,8 +14065,7 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"identity-obj-proxy": {
"version": "3.0.0",
@@ -14547,6 +14602,7 @@
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
+ "dev": true,
"requires": {
"@types/node": "*",
"merge-stream": "^2.0.0",
@@ -14556,12 +14612,14 @@
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
},
"supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
"requires": {
"has-flag": "^4.0.0"
}
@@ -14607,7 +14665,8 @@
"json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -14692,7 +14751,8 @@
"loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
- "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg=="
+ "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
+ "dev": true
},
"locate-path": {
"version": "6.0.0",
@@ -14814,7 +14874,8 @@
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true
},
"merge2": {
"version": "1.4.1",
@@ -14851,12 +14912,14 @@
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "dev": true
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dev": true,
"requires": {
"mime-db": "1.52.0"
}
@@ -15518,8 +15581,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"postcss-modules-local-by-default": {
"version": "4.0.4",
@@ -15755,17 +15817,6 @@
}
}
},
- "react": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
- "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
- "peer": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
- }
- },
"react-addons-shallow-compare": {
"version": "15.6.3",
"resolved": "https://registry.npmjs.org/react-addons-shallow-compare/-/react-addons-shallow-compare-15.6.3.tgz",
@@ -15820,18 +15871,6 @@
}
}
},
- "react-dom": {
- "version": "16.14.0",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
- "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==",
- "peer": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
- }
- },
"react-dropzone": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-4.3.0.tgz",
@@ -16168,7 +16207,8 @@
"require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true
},
"resize-observer-polyfill": {
"version": "1.5.1",
@@ -16328,20 +16368,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
- "scheduler": {
- "version": "0.19.1",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz",
- "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==",
- "peer": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- },
"schema-utils": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
+ "dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
@@ -16353,6 +16384,7 @@
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
+ "dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -16364,6 +16396,7 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
+ "dev": true,
"requires": {
"fast-deep-equal": "^3.1.3"
}
@@ -16371,7 +16404,8 @@
"json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
- "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true
}
}
},
@@ -16384,6 +16418,7 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
+ "dev": true,
"requires": {
"randombytes": "^2.1.0"
}
@@ -16504,6 +16539,7 @@
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -16512,7 +16548,8 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
}
}
},
@@ -16731,8 +16768,7 @@
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz",
"integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"styled-jsx": {
"version": "5.1.7",
@@ -16761,12 +16797,14 @@
"tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz",
- "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="
+ "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
+ "dev": true
},
"terser": {
"version": "5.39.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
+ "dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
@@ -16777,7 +16815,8 @@
"commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+ "dev": true
}
}
},
@@ -16785,6 +16824,7 @@
"version": "5.3.14",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
+ "dev": true,
"requires": {
"@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5",
@@ -17015,7 +17055,8 @@
"undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
- "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "dev": true
},
"unherit": {
"version": "1.1.3",
@@ -17132,6 +17173,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
"requires": {
"punycode": "^2.1.0"
}
@@ -17222,6 +17264,7 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz",
"integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==",
+ "dev": true,
"requires": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
@@ -17231,6 +17274,7 @@
"version": "5.101.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.0.tgz",
"integrity": "sha512-B4t+nJqytPeuZlHuIKTbalhljIFXeNRqrUGAQgTGlfOl2lXXKXw+yZu6bicycP+PUlM44CxBjCFD6aciKFT3LQ==",
+ "dev": true,
"requires": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -17262,18 +17306,20 @@
"acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
- "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true
},
"acorn-import-phases": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
- "requires": {}
+ "dev": true
},
"eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
"requires": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
@@ -17282,7 +17328,8 @@
"estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
}
}
},
@@ -17329,7 +17376,8 @@
"webpack-sources": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz",
- "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg=="
+ "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==",
+ "dev": true
},
"which": {
"version": "2.0.2",
diff --git a/components/dash-html-components/package-lock.json b/components/dash-html-components/package-lock.json
index d81fb368bf..9a6b4cbcaa 100644
--- a/components/dash-html-components/package-lock.json
+++ b/components/dash-html-components/package-lock.json
@@ -8389,8 +8389,7 @@
"version": "7.21.0-placeholder-for-preset-env.2",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@babel/plugin-syntax-import-assertions": {
"version": "7.27.1",
@@ -9613,22 +9612,19 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
"integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@webpack-cli/info": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz",
"integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@webpack-cli/serve": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz",
"integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@xtuc/ieee754": {
"version": "1.2.0",
@@ -9652,8 +9648,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"ajv": {
"version": "6.12.6",
@@ -13518,8 +13513,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"eslint-scope": {
"version": "5.1.1",
diff --git a/components/dash-table/package-lock.json b/components/dash-table/package-lock.json
index 1e195d09ba..99c128558f 100644
--- a/components/dash-table/package-lock.json
+++ b/components/dash-table/package-lock.json
@@ -8110,6 +8110,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -8691,6 +8692,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -9426,6 +9428,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -10243,6 +10246,7 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@@ -10545,6 +10549,7 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/react-select": {
diff --git a/dash/__init__.py b/dash/__init__.py
index 6f16a068aa..59ae9a1da5 100644
--- a/dash/__init__.py
+++ b/dash/__init__.py
@@ -15,6 +15,7 @@
from . import exceptions # noqa: F401,E402
from . import resources # noqa: F401,E402
from . import dcc # noqa: F401,E402
+from . import dcc_refresh # noqa: F401,E402
from . import html # noqa: F401,E402
from . import dash_table # noqa: F401,E402
from .version import __version__ # noqa: F401,E402
diff --git a/dash/dash.py b/dash/dash.py
index eab3e3358e..4963e7735e 100644
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -26,6 +26,7 @@
from importlib_metadata import version as _get_distribution_version
from dash import dcc
+from dash import dcc_refresh
from dash import html
from dash import dash_table
@@ -937,7 +938,7 @@ def _collect_and_register_resources(self, resources, include_async=True):
def _relative_url_path(relative_package_path="", namespace=""):
if any(
relative_package_path.startswith(x + "/")
- for x in ["dcc", "html", "dash_table"]
+ for x in ["dcc", "html", "dash_table", "dcc_refresh"]
):
relative_package_path = relative_package_path.replace("dash.", "")
version = importlib.import_module(
@@ -967,7 +968,10 @@ def _relative_url_path(relative_package_path="", namespace=""):
paths = [paths] if isinstance(paths, str) else paths
for rel_path in paths:
- if any(x in rel_path for x in ["dcc", "html", "dash_table"]):
+ if any(
+ x in rel_path
+ for x in ["dcc", "html", "dash_table", "dcc_refresh"]
+ ):
rel_path = rel_path.replace("dash.", "")
self.registered_paths[resource["namespace"]].add(rel_path)
@@ -1060,6 +1064,9 @@ def _generate_scripts_html(self) -> str:
+ self.scripts._resources._filter_resources(
dash_table._js_dist, dev_bundles=dev
)
+ + self.scripts._resources._filter_resources(
+ dcc_refresh._js_dist, dev_bundles=dev
+ )
+ self.scripts._resources._filter_resources(
self._hooks.hooks._js_dist, dev_bundles=dev
)
@@ -2026,7 +2033,7 @@ def enable_dev_tools(
):
component_packages_dist[i : i + 1] = [
os.path.join(os.path.dirname(package.path), x) # type: ignore[reportAttributeAccessIssue]
- for x in ["dcc", "html", "dash_table"]
+ for x in ["dcc", "html", "dash_table", "dcc_refresh"]
]
_reload.watch_thread = threading.Thread(
diff --git a/dash/dcc_refresh/.gitkeep b/dash/dcc_refresh/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/dash/development/update_components.py b/dash/development/update_components.py
index 2789be22fd..767dcf184f 100644
--- a/dash/development/update_components.py
+++ b/dash/development/update_components.py
@@ -23,6 +23,7 @@ class _CombinedFormatter(
"dash-core-components": "dcc",
"dash-html-components": "html",
"dash-table": "dash_table",
+ "dash-core-components-refresh": "dcc_refresh",
}
@@ -40,7 +41,7 @@ def bootstrap_components(components_source, concurrency, install_type):
source_glob = (
components_source
if components_source != "all"
- else "{dash-core-components,dash-html-components,dash-table}"
+ else "{dash-core-components,dash-html-components,dash-table,dash-core-components-refresh}"
)
cmdstr = f"npx lerna exec --concurrency {concurrency} --scope='{source_glob}' -- npm {install_type}"
@@ -76,7 +77,7 @@ def build_components(components_source, concurrency):
source_glob = (
components_source
if components_source != "all"
- else "{dash-core-components,dash-html-components,dash-table}"
+ else "{dash-core-components,dash-html-components,dash-table,dash-core-components-refresh}"
)
cmdstr = f"npx lerna exec --concurrency {concurrency} --scope='{source_glob}' -- npm run build"
diff --git a/package-lock.json b/package-lock.json
index 4d09c5d789..63960cd8f7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9320,8 +9320,7 @@
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
"integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"execa": {
"version": "5.0.0",
@@ -9915,8 +9914,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz",
"integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "13.3.2-cjs.1",
@@ -12470,8 +12468,7 @@
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz",
"integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"execa": {
"version": "5.0.0",
@@ -14937,8 +14934,7 @@
"version": "6.4.6",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz",
"integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==",
- "dev": true,
- "requires": {}
+ "dev": true
},
"picomatch": {
"version": "4.0.3",